Merge "Add waitForIdleSync call to displayAndSaved test" into androidx-main
diff --git a/activity/activity-compose/build.gradle b/activity/activity-compose/build.gradle
index 29c1dc1..e2318ec 100644
--- a/activity/activity-compose/build.gradle
+++ b/activity/activity-compose/build.gradle
@@ -30,7 +30,7 @@
     api("androidx.compose.runtime:runtime:1.0.1")
     api("androidx.compose.runtime:runtime-saveable:1.0.1")
     api(projectOrArtifact(":activity:activity-ktx"))
-    api("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+    api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
     api("androidx.compose.ui:ui:1.0.1")
 
     androidTestImplementation projectOrArtifact(":compose:ui:ui-test-junit4")
diff --git a/activity/activity-ktx/api/current.ignore b/activity/activity-ktx/api/current.ignore
deleted file mode 100644
index 1c56f4f..0000000
--- a/activity/activity-ktx/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.activity.PipHintTrackerKt:
-    Removed class androidx.activity.PipHintTrackerKt
diff --git a/activity/activity-ktx/api/current.txt b/activity/activity-ktx/api/current.txt
index 4023c72..d9a358f 100644
--- a/activity/activity-ktx/api/current.txt
+++ b/activity/activity-ktx/api/current.txt
@@ -6,6 +6,10 @@
     method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
   }
 
+  public final class PipHintTrackerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
 }
 
 package androidx.activity.result {
diff --git a/activity/activity-ktx/api/public_plus_experimental_current.txt b/activity/activity-ktx/api/public_plus_experimental_current.txt
index 980229c..d9a358f 100644
--- a/activity/activity-ktx/api/public_plus_experimental_current.txt
+++ b/activity/activity-ktx/api/public_plus_experimental_current.txt
@@ -7,7 +7,7 @@
   }
 
   public final class PipHintTrackerKt {
-    method @RequiresApi(android.os.Build.VERSION_CODES.O) @kotlinx.coroutines.ExperimentalCoroutinesApi public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
   }
 
 }
diff --git a/activity/activity-ktx/api/restricted_current.ignore b/activity/activity-ktx/api/restricted_current.ignore
deleted file mode 100644
index 1c56f4f..0000000
--- a/activity/activity-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.activity.PipHintTrackerKt:
-    Removed class androidx.activity.PipHintTrackerKt
diff --git a/activity/activity-ktx/api/restricted_current.txt b/activity/activity-ktx/api/restricted_current.txt
index 4023c72..d9a358f 100644
--- a/activity/activity-ktx/api/restricted_current.txt
+++ b/activity/activity-ktx/api/restricted_current.txt
@@ -6,6 +6,10 @@
     method @MainThread public static inline <reified VM extends androidx.lifecycle.ViewModel> kotlin.Lazy<? extends VM> viewModels(androidx.activity.ComponentActivity, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.viewmodel.CreationExtras>? extrasProducer, optional kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>? factoryProducer);
   }
 
+  public final class PipHintTrackerKt {
+    method @RequiresApi(android.os.Build.VERSION_CODES.O) public static suspend Object? trackPipAnimationHintView(android.app.Activity, android.view.View view, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+  }
+
 }
 
 package androidx.activity.result {
diff --git a/activity/activity-ktx/build.gradle b/activity/activity-ktx/build.gradle
index 254798a..2d5ca30 100644
--- a/activity/activity-ktx/build.gradle
+++ b/activity/activity-ktx/build.gradle
@@ -26,14 +26,14 @@
 dependencies {
 
     api(project(":activity:activity"))
-    api("androidx.core:core-ktx:1.1.0") {
+    api("androidx.core:core-ktx:1.9.0") {
         because "Mirror activity dependency graph for -ktx artifacts"
     }
-    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0") {
+    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1") {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0")
-    api("androidx.savedstate:savedstate-ktx:1.2.0") {
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+    api("androidx.savedstate:savedstate-ktx:1.2.1") {
         because 'Mirror activity dependency graph for -ktx artifacts'
     }
     api(libs.kotlinStdlib)
@@ -59,4 +59,4 @@
 
 android {
     namespace "androidx.activity.ktx"
-}
\ No newline at end of file
+}
diff --git a/activity/activity-ktx/src/main/java/androidx/activity/PipHintTracker.kt b/activity/activity-ktx/src/main/java/androidx/activity/PipHintTracker.kt
index 0801c64..710602a 100644
--- a/activity/activity-ktx/src/main/java/androidx/activity/PipHintTracker.kt
+++ b/activity/activity-ktx/src/main/java/androidx/activity/PipHintTracker.kt
@@ -23,7 +23,6 @@
 import android.view.View
 import android.view.ViewTreeObserver
 import androidx.annotation.RequiresApi
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.channels.awaitClose
 import kotlinx.coroutines.flow.callbackFlow
 
@@ -37,7 +36,6 @@
  *
  * @param view the view to use as the reference for the source rect hint
  */
-@ExperimentalCoroutinesApi
 @RequiresApi(Build.VERSION_CODES.O)
 public suspend fun Activity.trackPipAnimationHintView(view: View) {
     // Returns a rect of the window coordinates of a view.
diff --git a/activity/activity/api/current.ignore b/activity/activity/api/current.ignore
new file mode 100644
index 0000000..cd68216
--- /dev/null
+++ b/activity/activity/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.activity.ComponentActivity#onBackPressed():
+    Removed method androidx.activity.ComponentActivity.onBackPressed()
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index 4bbcdbb..ee25f24 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -23,7 +23,6 @@
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method public void invalidateMenu();
     method @Deprecated @CallSuper protected void onActivityResult(int, int, android.content.Intent?);
-    method @MainThread public void onBackPressed();
     method @CallSuper public void onMultiWindowModeChanged(boolean);
     method @CallSuper public void onPictureInPictureModeChanged(boolean);
     method @Deprecated @CallSuper public void onRequestPermissionsResult(int, String![], int[]);
diff --git a/activity/activity/api/public_plus_experimental_current.txt b/activity/activity/api/public_plus_experimental_current.txt
index 4bbcdbb..ee25f24 100644
--- a/activity/activity/api/public_plus_experimental_current.txt
+++ b/activity/activity/api/public_plus_experimental_current.txt
@@ -23,7 +23,6 @@
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method public void invalidateMenu();
     method @Deprecated @CallSuper protected void onActivityResult(int, int, android.content.Intent?);
-    method @MainThread public void onBackPressed();
     method @CallSuper public void onMultiWindowModeChanged(boolean);
     method @CallSuper public void onPictureInPictureModeChanged(boolean);
     method @Deprecated @CallSuper public void onRequestPermissionsResult(int, String![], int[]);
diff --git a/activity/activity/api/restricted_current.ignore b/activity/activity/api/restricted_current.ignore
new file mode 100644
index 0000000..cd68216
--- /dev/null
+++ b/activity/activity/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+RemovedMethod: androidx.activity.ComponentActivity#onBackPressed():
+    Removed method androidx.activity.ComponentActivity.onBackPressed()
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index 2997940..1210c28 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -22,7 +22,6 @@
     method public androidx.lifecycle.ViewModelStore getViewModelStore();
     method public void invalidateMenu();
     method @Deprecated @CallSuper protected void onActivityResult(int, int, android.content.Intent?);
-    method @MainThread public void onBackPressed();
     method @CallSuper public void onMultiWindowModeChanged(boolean);
     method @CallSuper public void onPictureInPictureModeChanged(boolean);
     method @Deprecated @CallSuper public void onRequestPermissionsResult(int, String![], int[]);
diff --git a/activity/activity/build.gradle b/activity/activity/build.gradle
index 8680383..f2b29f7 100644
--- a/activity/activity/build.gradle
+++ b/activity/activity/build.gradle
@@ -18,10 +18,10 @@
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.core:core:1.8.0")
-    api("androidx.lifecycle:lifecycle-runtime:2.6.0")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
-    api("androidx.savedstate:savedstate:1.2.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.0")
+    api("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
+    api("androidx.savedstate:savedstate:1.2.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     implementation("androidx.profileinstaller:profileinstaller:1.2.1")
     implementation("androidx.tracing:tracing:1.0.0")
     api(libs.kotlinStdlib)
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
index ced3ef7..7e3e172 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
+++ b/activity/activity/src/main/java/androidx/activity/ComponentActivity.java
@@ -685,10 +685,16 @@
      * {@link android.app.Activity#onBackPressed()} is invoked.
      *
      * @see #getOnBackPressedDispatcher()
+     *
+     * @deprecated This method has been deprecated in favor of using the
+     * {@link OnBackPressedDispatcher} via {@link #getOnBackPressedDispatcher()}.
+     * The OnBackPressedDispatcher controls how back button events are dispatched
+     * to one or more {@link OnBackPressedCallback} objects.
      */
-    @SuppressWarnings("deprecation")
     @Override
     @MainThread
+    @CallSuper
+    @Deprecated
     public void onBackPressed() {
         mOnBackPressedDispatcher.onBackPressed();
     }
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
index 7333e3e..1d866df 100644
--- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
+++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
@@ -32,10 +32,11 @@
 import androidx.lifecycle.LifecycleEventObserver;
 import androidx.lifecycle.LifecycleOwner;
 
+import kotlin.random.Random;
+
 import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.Map;
-import java.util.Random;
 
 /**
  * A registry that stores {@link ActivityResultCallback activity result callbacks} for
@@ -57,14 +58,11 @@
             "KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS";
     private static final String KEY_COMPONENT_ACTIVITY_PENDING_RESULTS =
             "KEY_COMPONENT_ACTIVITY_PENDING_RESULT";
-    private static final String KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT =
-            "KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT";
 
     private static final String LOG_TAG = "ActivityResultRegistry";
 
     // Use upper 16 bits for request codes
     private static final int INITIAL_REQUEST_CODE_VALUE = 0x00010000;
-    private Random mRandom = new Random();
 
     private final Map<Integer, String> mRcToKey = new HashMap<>();
     final Map<String, Integer> mKeyToRc = new HashMap<>();
@@ -311,7 +309,6 @@
                 new ArrayList<>(mLaunchedKeys));
         outState.putBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS,
                 (Bundle) mPendingResults.clone());
-        outState.putSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT, mRandom);
     }
 
     /**
@@ -333,7 +330,6 @@
         }
         mLaunchedKeys =
                 savedInstanceState.getStringArrayList(KEY_COMPONENT_ACTIVITY_LAUNCHED_KEYS);
-        mRandom = (Random) savedInstanceState.getSerializable(KEY_COMPONENT_ACTIVITY_RANDOM_OBJECT);
         mPendingResults.putAll(
                 savedInstanceState.getBundle(KEY_COMPONENT_ACTIVITY_PENDING_RESULTS));
         for (int i = 0; i < keys.size(); i++) {
@@ -442,10 +438,10 @@
      * @return the number
      */
     private int generateRandomNumber() {
-        int number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1)
+        int number = Random.Default.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1)
                 + INITIAL_REQUEST_CODE_VALUE;
         while (mRcToKey.containsKey(number)) {
-            number = mRandom.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1)
+            number = Random.Default.nextInt((Integer.MAX_VALUE - INITIAL_REQUEST_CODE_VALUE) + 1)
                     + INITIAL_REQUEST_CODE_VALUE;
         }
         return number;
diff --git a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/PipActivity.kt b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/PipActivity.kt
index 056b797..d162c43 100644
--- a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/PipActivity.kt
+++ b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/PipActivity.kt
@@ -30,10 +30,8 @@
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.lifecycleScope
 import androidx.lifecycle.repeatOnLifecycle
-import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
 
-@ExperimentalCoroutinesApi
 class PipActivity : ComponentActivity() {
 
     private lateinit var moveButton: Button
@@ -60,7 +58,6 @@
         return true
     }
 
-    @ExperimentalCoroutinesApi
     private fun trackHintView() {
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
             lifecycleScope.launch {
@@ -96,4 +93,4 @@
             activity.enterPictureInPictureMode(PictureInPictureParams.Builder().build())
         }
     }
-}
\ No newline at end of file
+}
diff --git a/appactions/interaction/interaction-capabilities-communication/api/current.txt b/appactions/interaction/interaction-capabilities-communication/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-communication/api/public_plus_experimental_current.txt b/appactions/interaction/interaction-capabilities-communication/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-communication/api/res-current.txt b/appactions/interaction/interaction-capabilities-communication/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/api/res-current.txt
diff --git a/appactions/interaction/interaction-capabilities-communication/api/restricted_current.txt b/appactions/interaction/interaction-capabilities-communication/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-communication/build.gradle b/appactions/interaction/interaction-capabilities-communication/build.gradle
new file mode 100644
index 0000000..d73076d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    implementation("androidx.annotation:annotation:1.1.0")
+}
+
+android {
+    namespace "androidx.appactions.interaction.capabilities.communication"
+}
+
+androidx {
+    name = "androidx.appactions.interaction:interaction-capabilities-communication"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Capability library for communication apps integrating with virtual assistant."
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/package-info.java b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/package-info.java
new file mode 100644
index 0000000..4c3216d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-communication/src/main/java/androidx/appactions/interaction/capabilities/communication/package-info.java
@@ -0,0 +1,20 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.appactions.interaction.capabilities.communication;
+
+import androidx.annotation.RestrictTo;
diff --git a/appactions/interaction/interaction-capabilities-core/build.gradle b/appactions/interaction/interaction-capabilities-core/build.gradle
index 6c5b2e1..1883f29 100644
--- a/appactions/interaction/interaction-capabilities-core/build.gradle
+++ b/appactions/interaction/interaction-capabilities-core/build.gradle
@@ -29,9 +29,10 @@
 
     api(libs.autoValueAnnotations)
     implementation(libs.guavaListenableFuture)
-    implementation(libs.kotlinCoroutinesGuava)
+    implementation(libs.kotlinCoroutinesCore)
     implementation(libs.kotlinStdlib)
     implementation("androidx.concurrent:concurrent-futures:1.1.0")
+    implementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
 
     testAnnotationProcessor(libs.autoValue)
     testImplementation(libs.junit)
@@ -57,8 +58,7 @@
     }
 
     lintOptions {
-        // TODO(b/266849030): Remove when updating library.
-        disable("UnknownNullness", "SyntheticAccessor")
+        disable("UnknownNullness")
     }
 
     namespace "androidx.appactions.interaction.capabilities.core"
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
new file mode 100644
index 0000000..186b31c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/EndDate.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.builtintypes.properties
+
+import java.time.LocalDate
+
+class EndDate(localDate: LocalDate) {
+    val localDate: LocalDate? = localDate
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
new file mode 100644
index 0000000..8b94d93
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/properties/StartDate.kt
@@ -0,0 +1,23 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.builtintypes.properties
+
+import java.time.LocalDate
+
+class StartDate(localDate: LocalDate) {
+    val localDate: LocalDate? = localDate
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
new file mode 100644
index 0000000..5afb6b6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Alarm.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.builtintypes.types
+
+import androidx.appactions.builtintypes.properties.Name
+
+interface Alarm : Thing {
+    override fun toBuilder(): Builder<*>
+
+    companion object {
+        @JvmStatic
+        fun Builder(): Builder<*> = AlarmBuilderImpl()
+    }
+
+    interface Builder<Self : Builder<Self>> : Thing.Builder<Self> {
+        override fun build(): Alarm
+    }
+}
+
+private class AlarmBuilderImpl : Alarm.Builder<AlarmBuilderImpl> {
+
+    private var identifier: String? = null
+    private var name: Name? = null
+
+    override fun build() = AlarmImpl(identifier, name)
+
+    override fun setIdentifier(text: String?): AlarmBuilderImpl = apply { identifier = text }
+
+    override fun setName(text: String): AlarmBuilderImpl = apply { name = Name(text) }
+
+    override fun setName(name: Name?): AlarmBuilderImpl = apply { this.name = name }
+
+    override fun clearName(): AlarmBuilderImpl = apply { name = null }
+}
+
+private class AlarmImpl(override val identifier: String?, override val name: Name?) : Alarm {
+    override fun toBuilder(): Alarm.Builder<*> =
+        AlarmBuilderImpl().setIdentifier(identifier).setName(name)
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/CalendarEvent.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/CalendarEvent.kt
new file mode 100644
index 0000000..abd0fe1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/CalendarEvent.kt
@@ -0,0 +1,108 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.builtintypes.types
+
+// TODO(b/271634410): Update Attendee references
+import androidx.appactions.builtintypes.properties.EndDate
+import androidx.appactions.builtintypes.properties.Name
+import androidx.appactions.builtintypes.properties.StartDate
+import androidx.appactions.interaction.capabilities.core.values.properties.Attendee
+import java.time.LocalDate
+
+interface CalendarEvent : Thing {
+    val startDate: StartDate?
+    val endDate: EndDate?
+    val attendeeList: List<Attendee>
+    override fun toBuilder(): Builder<*>
+
+    companion object {
+        @JvmStatic
+        fun Builder(): Builder<*> = CalendarEventBuilderImpl()
+    }
+
+    interface Builder<Self : Builder<Self>> : Thing.Builder<Self> {
+        fun setStartDate(startDate: StartDate?): Self
+        fun setStartDate(value: LocalDate): Self
+        fun setEndDate(endDate: EndDate?): Self
+        fun setEndDate(value: LocalDate): Self
+        fun addAttendee(attendee: Attendee): Self
+        fun addAllAttendee(value: Iterable<Attendee>): Self
+
+        override fun build(): CalendarEvent
+    }
+}
+
+private class CalendarEventBuilderImpl : CalendarEvent.Builder<CalendarEventBuilderImpl> {
+
+    private var identifier: String? = null
+    private var name: Name? = null
+    private var startDate: StartDate? = null
+    private var endDate: EndDate? = null
+    private var attendeeList = mutableListOf<Attendee>()
+
+    override fun build() = CalendarEventImpl(identifier, name, startDate, endDate, attendeeList)
+
+    override fun setStartDate(startDate: StartDate?): CalendarEventBuilderImpl = apply {
+        this.startDate = startDate
+    }
+
+    override fun setStartDate(value: LocalDate): CalendarEventBuilderImpl = apply {
+        startDate = StartDate(value)
+    }
+
+    override fun setEndDate(endDate: EndDate?): CalendarEventBuilderImpl = apply {
+        this.endDate = endDate
+    }
+
+    override fun setEndDate(value: LocalDate): CalendarEventBuilderImpl = apply {
+        endDate = EndDate(value)
+    }
+
+    override fun addAttendee(attendee: Attendee): CalendarEventBuilderImpl = apply {
+        attendeeList.add(attendee)
+    }
+
+    override fun addAllAttendee(value: Iterable<Attendee>): CalendarEventBuilderImpl = apply {
+        attendeeList.addAll(value)
+    }
+
+    override fun setIdentifier(text: String?): CalendarEventBuilderImpl =
+        apply { identifier = text }
+
+    override fun setName(text: String): CalendarEventBuilderImpl = apply { name = Name(text) }
+
+    override fun setName(name: Name?): CalendarEventBuilderImpl = apply { this.name = name }
+
+    override fun clearName(): CalendarEventBuilderImpl = apply { name = null }
+}
+
+private class CalendarEventImpl(
+    override val identifier: String?,
+    override val name: Name?,
+    override val startDate: StartDate?,
+    override val endDate: EndDate?,
+    override val attendeeList: List<Attendee>
+) :
+    CalendarEvent {
+    override fun toBuilder(): CalendarEvent.Builder<*> =
+        CalendarEventBuilderImpl()
+            .setIdentifier(identifier)
+            .setName(name)
+            .setStartDate(startDate)
+            .setEndDate(endDate)
+            .addAllAttendee(attendeeList)
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/SafetyCheck.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/SafetyCheck.kt
new file mode 100644
index 0000000..61c9d61
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/SafetyCheck.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.builtintypes.types
+
+import androidx.appactions.builtintypes.properties.Name
+import java.time.Duration
+import java.time.ZonedDateTime
+
+interface SafetyCheck : Thing {
+    val duration: Duration?
+    val checkInTime: ZonedDateTime?
+
+    override fun toBuilder(): Builder<*>
+
+    companion object {
+        @JvmStatic
+        fun Builder(): Builder<*> = SafetyCheckBuilderImpl()
+    }
+
+    interface Builder<Self : Builder<Self>> : Thing.Builder<Self> {
+        fun setDuration(duration: Duration?): Self
+        fun setCheckInTime(checkInTime: ZonedDateTime?): Self
+        override fun build(): SafetyCheck
+    }
+}
+
+private class SafetyCheckBuilderImpl : SafetyCheck.Builder<SafetyCheckBuilderImpl> {
+
+    private var identifier: String? = null
+    private var name: Name? = null
+    private var duration: Duration? = null
+    private var checkInTime: ZonedDateTime? = null
+
+    override fun build(): SafetyCheck = SafetyCheckImpl(identifier, name, duration, checkInTime)
+
+    override fun setDuration(duration: Duration?): SafetyCheckBuilderImpl =
+        apply { this.duration = duration }
+
+    override fun setCheckInTime(checkInTime: ZonedDateTime?): SafetyCheckBuilderImpl =
+        apply { this.checkInTime = checkInTime }
+
+    override fun setIdentifier(text: String?): SafetyCheckBuilderImpl = apply { identifier = text }
+
+    override fun setName(text: String): SafetyCheckBuilderImpl = apply { name = Name(text) }
+
+    override fun setName(name: Name?): SafetyCheckBuilderImpl = apply { this.name = name }
+
+    override fun clearName(): SafetyCheckBuilderImpl = apply { name = null }
+}
+
+private class SafetyCheckImpl(
+    override val identifier: String?,
+    override val name: Name?,
+    override val duration: Duration?,
+    override val checkInTime: ZonedDateTime?
+) : SafetyCheck {
+    override fun toBuilder(): SafetyCheck.Builder<*> =
+        SafetyCheckBuilderImpl()
+            .setIdentifier(identifier)
+            .setName(name)
+            .setDuration(duration)
+            .setCheckInTime(checkInTime)
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
index 4f0ecb3..d9f957a 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Thing.kt
@@ -26,7 +26,7 @@
 
     companion object {
         @JvmStatic
-        fun builder(): Builder<*> = ThingBuilderImpl()
+        fun Builder(): Builder<*> = ThingBuilderImpl()
     }
 
     @Suppress("StaticFinalBuilder")
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Timer.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
new file mode 100644
index 0000000..5a25e18
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/builtintypes/types/Timer.kt
@@ -0,0 +1,53 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.builtintypes.types
+
+import androidx.appactions.builtintypes.properties.Name
+
+interface Timer : Thing {
+    override fun toBuilder(): Builder<*>
+
+    companion object {
+        @JvmStatic
+        fun Builder(): Builder<*> = TimerBuilderImpl()
+    }
+
+    interface Builder<Self : Builder<Self>> : Thing.Builder<Self> {
+        override fun build(): Timer
+    }
+}
+
+private class TimerBuilderImpl : Timer.Builder<TimerBuilderImpl> {
+
+    private var identifier: String? = null
+    private var name: Name? = null
+
+    override fun build() = TimerImpl(identifier, name)
+
+    override fun setIdentifier(text: String?): TimerBuilderImpl = apply { identifier = text }
+
+    override fun setName(text: String): TimerBuilderImpl = apply { name = Name(text) }
+
+    override fun setName(name: Name?): TimerBuilderImpl = apply { this.name = name }
+
+    override fun clearName(): TimerBuilderImpl = apply { name = null }
+}
+
+private class TimerImpl(override val identifier: String?, override val name: Name?) : Timer {
+    override fun toBuilder(): Timer.Builder<*> =
+        TimerBuilderImpl().setIdentifier(identifier).setName(name)
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.kt
index da64c67..d3ba23f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.kt
@@ -18,9 +18,6 @@
 
 import androidx.annotation.RestrictTo
 import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
-import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper
-import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
-import androidx.appactions.interaction.capabilities.core.impl.TouchEventCallback
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
 
 /**
@@ -49,39 +46,11 @@
     fun getAppAction(): AppAction
 
     /**
-     * Executes the action and returns the result of execution.
-     *
-     * @param argumentsWrapper The arguments send from assistant to the activity.
-     * @param callback The callback to receive app action result.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun execute(
-        argumentsWrapper: ArgumentsWrapper,
-        callback: CallbackInternal,
-    ) {
-        throw UnsupportedOperationException()
-    }
-
-    /**
-     * Support for manual input. This method should be invoked by AppInteraction SDKs
-     * (background/foreground), so the developers have a way to report state updates back to
-     * Assistant.
-     *
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun setTouchEventCallback(callback: TouchEventCallback) {}
-
-    /**
      * Create a new capability session. The capability library doesn't maintain registry of
      * capabilities, so it's not going to assign any session id.
      *
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    fun createSession(hostProperties: HostProperties): ActionCapabilitySession {
-        throw UnsupportedOperationException()
-    }
+    fun createSession(hostProperties: HostProperties): ActionCapabilitySession
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutorAsync.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutorAsync.kt
index 532d681..359cb9f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutorAsync.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutorAsync.kt
@@ -16,12 +16,16 @@
 
 package androidx.appactions.interaction.capabilities.core
 
+import androidx.annotation.RestrictTo
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.convertToListenableFuture
 import com.google.common.util.concurrent.ListenableFuture
 
-/**
- * An ListenableFuture-based interface of executing an action.
- */
+/** An ListenableFuture-based interface of executing an action. */
 fun interface ActionExecutorAsync<ArgumentT, OutputT> {
+    @get:RestrictTo(RestrictTo.Scope.LIBRARY)
+    val uiHandle: Any
+        get() = this
+
     /**
      * Calls to execute the action.
      *
@@ -29,4 +33,18 @@
      * @return A ListenableFuture containing the ExecutionResult
      */
     fun execute(argument: ArgumentT): ListenableFuture<ExecutionResult<OutputT>>
+
+    companion object {
+        fun <ArgumentT, OutputT> ActionExecutor<ArgumentT, OutputT>.toActionExecutorAsync():
+            ActionExecutorAsync<ArgumentT, OutputT> =
+            object : ActionExecutorAsync<ArgumentT, OutputT> {
+                override val uiHandle = this@toActionExecutorAsync
+                override fun execute(
+                    argument: ArgumentT,
+                ): ListenableFuture<ExecutionResult<OutputT>> =
+                    convertToListenableFuture("ActionExecutor#execute") {
+                        this@toActionExecutorAsync.execute(argument)
+                    }
+            }
+    }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/BaseSession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/BaseSession.kt
index e4b20fe..f7561eb 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/BaseSession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/BaseSession.kt
@@ -16,13 +16,10 @@
 
 package androidx.appactions.interaction.capabilities.core
 
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.convertToListenableFuture
 import com.google.common.util.concurrent.ListenableFuture
-import kotlinx.coroutines.DelicateCoroutinesApi
-import kotlinx.coroutines.GlobalScope
-import kotlinx.coroutines.guava.future
-/**
- * Base interface for Session of all verticals.
- */
+
+/** Base interface for Session of all verticals. */
 interface BaseSession<ArgumentT, OutputT> {
     /**
      * Implement any initialization logic.
@@ -33,6 +30,7 @@
 
     /**
      * Called when all arguments are finalized.
+     *
      * @param argument the Argument instance containing data for fulfillment.
      * @return an ExecutionResult instance.
      */
@@ -42,17 +40,14 @@
 
     /**
      * Called when all arguments are finalized.
+     *
      * @param argument the Argument instance containing data for fulfillment.
      * @return a ListenableFuture containing an ExecutionResult instance.
      */
-    @kotlin.OptIn(DelicateCoroutinesApi::class)
     fun onFinishAsync(argument: ArgumentT): ListenableFuture<ExecutionResult<OutputT>> {
-        return GlobalScope.future { onFinish(argument) }
+        return convertToListenableFuture("onFinish") { onFinish(argument) }
     }
 
-    /**
-     * Implement any cleanup logic.
-     * This method is called some time after the session finishes.
-     */
+    /** Implement any cleanup logic. This method is called some time after the session finishes. */
     fun onDestroy() {}
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/CapabilityBuilderBase.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/CapabilityBuilderBase.kt
index 536b5c0..3ceb482 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/CapabilityBuilderBase.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/CapabilityBuilderBase.kt
@@ -17,6 +17,7 @@
 package androidx.appactions.interaction.capabilities.core
 
 import androidx.annotation.RestrictTo
+import androidx.appactions.interaction.capabilities.core.ActionExecutorAsync.Companion.toActionExecutorAsync
 import androidx.appactions.interaction.capabilities.core.impl.SingleTurnCapabilityImpl
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
 import androidx.appactions.interaction.capabilities.core.task.impl.AbstractTaskUpdater
@@ -48,7 +49,6 @@
 ) {
     private var id: String? = null
     private var property: PropertyT? = null
-    private var actionExecutor: ActionExecutor<ArgumentT, OutputT>? = null
     private var actionExecutorAsync: ActionExecutorAsync<ArgumentT, OutputT>? = null
     private var sessionFactory: SessionFactory<SessionT>? = null
 
@@ -95,8 +95,7 @@
      * which accepts the ActionExecutorAsync instead.
      */
     fun setExecutor(actionExecutor: ActionExecutor<ArgumentT, OutputT>) = asBuilder().apply {
-        this.actionExecutorAsync = null
-        this.actionExecutor = actionExecutor
+        this.actionExecutorAsync = actionExecutor.toActionExecutorAsync()
     }
 
     /**
@@ -110,7 +109,6 @@
     fun setExecutor(
         actionExecutorAsync: ActionExecutorAsync<ArgumentT, OutputT>,
     ) = asBuilder().apply {
-        this.actionExecutor = null
         this.actionExecutorAsync = actionExecutorAsync
     }
 
@@ -130,14 +128,7 @@
     open fun build(): ActionCapability {
         val checkedId = requireNotNull(id, { "setId must be called before build" })
         val checkedProperty = requireNotNull(property, { "property must not be null." })
-        if (actionExecutor != null) {
-            return SingleTurnCapabilityImpl(
-                checkedId,
-                actionSpec,
-                checkedProperty,
-                actionExecutor!!,
-            )
-        } else if (actionExecutorAsync != null) {
+        if (actionExecutorAsync != null) {
             return SingleTurnCapabilityImpl(
                 checkedId,
                 actionSpec,
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/LibInfo.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/LibInfo.kt
index fb27353..0a11514 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/LibInfo.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/LibInfo.kt
@@ -24,17 +24,18 @@
 
 /** @hide */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-internal data class LibInfo(val context: Context) {
-  fun getVersion(): Version {
-    return Version.parse(
-            context.getResources().getString(R.string.appactions_interaction_library_version))
-  }
+data class LibInfo(val context: Context) {
+    fun getVersion(): Version {
+        return Version.parse(
+            context.resources.getString(R.string.appactions_interaction_library_version)
+        )
+    }
 
-  data class Version(
-    val major: Int,
-    val minor: Int,
-    val patch: Int,
-    val preReleaseId: String? = null,
+    data class Version(
+        val major: Int,
+        val minor: Int,
+        val patch: Int,
+        val preReleaseId: String? = null,
   ) : Comparable<Version> {
 
     override fun compareTo(other: Version) = compareValuesBy(
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilitySession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilitySession.kt
index 2686376..33e2a12 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilitySession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilitySession.kt
@@ -17,7 +17,7 @@
 package androidx.appactions.interaction.capabilities.core.impl
 
 import androidx.annotation.RestrictTo
-import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
 
 /**
  * Internal interface for a session, contains developer's Session instance
@@ -52,7 +52,7 @@
     /**
      * The current state of the multi-turn session including slot values and their statuses.
      */
-    val state: AppAction
+    val state: AppDialogState
 
     /** The current status of the ActionCapabilitySession. */
     val status: Status
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.kt
index c3d767b..3eece1d 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.kt
@@ -47,7 +47,7 @@
             )
         }
 
-        private fun createRequestMetadata(fulfillment: Fulfillment): RequestMetadata? {
+        internal fun createRequestMetadata(fulfillment: Fulfillment): RequestMetadata? {
             return if (
                 fulfillment.type == Fulfillment.Type.UNKNOWN_TYPE ||
                 fulfillment.type == Fulfillment.Type.UNRECOGNIZED
@@ -61,7 +61,7 @@
         @Suppress(
             "DEPRECATION",
         ) // Convert the deprecated "fp.valuesList" property to the new format.
-        private fun convertToArgumentMap(
+        internal fun convertToArgumentMap(
             fulfillment: Fulfillment,
         ): Map<String, List<FulfillmentValue>> {
             val result = mutableMapOf<String, List<FulfillmentValue>>()
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
index a660fd5..80e4af5 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityImpl.kt
@@ -18,10 +18,8 @@
 
 import androidx.annotation.RestrictTo
 import androidx.appactions.interaction.capabilities.core.ActionCapability
-import androidx.appactions.interaction.capabilities.core.ActionExecutor
 import androidx.appactions.interaction.capabilities.core.ActionExecutorAsync
 import androidx.appactions.interaction.capabilities.core.HostProperties
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.ListenableFutureHelper
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
 import androidx.appactions.interaction.proto.TaskInfo
@@ -32,45 +30,12 @@
     PropertyT,
     ArgumentT,
     OutputT,
-    > private constructor(
+    > constructor(
     override val id: String,
     val actionSpec: ActionSpec<PropertyT, ArgumentT, OutputT>,
     val property: PropertyT,
     val actionExecutorAsync: ActionExecutorAsync<ArgumentT, OutputT>,
-    val uiHandle: Any,
 ) : ActionCapability {
-    /** constructor using ActionExecutor. */
-    constructor(
-        id: String,
-        actionSpec: ActionSpec<PropertyT, ArgumentT, OutputT>,
-        property: PropertyT,
-        actionExecutor: ActionExecutor<ArgumentT, OutputT>,
-    ) : this(
-        id,
-        actionSpec,
-        property,
-        ActionExecutorAsync<ArgumentT, OutputT> {
-                argument ->
-            ListenableFutureHelper.convertToListenableFuture {
-                actionExecutor.execute(argument)
-            }
-        },
-        actionExecutor,
-    )
-
-    /** constructor using ActionExecutorAsync.  */
-    constructor(
-        id: String,
-        actionSpec: ActionSpec<PropertyT, ArgumentT, OutputT>,
-        property: PropertyT,
-        actionExecutorAsync: ActionExecutorAsync<ArgumentT, OutputT>,
-    ) : this(
-        id,
-        actionSpec,
-        property,
-        actionExecutorAsync,
-        actionExecutorAsync,
-    )
     override val supportsMultiTurnTask = false
 
     override fun getAppAction(): AppAction {
@@ -84,7 +49,6 @@
         return SingleTurnCapabilitySession(
             actionSpec,
             actionExecutorAsync,
-            uiHandle,
         )
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
index 7ad9d90..6888593 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilitySession.kt
@@ -22,7 +22,7 @@
 import androidx.appactions.interaction.capabilities.core.impl.concurrent.FutureCallback
 import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
-import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
 import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue
 import androidx.appactions.interaction.proto.FulfillmentResponse
 import androidx.appactions.interaction.proto.ParamValue
@@ -39,9 +39,8 @@
     >(
     val actionSpec: ActionSpec<*, ArgumentT, OutputT>,
     val actionExecutorAsync: ActionExecutorAsync<ArgumentT, OutputT>,
-    override val uiHandle: Any,
 ) : ActionCapabilitySession {
-    override val state: AppAction
+    override val state: AppDialogState
         get() {
             throw UnsupportedOperationException()
         }
@@ -50,6 +49,8 @@
             throw UnsupportedOperationException()
         }
 
+    override val uiHandle: Any = actionExecutorAsync.uiHandle
+
     override fun destroy() {}
 
     // single-turn capability does not have touch events
@@ -88,7 +89,7 @@
     }
 
     /** Converts typed {@link ExecutionResult} to {@link FulfillmentResponse} proto. */
-    private fun convertToFulfillmentResponse(
+    internal fun convertToFulfillmentResponse(
         executionResult: ExecutionResult<OutputT>,
     ): FulfillmentResponse {
         val fulfillmentResponseBuilder =
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelper.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelper.kt
index 0bb99a4..ea4af43 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelper.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelper.kt
@@ -1,60 +1,33 @@
 package androidx.appactions.interaction.capabilities.core.impl.concurrent
 
-import java.util.concurrent.Executor
-import java.util.concurrent.TimeUnit
+import androidx.annotation.RestrictTo
+import androidx.concurrent.futures.CallbackToFutureAdapter
 import com.google.common.util.concurrent.ListenableFuture
-import com.google.common.util.concurrent.SettableFuture
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
-import kotlinx.coroutines.cancel
 
 // TODO(b/269525385): merge this into Futures utility class once it's migrated to Kotlin.
-internal class ListenableFutureHelper {
-  companion object {
-    fun <T> convertToListenableFuture(suspendFunction: suspend () -> T): ListenableFuture<T> {
-      val scope = CoroutineScope(Dispatchers.Default)
-      val future = SettableFuture.create<T>()
-
-      scope.launch {
-        try {
-          val result = suspendFunction()
-          future.set(result)
-        } catch (t: Throwable) {
-          future.setException(t)
-        }
-      }
-
-      return object : ListenableFuture<T> {
-        override fun addListener(listener: Runnable, executor: Executor) {
-          future.addListener(listener, executor)
-        }
-
-        override fun isDone(): Boolean {
-          return future.isDone
-        }
-
-        override fun get(): T {
-          return future.get()
-        }
-
-        override fun get(timeout: Long, unit: TimeUnit): T {
-          return future.get(timeout, unit)
-        }
-
-        override fun cancel(mayInterruptIfRunning: Boolean): Boolean {
-          return future.cancel(mayInterruptIfRunning)
-        }
-
-        override fun isCancelled(): Boolean {
-          return future.isCancelled
-        }
-
-        // Add a method to explicitly close the scope
-        fun close() {
-          scope.cancel()
-        }
-      }
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+fun <T> convertToListenableFuture(
+    tag: String,
+    block: suspend CoroutineScope.() -> T,
+): ListenableFuture<T> {
+    val scope = CoroutineScope(Dispatchers.Default)
+    return CallbackToFutureAdapter.getFuture { completer ->
+        val job =
+            scope.launch {
+                try {
+                    completer.set(scope.block())
+                } catch (t: Throwable) {
+                    completer.setException(t)
+                }
+            }
+        completer.addCancellationListener(
+            { job.cancel() },
+            Runnable::run,
+        )
+        "ListenableFutureHelper#convertToListenableFuture for '$tag'"
     }
-  }
-}
\ No newline at end of file
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
index 3b610ad..272395f 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
@@ -646,7 +646,7 @@
         return builder.setStructValue(Struct.newBuilder().putAllFields(fieldsMap).build()).build();
     }
 
-    private static String getStructType(Struct struct) throws StructConversionException {
+    static String getStructType(Struct struct) throws StructConversionException {
         Map<String, Value> fieldsMap = struct.getFieldsMap();
         if (!fieldsMap.containsKey(FIELD_NAME_TYPE)
                 || fieldsMap.get(FIELD_NAME_TYPE).getStringValue().isEmpty()) {
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.kt
index 5da2d41..d2a7ea3 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.kt
@@ -16,13 +16,10 @@
 
 package androidx.appactions.interaction.capabilities.core.task
 
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.ListenableFutureHelper
-
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.convertToListenableFuture
 import com.google.common.util.concurrent.ListenableFuture
 
-/**
- * Provides a mechanism for the app to listen to argument updates from Assistant.
- */
+/** Provides a mechanism for the app to listen to argument updates from Assistant. */
 interface ValueListener<T> {
     /**
      * Invoked when Assistant reports that an argument value has changed. This method should be
@@ -30,10 +27,9 @@
      * initial value change.
      *
      * <p>This method should:
-     *
      * <ul>
-     *   <li>1. validate the given argument value(s).
-     *   <li>2. If the given values are valid, update app UI state if applicable.
+     * <li>1. validate the given argument value(s).
+     * <li>2. If the given values are valid, update app UI state if applicable.
      * </ul>
      *
      * <p>Returns the ValidationResult.
@@ -48,16 +44,13 @@
      * initial value change.
      *
      * <p>This method should:
-     *
      * <ul>
-     *   <li>1. validate the given argument value(s).
-     *   <li>2. If the given values are valid, update app UI state if applicable.
+     * <li>1. validate the given argument value(s).
+     * <li>2. If the given values are valid, update app UI state if applicable.
      * </ul>
      *
      * <p>Returns a ListenableFuture containing the ValidationResult.
      */
     fun onReceivedAsync(value: T): ListenableFuture<ValidationResult> =
-        ListenableFutureHelper.convertToListenableFuture {
-            onReceived(value)
-        }
+        convertToListenableFuture("ValueListener#onReceived") { onReceived(value) }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.java
deleted file mode 100644
index 31570a2..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.java
+++ /dev/null
@@ -1,40 +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.appactions.interaction.capabilities.core.task.impl;
-
-import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper;
-import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal;
-
-import com.google.auto.value.AutoValue;
-
-/** Represents a fulfillment request coming from Assistant. */
-@AutoValue
-abstract class AssistantUpdateRequest {
-
-    static AssistantUpdateRequest create(
-            ArgumentsWrapper argumentsWrapper, CallbackInternal callbackInternal) {
-        return new AutoValue_AssistantUpdateRequest(argumentsWrapper, callbackInternal);
-    }
-
-    /** The fulfillment request data. */
-    abstract ArgumentsWrapper argumentsWrapper();
-
-    /*
-     * The callback to be report results from handling this request.
-     */
-    abstract CallbackInternal callbackInternal();
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.kt
new file mode 100644
index 0000000..4799845
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.annotation.RestrictTo
+import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper
+import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
+
+/**
+ * Represents a fulfillment request coming from Assistant.
+ *
+ * @param argumentsWrapper The fulfillment request data.
+ * @param callbackInternal The callback to report results from handling this request.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+data class AssistantUpdateRequest(
+    val argumentsWrapper: ArgumentsWrapper,
+    val callbackInternal: CallbackInternal,
+)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.java
deleted file mode 100644
index 6fa70e6..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.java
+++ /dev/null
@@ -1,47 +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.appactions.interaction.capabilities.core.task.impl;
-
-import androidx.appactions.interaction.proto.CurrentValue;
-
-import com.google.auto.value.AutoValue;
-
-import java.util.List;
-
-@SuppressWarnings("AutoValueImmutableFields")
-@AutoValue
-abstract class SlotProcessingResult {
-    static SlotProcessingResult create(Boolean isSuccessful, List<CurrentValue> processedValues) {
-        return new AutoValue_SlotProcessingResult(isSuccessful, processedValues);
-    }
-
-    /**
-     * Whether or not the next slot should be processed.
-     *
-     * <p>This is true if the following conditions were met during processing.
-     *
-     * <ul>
-     *   <li>there are no ungroundedValues remaining (either rejected or disambig)
-     *   <li>listener#onReceived returned ACCEPTED for all grounded values (which could be empty
-     *   list)
-     * </ul>
-     */
-    abstract Boolean isSuccessful();
-
-    /** Processed CurrentValue objects. */
-    abstract List<CurrentValue> processedValues();
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.kt
new file mode 100644
index 0000000..93f8e5d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.kt
@@ -0,0 +1,32 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.annotation.RestrictTo
+import androidx.appactions.interaction.proto.CurrentValue
+
+/**
+ * @param isSuccessful Whether or not the next slot should be processed. This is true if the
+ *   following conditions were met during processing.
+ * * there are no ungrounded values remaining (either rejected or disambig)
+ * * listener#onReceived returned ACCEPTED for all grounded values (which could be empty list)
+ *
+ * @param processedValues Processed CurrentValue objects.
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+data class SlotProcessingResult
+constructor(val isSuccessful: Boolean, val processedValues: List<CurrentValue>)
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilitySession.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilitySession.kt
index 16280db..6f044e9 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilitySession.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilitySession.kt
@@ -16,34 +16,38 @@
 
 package androidx.appactions.interaction.capabilities.core.task.impl
 
+import androidx.annotation.GuardedBy
 import androidx.appactions.interaction.capabilities.core.BaseSession
 import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
 import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper
 import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
 import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal
 import androidx.appactions.interaction.capabilities.core.impl.TouchEventCallback
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.FutureCallback
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
 import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
-import androidx.appactions.interaction.proto.ParamValue
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
+import androidx.appactions.interaction.proto.ParamValue
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.launch
 
 internal class TaskCapabilitySession<
     ArgumentT,
     OutputT,
     ConfirmationT,
-    > (
-    val actionSpec: ActionSpec<*, ArgumentT, OutputT>,
-    val appAction: AppAction,
-    val taskHandler: TaskHandler<ConfirmationT>,
-    val externalSession: BaseSession<ArgumentT, OutputT>,
+>(
+    actionSpec: ActionSpec<*, ArgumentT, OutputT>,
+    appAction: AppAction,
+    taskHandler: TaskHandler<ConfirmationT>,
+    externalSession: BaseSession<ArgumentT, OutputT>,
+    private val scope: CoroutineScope = CoroutineScope(Dispatchers.Default),
 ) : ActionCapabilitySession, TaskUpdateHandler {
-    override val state: AppAction
-        get() = sessionOrchestrator.getAppAction()
+    override val state: AppDialogState
+        get() = sessionOrchestrator.appDialogState
 
     // single-turn capability does not have status
     override val status: ActionCapabilitySession.Status
-        get() = sessionOrchestrator.getStatus()
+        get() = sessionOrchestrator.status
 
     override fun destroy() {
         // TODO(b/270751989): cancel current processing request immediately
@@ -56,20 +60,24 @@
     private val requestLock = Any()
 
     /** Contains session state and request processing logic. */
-    private val sessionOrchestrator: TaskOrchestrator<
-        ArgumentT, OutputT, ConfirmationT,> =
+    private val sessionOrchestrator:
+        TaskOrchestrator<
+            ArgumentT,
+            OutputT,
+            ConfirmationT,
+        > =
         TaskOrchestrator(
             actionSpec,
             appAction,
             taskHandler,
             externalSession,
-            Runnable::run,
         )
-    private var pendingAssistantRequest: AssistantUpdateRequest? = null
-    private var pendingTouchEventRequest: TouchEventUpdateRequest? = null
+
+    @GuardedBy("requestLock") private var pendingAssistantRequest: AssistantUpdateRequest? = null
+    @GuardedBy("requestLock") private var pendingTouchEventRequest: TouchEventUpdateRequest? = null
 
     override fun execute(argumentsWrapper: ArgumentsWrapper, callback: CallbackInternal) {
-        enqueueAssistantRequest(AssistantUpdateRequest.create(argumentsWrapper, callback))
+        enqueueAssistantRequest(AssistantUpdateRequest(argumentsWrapper, callback))
     }
 
     override fun updateParamValues(paramValuesMap: Map<String, List<ParamValue>>) {
@@ -89,7 +97,7 @@
      */
     private fun enqueueAssistantRequest(request: AssistantUpdateRequest) {
         synchronized(requestLock) {
-            pendingAssistantRequest?.callbackInternal()?.onError(ErrorStatusInternal.CANCELLED)
+            pendingAssistantRequest?.callbackInternal?.onError(ErrorStatusInternal.CANCELLED)
             pendingAssistantRequest = request
             dispatchPendingRequestIfIdle()
         }
@@ -97,19 +105,16 @@
 
     private fun enqueueTouchEventRequest(request: TouchEventUpdateRequest) {
         synchronized(requestLock) {
-            if (pendingTouchEventRequest == null) {
-                pendingTouchEventRequest = request
-            } else {
-                pendingTouchEventRequest =
-                    TouchEventUpdateRequest.merge(pendingTouchEventRequest!!, request)
-            }
+            pendingTouchEventRequest =
+                if (pendingTouchEventRequest == null) request
+                else pendingTouchEventRequest!!.mergeWith(request)
             dispatchPendingRequestIfIdle()
         }
     }
 
     /**
-     * If sessionOrchestrator is idle, select the next request to dispatch to sessionOrchestrator (if
-     * there are any pending requests).
+     * If sessionOrchestrator is idle, select the next request to dispatch to sessionOrchestrator
+     * (if there are any pending requests).
      *
      * <p>If sessionOrchestrator is not idle, do nothing, since this method will automatically be
      * called when sessionOrchestrator becomes idle.
@@ -121,40 +126,17 @@
             }
             var nextRequest: UpdateRequest? = null
             if (pendingAssistantRequest != null) {
-                nextRequest = UpdateRequest.of(pendingAssistantRequest)
+                nextRequest = UpdateRequest(pendingAssistantRequest!!)
                 pendingAssistantRequest = null
             } else if (pendingTouchEventRequest != null) {
-                nextRequest = UpdateRequest.of(pendingTouchEventRequest)
+                nextRequest = UpdateRequest(pendingTouchEventRequest!!)
                 pendingTouchEventRequest = null
             }
             if (nextRequest != null) {
-                Futures.addCallback(
-                    sessionOrchestrator.processUpdateRequest(nextRequest),
-                    object : FutureCallback<Void?> {
-                        override fun onSuccess(unused: Void?) {
-                            dispatchPendingRequestIfIdle()
-                        }
-
-                        /**
-                         * A fatal exception has occurred, cause by one of the following:
-                         *
-                         * <ul>
-                         *   <li>1. The developer listener threw some runtime exception
-                         *   <li>2. The SDK encountered some uncaught internal exception
-                         * </ul>
-                         *
-                         * <p>In both cases, this exception will be rethrown which will crash
-                         * the app.
-                         */
-                        override fun onFailure(t: Throwable) {
-                            throw IllegalStateException(
-                                "unhandled exception in request processing",
-                                t,
-                            )
-                        }
-                    },
-                    Runnable::run,
-                )
+                scope.launch {
+                    sessionOrchestrator.processUpdateRequest(nextRequest)
+                    dispatchPendingRequestIfIdle()
+                }
             }
         }
     }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.java
deleted file mode 100644
index 1fca454..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.java
+++ /dev/null
@@ -1,251 +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.appactions.interaction.capabilities.core.task.impl;
-
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableMap;
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableSet;
-
-import androidx.annotation.NonNull;
-import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingRequiredArgException;
-import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
-import androidx.appactions.interaction.proto.CurrentValue;
-import androidx.appactions.interaction.proto.CurrentValue.Status;
-import androidx.appactions.interaction.proto.DisambiguationData;
-import androidx.appactions.interaction.proto.Entity;
-import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue;
-import androidx.appactions.interaction.proto.ParamValue;
-import androidx.appactions.interaction.protobuf.Struct;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Set;
-import java.util.stream.IntStream;
-
-/** Utility methods used for implementing Task Capabilities. */
-public final class TaskCapabilityUtils {
-    private TaskCapabilityUtils() {
-    }
-
-    /** Uses Property to detect if all required arguments are present. */
-    static boolean isSlotFillingComplete(
-            Map<String, List<ParamValue>> finalArguments, List<IntentParameter> paramsList) {
-        Set<String> requiredParams =
-                paramsList.stream()
-                        .filter(IntentParameter::getIsRequired)
-                        .map(IntentParameter::getName)
-                        .collect(toImmutableSet());
-        for (String paramName : requiredParams) {
-            if (!finalArguments.containsKey(paramName)) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    static List<CurrentValue> paramValuesToCurrentValue(
-            List<ParamValue> paramValueList, Status status) {
-        return paramValueList.stream()
-                .map(paramValue -> toCurrentValue(paramValue, status))
-                .collect(toImmutableList());
-    }
-
-    static List<FulfillmentValue> paramValuesToFulfillmentValues(List<ParamValue> paramValueList) {
-        return paramValueList.stream()
-                .map(paramValue -> FulfillmentValue.newBuilder().setValue(paramValue).build())
-                .collect(toImmutableList());
-    }
-
-    static Map<String, List<FulfillmentValue>> paramValuesMapToFulfillmentValuesMap(
-            Map<String, List<ParamValue>> paramValueMap) {
-        return paramValueMap.entrySet().stream()
-                .collect(
-                        toImmutableMap(
-                                Map.Entry::getKey,
-                                (entry) -> paramValuesToFulfillmentValues(entry.getValue())));
-    }
-
-    static List<CurrentValue> fulfillmentValuesToCurrentValues(
-            List<FulfillmentValue> fulfillmentValueList, Status status) {
-        return fulfillmentValueList.stream()
-                .map(fulfillmentValue -> toCurrentValue(fulfillmentValue, status))
-                .collect(toImmutableList());
-    }
-
-    static CurrentValue toCurrentValue(ParamValue paramValue, Status status) {
-        return CurrentValue.newBuilder().setValue(paramValue).setStatus(status).build();
-    }
-
-    static CurrentValue toCurrentValue(FulfillmentValue fulfillmentValue, Status status) {
-        CurrentValue.Builder result = CurrentValue.newBuilder();
-        if (fulfillmentValue.hasValue()) {
-            result.setValue(fulfillmentValue.getValue());
-        }
-        if (fulfillmentValue.hasDisambigData()) {
-            result.setDisambiguationData(fulfillmentValue.getDisambigData());
-        }
-        return result.setStatus(status).build();
-    }
-
-    static ParamValue groundedValueToParamValue(Entity groundedEntity) {
-        if (groundedEntity.hasValue()) {
-            return ParamValue.newBuilder()
-                    .setIdentifier(groundedEntity.getIdentifier())
-                    .setStructValue(groundedEntity.getValue())
-                    .build();
-        } else {
-            return ParamValue.newBuilder()
-                    .setIdentifier(groundedEntity.getIdentifier())
-                    .setStringValue(groundedEntity.getName())
-                    .build();
-        }
-    }
-
-    /** Create a CurrentValue based on Disambugation result for a ParamValue. */
-    static CurrentValue getCurrentValueForDisambiguation(
-            ParamValue paramValue, List<Entity> disambiguationEntities) {
-        return CurrentValue.newBuilder()
-                .setValue(paramValue)
-                .setStatus(Status.DISAMBIG)
-                .setDisambiguationData(
-                        DisambiguationData.newBuilder().addAllEntities(disambiguationEntities))
-                .build();
-    }
-
-    /** Convenience method to be used in onFinishListeners. */
-    @NonNull
-    public static List<ParamValue> checkRequiredArg(
-            @NonNull Map<String, List<ParamValue>> args, @NonNull String argName)
-            throws MissingRequiredArgException {
-        List<ParamValue> result = args.get(argName);
-        if (result == null) {
-            throw new MissingRequiredArgException(
-                    String.format(
-                            "'%s' is a required argument but is missing from the final arguments "
-                                    + "map.",
-                            argName));
-        }
-        return result;
-    }
-
-    /** Compares two ParamValue, returns false if they are equivalent, true otherwise. */
-    private static boolean hasParamValueDiff(ParamValue oldArg, ParamValue newArg) {
-        if (oldArg.getValueCase().getNumber() != newArg.getValueCase().getNumber()) {
-            return true;
-        }
-        if (!oldArg.getIdentifier().equals(newArg.getIdentifier())) {
-            return true;
-        }
-        switch (oldArg.getValueCase()) {
-            case VALUE_NOT_SET:
-                return false;
-            case STRING_VALUE:
-                return !oldArg.getStringValue().equals(newArg.getStringValue());
-            case BOOL_VALUE:
-                return oldArg.getBoolValue() != newArg.getBoolValue();
-            case NUMBER_VALUE:
-                return oldArg.getNumberValue() != newArg.getNumberValue();
-            case STRUCT_VALUE:
-                return !Arrays.equals(
-                        oldArg.getStructValue().toByteArray(),
-                        newArg.getStructValue().toByteArray());
-        }
-        return true;
-    }
-
-    /**
-     * Returns true if we can skip processing of new FulfillmentValues for a slot.
-     *
-     * <p>There are two required conditions for skipping processing:
-     *
-     * <ul>
-     *   <li>1. currentValues are all ACCEPTED.
-     *   <li>2. there are no differences between the ParamValues in currentValues and
-     *       fulfillmentValues.
-     * </ul>
-     */
-    static boolean canSkipSlotProcessing(
-            List<CurrentValue> currentValues, List<FulfillmentValue> fulfillmentValues) {
-        if (currentValues.stream()
-                .allMatch(currentValue -> currentValue.getStatus().equals(Status.ACCEPTED))) {
-            if (currentValues.size() == fulfillmentValues.size()) {
-                return IntStream.range(0, fulfillmentValues.size())
-                        .allMatch(
-                                i ->
-                                        !TaskCapabilityUtils.hasParamValueDiff(
-                                                currentValues.get(i).getValue(),
-                                                fulfillmentValues.get(i).getValue()));
-            }
-        }
-        return false;
-    }
-
-    /** Given a {@code List<CurrentValue>} find all the Struct in them as a Map. */
-    private static Map<String, Struct> getStructsFromCurrentValues(
-            List<CurrentValue> currentValues) {
-        Map<String, Struct> candidates = new HashMap<>();
-        for (CurrentValue currentValue : currentValues) {
-            if (currentValue.getStatus() == CurrentValue.Status.ACCEPTED
-                    && currentValue.getValue().hasStructValue()) {
-                candidates.put(
-                        currentValue.getValue().getIdentifier(),
-                        currentValue.getValue().getStructValue());
-            } else if (currentValue.getStatus() == CurrentValue.Status.DISAMBIG) {
-                for (Entity entity : currentValue.getDisambiguationData().getEntitiesList()) {
-                    if (entity.hasValue()) {
-                        candidates.put(entity.getIdentifier(), entity.getValue());
-                    }
-                }
-            }
-        }
-        return Collections.unmodifiableMap(candidates);
-    }
-
-    /**
-     * Grounded values for donated inventory slots are sent as identifier only, so find matching
-     * Struct from previous turn and add them to the fulfillment values.
-     */
-    static List<FulfillmentValue> getMaybeModifiedSlotValues(
-            List<CurrentValue> currentValues, List<FulfillmentValue> newSlotValues) {
-        Map<String, Struct> candidates = getStructsFromCurrentValues(currentValues);
-        if (candidates.isEmpty()) {
-            return newSlotValues;
-        }
-        return newSlotValues.stream()
-                .map(
-                        fulfillmentValue -> {
-                            ParamValue paramValue = fulfillmentValue.getValue();
-                            if (paramValue.hasIdentifier()
-                                    && !paramValue.hasStructValue()
-                                    && candidates.containsKey(paramValue.getIdentifier())) {
-                                // TODO(b/243944366) throw error if struct filling fails for an
-                                //  inventory slot.
-                                return fulfillmentValue.toBuilder()
-                                        .setValue(
-                                                paramValue.toBuilder()
-                                                        .setStructValue(candidates.get(
-                                                                paramValue.getIdentifier())))
-                                        .build();
-                            }
-                            return fulfillmentValue;
-                        })
-                .collect(toImmutableList());
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.kt
new file mode 100644
index 0000000..dbe2113
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.kt
@@ -0,0 +1,193 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.appactions.interaction.proto.AppActionsContext
+import androidx.appactions.interaction.proto.CurrentValue
+import androidx.appactions.interaction.proto.DisambiguationData
+import androidx.appactions.interaction.proto.Entity
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.protobuf.Struct
+import java.util.Arrays
+
+/** Utility methods used for implementing Task Capabilities. */
+internal object TaskCapabilityUtils {
+    /** Uses Property to detect if all required arguments are present. */
+    fun isSlotFillingComplete(
+        finalArguments: Map<String, List<ParamValue>>,
+        paramsList: List<AppActionsContext.IntentParameter>
+    ) = paramsList.filter { it.isRequired }.map { it.name }.all { finalArguments.containsKey(it) }
+
+    fun paramValuesToCurrentValue(
+        paramValueList: List<ParamValue>,
+        status: CurrentValue.Status
+    ): List<CurrentValue> = paramValueList.map { toCurrentValue(it, status) }
+
+    private fun paramValuesToFulfillmentValues(
+        paramValueList: List<ParamValue>
+    ): List<FulfillmentRequest.Fulfillment.FulfillmentValue> =
+        paramValueList.map {
+            FulfillmentRequest.Fulfillment.FulfillmentValue.newBuilder().setValue(it).build()
+        }
+
+    fun paramValuesMapToFulfillmentValuesMap(
+        paramValueMap: Map<String, List<ParamValue>>
+    ): Map<String, List<FulfillmentRequest.Fulfillment.FulfillmentValue>> =
+        paramValueMap.entries.associate { entry ->
+            entry.key to paramValuesToFulfillmentValues(entry.value)
+        }
+
+    fun fulfillmentValuesToCurrentValues(
+        fulfillmentValueList: List<FulfillmentRequest.Fulfillment.FulfillmentValue>,
+        status: CurrentValue.Status
+    ): List<CurrentValue> = fulfillmentValueList.map { toCurrentValue(it, status) }
+
+    fun toCurrentValue(paramValue: ParamValue, status: CurrentValue.Status): CurrentValue =
+        CurrentValue.newBuilder().setValue(paramValue).setStatus(status).build()
+
+    private fun toCurrentValue(
+        fulfillmentValue: FulfillmentRequest.Fulfillment.FulfillmentValue,
+        status: CurrentValue.Status
+    ): CurrentValue {
+        val result = CurrentValue.newBuilder()
+        if (fulfillmentValue.hasValue()) {
+            result.value = fulfillmentValue.value
+        }
+        if (fulfillmentValue.hasDisambigData()) {
+            result.disambiguationData = fulfillmentValue.disambigData
+        }
+        return result.setStatus(status).build()
+    }
+
+    fun groundedValueToParamValue(groundedEntity: Entity): ParamValue =
+        if (groundedEntity.hasValue())
+            ParamValue.newBuilder()
+                .setIdentifier(groundedEntity.identifier)
+                .setStructValue(groundedEntity.value)
+                .build()
+        else
+            ParamValue.newBuilder()
+                .setIdentifier(groundedEntity.identifier)
+                .setStringValue(groundedEntity.name)
+                .build()
+
+    /** Create a [CurrentValue] based on Disambiguation result for a ParamValue. */
+    fun getCurrentValueForDisambiguation(
+        paramValue: ParamValue,
+        disambiguationEntities: List<Entity>
+    ): CurrentValue =
+        CurrentValue.newBuilder()
+            .setValue(paramValue)
+            .setStatus(CurrentValue.Status.DISAMBIG)
+            .setDisambiguationData(
+                DisambiguationData.newBuilder().addAllEntities(disambiguationEntities)
+            )
+            .build()
+
+    /** Compares two [ParamValue], returns false if they are equivalent, true otherwise. */
+    private fun hasParamValueDiff(oldArg: ParamValue, newArg: ParamValue): Boolean {
+        if (oldArg.valueCase.number != newArg.valueCase.number) {
+            return true
+        }
+        return if (oldArg.identifier != newArg.identifier) {
+            true
+        } else
+            when (oldArg.valueCase) {
+                ParamValue.ValueCase.VALUE_NOT_SET -> false
+                ParamValue.ValueCase.STRING_VALUE -> oldArg.stringValue != newArg.stringValue
+                ParamValue.ValueCase.BOOL_VALUE -> oldArg.boolValue != newArg.boolValue
+                ParamValue.ValueCase.NUMBER_VALUE -> oldArg.numberValue != newArg.numberValue
+                ParamValue.ValueCase.STRUCT_VALUE ->
+                    !Arrays.equals(
+                        oldArg.structValue.toByteArray(),
+                        newArg.structValue.toByteArray()
+                    )
+                else -> true
+            }
+    }
+
+    /**
+     * Returns true if we can skip processing of new [fulfillmentValues] for a slot.
+     *
+     * There are two required conditions for skipping processing:
+     * * 1. [currentValues] are all ACCEPTED.
+     * * 2. there are no differences between the [ParamValue]s in [currentValues] and
+     *   [fulfillmentValues].
+     */
+    fun canSkipSlotProcessing(
+        currentValues: List<CurrentValue>,
+        fulfillmentValues: List<FulfillmentRequest.Fulfillment.FulfillmentValue>
+    ): Boolean =
+        currentValues.all { it.status == CurrentValue.Status.ACCEPTED } &&
+            currentValues.size == fulfillmentValues.size &&
+            fulfillmentValues.indices.all {
+                !hasParamValueDiff(currentValues[it].value, fulfillmentValues[it].value)
+            }
+
+    /** Given a [List<CurrentValue>], find all the Struct in them as a Map. */
+    private fun getStructsFromCurrentValues(
+        currentValues: List<CurrentValue>
+    ): Map<String, Struct> {
+        val candidates = mutableMapOf<String, Struct>()
+        for (currentValue in currentValues) {
+            if (
+                currentValue.status == CurrentValue.Status.ACCEPTED &&
+                    currentValue.value.hasStructValue()
+            ) {
+                candidates[currentValue.value.identifier] = currentValue.value.structValue
+            } else if (currentValue.status == CurrentValue.Status.DISAMBIG) {
+                for (entity in currentValue.disambiguationData.entitiesList) {
+                    if (entity.hasValue()) {
+                        candidates[entity.identifier] = entity.value
+                    }
+                }
+            }
+        }
+        return candidates.toMap()
+    }
+
+    /**
+     * Grounded values for donated inventory slots are sent as identifier only, so find matching
+     * Struct from previous turn and add them to the fulfillment values.
+     */
+    fun getMaybeModifiedSlotValues(
+        currentValues: List<CurrentValue>,
+        newSlotValues: List<FulfillmentRequest.Fulfillment.FulfillmentValue>
+    ): List<FulfillmentRequest.Fulfillment.FulfillmentValue> {
+        val candidates = getStructsFromCurrentValues(currentValues)
+        return if (candidates.isEmpty()) {
+            newSlotValues
+        } else
+            newSlotValues.map {
+                val paramValue = it.value
+                if (
+                    paramValue.hasIdentifier() &&
+                        !paramValue.hasStructValue() &&
+                        candidates.containsKey(paramValue.identifier)
+                ) {
+                    // TODO(b/243944366) throw error if struct filling fails for an
+                    //  inventory slot.
+                    return@map it.toBuilder()
+                        .setValue(
+                            paramValue.toBuilder().setStructValue(candidates[paramValue.identifier])
+                        )
+                        .build()
+                }
+                it
+            }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.java
deleted file mode 100644
index 0dd26ab..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.java
+++ /dev/null
@@ -1,678 +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.appactions.interaction.capabilities.core.task.impl;
-
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableMap;
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableSet;
-
-import androidx.annotation.GuardedBy;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appactions.interaction.capabilities.core.BaseSession;
-import androidx.appactions.interaction.capabilities.core.ConfirmationOutput;
-import androidx.appactions.interaction.capabilities.core.ExecutionResult;
-import androidx.appactions.interaction.capabilities.core.InitArg;
-import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession;
-import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper;
-import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal;
-import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal;
-import androidx.appactions.interaction.capabilities.core.impl.TouchEventCallback;
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.FutureCallback;
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures;
-import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
-import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
-import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger;
-import androidx.appactions.interaction.capabilities.core.impl.utils.LoggerInternal;
-import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingRequiredArgException;
-import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
-import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
-import androidx.appactions.interaction.proto.CurrentValue;
-import androidx.appactions.interaction.proto.CurrentValue.Status;
-import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment;
-import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue;
-import androidx.appactions.interaction.proto.FulfillmentResponse;
-import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
-import androidx.appactions.interaction.proto.ParamValue;
-import androidx.appactions.interaction.proto.TaskInfo;
-import androidx.appactions.interaction.proto.TouchEventMetadata;
-import androidx.concurrent.futures.CallbackToFutureAdapter;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.Optional;
-import java.util.Set;
-import java.util.concurrent.Executor;
-import java.util.function.Function;
-
-/**
- * TaskOrchestrator is responsible for holding session state, and processing assistant / manual
- * input updates to update session state.
- *
- * <p>TaskOrchestrator is also responsible to communicating state updates to developer provided
- * listeners.
- *
- * <p>Only one request can be processed at a time.
- */
-final class TaskOrchestrator<ArgumentT, OutputT, ConfirmationT> {
-
-    private static final String LOG_TAG = "TaskOrchestrator";
-    private final ActionSpec<?, ArgumentT, OutputT> mActionSpec;
-    private final AppAction mAppAction;
-    private final TaskHandler<ConfirmationT> mTaskHandler;
-    private final Executor mExecutor;
-    private final BaseSession<ArgumentT, OutputT> mExternalSession;
-
-    /**
-     * Map of argument name to the {@link CurrentValue} which wraps the argument name and status .
-     */
-    private final Map<String, List<CurrentValue>> mCurrentValuesMap;
-    /**
-     * Internal lock to enable synchronization while processing update requests. Also used for
-     * synchronization of Task orchestrator state. ie indicate whether it is idle or not
-     */
-    private final Object mTaskOrchestratorLock = new Object();
-    /**
-     * The callback that should be invoked when manual input processing finishes. This sends the
-     * processing results to the AppInteraction SDKs. Note, this field is not provided on
-     * construction because the callback is not available at the time when the developer creates the
-     * capability.
-     */
-    @Nullable TouchEventCallback mTouchEventCallback;
-    /** Current status of the overall task (i.e. status of the task). */
-    private ActionCapabilitySession.Status mTaskStatus;
-
-    /** True if an UpdateRequest is currently being processed, false otherwise. */
-    @GuardedBy("mTaskOrchestratorLock")
-    private boolean mIsIdle = true;
-
-    TaskOrchestrator(
-            ActionSpec<?, ArgumentT, OutputT> actionSpec,
-            AppAction appAction,
-            TaskHandler<ConfirmationT> taskHandler,
-            BaseSession<ArgumentT, OutputT> externalSession,
-            Executor executor) {
-        this.mActionSpec = actionSpec;
-        this.mAppAction = appAction;
-        this.mTaskHandler = taskHandler;
-        this.mExternalSession = externalSession;
-        this.mExecutor = executor;
-
-        this.mCurrentValuesMap = Collections.synchronizedMap(new HashMap<>());
-        this.mTaskStatus = ActionCapabilitySession.Status.UNINITIATED;
-    }
-    // Set a TouchEventCallback instance. This callback is invoked when state changes from manual
-    // input.
-    void setTouchEventCallback(@Nullable TouchEventCallback touchEventCallback) {
-        this.mTouchEventCallback = touchEventCallback;
-    }
-
-    /** Returns whether or not a request is currently being processed */
-    boolean isIdle() {
-        synchronized (mTaskOrchestratorLock) {
-            return mIsIdle;
-        }
-    }
-
-    ActionCapabilitySession.Status getStatus() {
-        return mTaskStatus;
-    }
-
-    /**
-     * processes the provided UpdateRequest asynchronously.
-     *
-     * <p>Returns a {@code ListenableFuture<Void>} that is completed when the request handling is
-     * completed.
-     *
-     * <p>An unhandled exception when handling an UpdateRequest will cause all future update
-     * requests to fail.
-     *
-     * <p>This method should never be called when isIdle() returns false.
-     */
-    ListenableFuture<Void> processUpdateRequest(UpdateRequest updateRequest) {
-        synchronized (mTaskOrchestratorLock) {
-            if (!mIsIdle) {
-                throw new IllegalStateException(
-                        "processUpdateRequest should never be called when isIdle is false.");
-            }
-            mIsIdle = false;
-            ListenableFuture<Void> requestProcessingFuture;
-            switch (updateRequest.getKind()) {
-                case ASSISTANT:
-                    requestProcessingFuture =
-                            processAssistantUpdateRequest(updateRequest.assistant());
-                    break;
-                case TOUCH_EVENT:
-                    requestProcessingFuture =
-                            processTouchEventUpdateRequest(updateRequest.touchEvent());
-                    break;
-                default:
-                    throw new IllegalArgumentException("unknown UpdateRequest type");
-            }
-            return Futures.transform(
-                    requestProcessingFuture,
-                    unused -> {
-                        synchronized (mTaskOrchestratorLock) {
-                            mIsIdle = true;
-                            return null;
-                        }
-                    },
-                    mExecutor,
-                    "set isIdle");
-        }
-    }
-
-    /** Processes an assistant update request. */
-    private ListenableFuture<Void> processAssistantUpdateRequest(
-            AssistantUpdateRequest assistantUpdateRequest) {
-        ArgumentsWrapper argumentsWrapper = assistantUpdateRequest.argumentsWrapper();
-        CallbackInternal callback = assistantUpdateRequest.callbackInternal();
-
-        if (argumentsWrapper.getRequestMetadata() == null) {
-            callback.onError(ErrorStatusInternal.INVALID_REQUEST_TYPE);
-            return Futures.immediateVoidFuture();
-        }
-        Fulfillment.Type requestType = argumentsWrapper.getRequestMetadata().requestType();
-        switch (requestType) {
-            case UNRECOGNIZED:
-            case UNKNOWN_TYPE:
-                callback.onError(ErrorStatusInternal.INVALID_REQUEST_TYPE);
-                break;
-            case SYNC:
-                return handleSync(argumentsWrapper, callback);
-            case CONFIRM:
-                return handleConfirm(callback);
-            case CANCEL:
-            case TERMINATE:
-                terminate();
-                callback.onSuccess(FulfillmentResponse.getDefaultInstance());
-                break;
-        }
-        return Futures.immediateVoidFuture();
-    }
-
-    public ListenableFuture<Void> processTouchEventUpdateRequest(
-            TouchEventUpdateRequest touchEventUpdateRequest) {
-        Map<String, List<ParamValue>> paramValuesMap = touchEventUpdateRequest.getParamValuesMap();
-        if (mTouchEventCallback == null
-                || paramValuesMap.isEmpty()
-                || mTaskStatus != ActionCapabilitySession.Status.IN_PROGRESS) {
-            return Futures.immediateVoidFuture();
-        }
-        for (Map.Entry<String, List<ParamValue>> entry : paramValuesMap.entrySet()) {
-            String argName = entry.getKey();
-            mCurrentValuesMap.put(
-                    argName,
-                    entry.getValue().stream()
-                            .map(
-                                    paramValue ->
-                                            TaskCapabilityUtils.toCurrentValue(
-                                                    paramValue, Status.ACCEPTED))
-                            .collect(toImmutableList()));
-        }
-        ListenableFuture<Void> argumentsProcessingFuture;
-        if (anyParamsOfStatus(Status.DISAMBIG)) {
-            argumentsProcessingFuture = Futures.immediateVoidFuture();
-        } else {
-            Map<String, List<FulfillmentValue>> fulfillmentValuesMap =
-                    TaskCapabilityUtils.paramValuesMapToFulfillmentValuesMap(
-                            getCurrentPendingArguments());
-            argumentsProcessingFuture = processFulfillmentValues(fulfillmentValuesMap);
-        }
-
-        ListenableFuture<FulfillmentResponse> fulfillmentResponseFuture =
-                Futures.transformAsync(
-                        argumentsProcessingFuture,
-                        (unused) -> maybeConfirmOrFinish(),
-                        mExecutor,
-                        "maybeConfirmOrFinish");
-
-        return invokeTouchEventCallback(fulfillmentResponseFuture);
-    }
-
-    private ListenableFuture<Void> invokeTouchEventCallback(
-            ListenableFuture<FulfillmentResponse> fulfillmentResponseFuture) {
-        return CallbackToFutureAdapter.getFuture(
-                completer -> {
-                    Futures.addCallback(
-                            fulfillmentResponseFuture,
-                            new FutureCallback<FulfillmentResponse>() {
-                                @Override
-                                public void onSuccess(FulfillmentResponse fulfillmentResponse) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.INFO,
-                                            LOG_TAG,
-                                            "Manual input success");
-                                    if (mTouchEventCallback != null) {
-                                        mTouchEventCallback.onSuccess(
-                                                fulfillmentResponse,
-                                                TouchEventMetadata.getDefaultInstance());
-                                    } else {
-                                        LoggerInternal.log(
-                                                CapabilityLogger.LogLevel.ERROR,
-                                                LOG_TAG,
-                                                "Manual input null callback");
-                                    }
-                                    completer.set(null);
-                                }
-
-                                @Override
-                                public void onFailure(@NonNull Throwable t) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.ERROR,
-                                            LOG_TAG,
-                                            "Manual input fail");
-                                    if (mTouchEventCallback != null) {
-                                        mTouchEventCallback.onError(
-                                                ErrorStatusInternal.TOUCH_EVENT_REQUEST_FAILURE);
-                                    } else {
-                                        LoggerInternal.log(
-                                                CapabilityLogger.LogLevel.ERROR,
-                                                LOG_TAG,
-                                                "Manual input null callback");
-                                    }
-                                    completer.set(null);
-                                }
-                            },
-                            mExecutor);
-                    return "handle fulfillmentResponseFuture for manual input";
-                });
-    }
-
-    // TODO: add cleanup logic if any
-    void terminate() {
-        this.mExternalSession.onDestroy();
-        this.mTaskStatus = ActionCapabilitySession.Status.DESTROYED;
-    }
-
-    /**
-     * If slot filling is incomplete, the future contains default FulfillmentResponse.
-     *
-     * <p>Otherwise, the future contains a FulfillmentResponse containing BIC or BIO data.
-     */
-    private ListenableFuture<FulfillmentResponse> maybeConfirmOrFinish() {
-        Map<String, List<ParamValue>> finalArguments = getCurrentAcceptedArguments();
-        if (anyParamsOfStatus(Status.REJECTED)
-                || !TaskCapabilityUtils.isSlotFillingComplete(
-                        finalArguments, mAppAction.getParamsList())) {
-            return Futures.immediateFuture(FulfillmentResponse.getDefaultInstance());
-        }
-        if (mTaskHandler.getOnReadyToConfirmListener() != null) {
-            return getFulfillmentResponseForConfirmation(finalArguments);
-        }
-        return getFulfillmentResponseForExecution(finalArguments);
-    }
-
-    private ListenableFuture<Void> maybeInitializeTask() {
-        if (this.mTaskStatus == ActionCapabilitySession.Status.UNINITIATED) {
-            mExternalSession.onInit(new InitArg());
-        }
-        this.mTaskStatus = ActionCapabilitySession.Status.IN_PROGRESS;
-        return Futures.immediateVoidFuture();
-    }
-
-    /**
-     * Handles a SYNC request from assistant.
-     *
-     * <p>Control-flow logic for a single task turn. Note, a task may start and finish in the same
-     * turn, so the logic should include onEnter, arg validation, and onExit.
-     */
-    private ListenableFuture<Void> handleSync(
-            ArgumentsWrapper argumentsWrapper, CallbackInternal callback) {
-        ListenableFuture<Void> onInitFuture = maybeInitializeTask();
-
-        clearMissingArgs(argumentsWrapper);
-        ListenableFuture<Void> argResolutionFuture =
-                Futures.transformAsync(
-                        onInitFuture,
-                        unused -> processFulfillmentValues(argumentsWrapper.getParamValues()),
-                        mExecutor,
-                        "processFulfillmentValues");
-
-        ListenableFuture<FulfillmentResponse> fulfillmentResponseFuture =
-                Futures.transformAsync(
-                        argResolutionFuture,
-                        unused -> maybeConfirmOrFinish(),
-                        mExecutor,
-                        "maybeConfirmOrFinish");
-        return CallbackToFutureAdapter.getFuture(
-                completer -> {
-                    Futures.addCallback(
-                            fulfillmentResponseFuture,
-                            new FutureCallback<FulfillmentResponse>() {
-                                @Override
-                                public void onSuccess(FulfillmentResponse fulfillmentResponse) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.INFO,
-                                            LOG_TAG,
-                                            "Task sync success");
-                                    callback.onSuccess(fulfillmentResponse);
-                                    completer.set(null);
-                                }
-
-                                @Override
-                                public void onFailure(@NonNull Throwable t) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.ERROR,
-                                            LOG_TAG,
-                                            "Task sync fail",
-                                            t);
-                                    callback.onError(ErrorStatusInternal.SYNC_REQUEST_FAILURE);
-                                    completer.set(null);
-                                }
-                            },
-                            mExecutor);
-                    return "handle fulfillmentResponseFuture for SYNC";
-                });
-    }
-
-    /**
-     * Control-flow logic for a single task turn in which the user has confirmed in the previous
-     * turn.
-     */
-    private ListenableFuture<Void> handleConfirm(CallbackInternal callback) {
-        Map<String, List<ParamValue>> finalArguments = getCurrentAcceptedArguments();
-        return CallbackToFutureAdapter.getFuture(
-                completer -> {
-                    Futures.addCallback(
-                            getFulfillmentResponseForExecution(finalArguments),
-                            new FutureCallback<FulfillmentResponse>() {
-                                @Override
-                                public void onSuccess(FulfillmentResponse fulfillmentResponse) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.INFO,
-                                            LOG_TAG,
-                                            "Task confirm success");
-                                    callback.onSuccess(fulfillmentResponse);
-                                    completer.set(null);
-                                }
-
-                                @Override
-                                public void onFailure(@NonNull Throwable t) {
-                                    LoggerInternal.log(
-                                            CapabilityLogger.LogLevel.ERROR,
-                                            LOG_TAG,
-                                            "Task confirm fail");
-                                    callback.onError(
-                                            ErrorStatusInternal.CONFIRMATION_REQUEST_FAILURE);
-                                    completer.set(null);
-                                }
-                            },
-                            mExecutor);
-                    return "handle fulfillmentResponseFuture for CONFIRM";
-                });
-    }
-
-    private void clearMissingArgs(ArgumentsWrapper assistantArgs) {
-        Set<String> argsCleared =
-                mCurrentValuesMap.keySet().stream()
-                        .filter(argName -> !assistantArgs.getParamValues().containsKey(argName))
-                        .collect(toImmutableSet());
-        for (String arg : argsCleared) {
-            mCurrentValuesMap.remove(arg);
-            // TODO(b/234170829): notify listener#onReceived of the cleared arguments
-        }
-    }
-
-    /**
-     * Main processing chain for both assistant requests and manual input requests. All pending
-     * parameters contained in fulfillmentValuesMap are chained together in a serial fassion. We use
-     * Futures here to make sure long running app processing (such as argument grounding or argument
-     * validation) are executed asynchronously.
-     */
-    private ListenableFuture<Void> processFulfillmentValues(
-            Map<String, List<FulfillmentValue>> fulfillmentValuesMap) {
-        ListenableFuture<SlotProcessingResult> currentFuture =
-                Futures.immediateFuture(SlotProcessingResult.create(true, Collections.emptyList()));
-        for (Map.Entry<String, List<FulfillmentValue>> entry : fulfillmentValuesMap.entrySet()) {
-            String name = entry.getKey();
-            List<FulfillmentValue> fulfillmentValues = entry.getValue();
-            currentFuture =
-                    Futures.transformAsync(
-                            currentFuture,
-                            (previousResult) ->
-                                    maybeProcessSlotAndUpdateCurrentValues(
-                                            previousResult, name, fulfillmentValues),
-                            mExecutor,
-                            "maybeProcessSlotAndUpdateCurrentValues");
-        }
-        // Transform the final Boolean future to a void one.
-        return Futures.transform(currentFuture, (unused) -> null, mExecutor, "return null");
-    }
-
-    private ListenableFuture<SlotProcessingResult> maybeProcessSlotAndUpdateCurrentValues(
-            SlotProcessingResult previousResult,
-            String slotKey,
-            List<FulfillmentValue> newSlotValues) {
-        List<CurrentValue> currentSlotValues =
-                mCurrentValuesMap.getOrDefault(slotKey, Collections.emptyList());
-        List<FulfillmentValue> modifiedSlotValues =
-                TaskCapabilityUtils.getMaybeModifiedSlotValues(currentSlotValues, newSlotValues);
-        if (TaskCapabilityUtils.canSkipSlotProcessing(currentSlotValues, modifiedSlotValues)) {
-            return Futures.immediateFuture(previousResult);
-        }
-        List<CurrentValue> pendingArgs =
-                TaskCapabilityUtils.fulfillmentValuesToCurrentValues(
-                        modifiedSlotValues, Status.PENDING);
-        return Futures.transform(
-                processSlot(slotKey, previousResult, pendingArgs),
-                currentResult -> {
-                    mCurrentValuesMap.put(slotKey, currentResult.processedValues());
-                    return currentResult;
-                },
-                mExecutor,
-                "update currentValuesMap");
-    }
-
-    /**
-     * Process pending param values for a slot.
-     *
-     * <p>If the previous slot was accepted, go through grounding/validation with TaskSlotProcessor,
-     * otherwise just return the pending values as is.
-     */
-    private ListenableFuture<SlotProcessingResult> processSlot(
-            String name, SlotProcessingResult previousResult, List<CurrentValue> pendingArgs) {
-        if (!previousResult.isSuccessful()) {
-            return Futures.immediateFuture(SlotProcessingResult.create(false, pendingArgs));
-        }
-        return TaskSlotProcessor.processSlot(
-                name, pendingArgs, mTaskHandler.getTaskParamMap(), mExecutor);
-    }
-
-    /**
-     * Retrieve all ParamValue from accepted slots in currentValuesMap.
-     *
-     * <p>A slot is considered accepted if all CurrentValues in the slot has ACCEPTED status.
-     */
-    private Map<String, List<ParamValue>> getCurrentAcceptedArguments() {
-        return mCurrentValuesMap.entrySet().stream()
-                .filter(
-                        entry ->
-                                entry.getValue().stream()
-                                        .allMatch(
-                                                currentValue ->
-                                                        currentValue.getStatus()
-                                                                == Status.ACCEPTED))
-                .collect(
-                        toImmutableMap(
-                                Map.Entry::getKey,
-                                entry ->
-                                        entry.getValue().stream()
-                                                .map(CurrentValue::getValue)
-                                                .collect(toImmutableList())));
-    }
-
-    /**
-     * Retrieve all ParamValue from pending slots in currentValuesMap.
-     *
-     * <p>A slot is considered pending if any CurrentValues in the slot has PENDING status.
-     */
-    private Map<String, List<ParamValue>> getCurrentPendingArguments() {
-        return mCurrentValuesMap.entrySet().stream()
-                .filter(
-                        entry ->
-                                entry.getValue().stream()
-                                        .anyMatch(
-                                                currentValue ->
-                                                        currentValue.getStatus() == Status.PENDING))
-                .collect(
-                        toImmutableMap(
-                                Map.Entry::getKey,
-                                entry ->
-                                        entry.getValue().stream()
-                                                .map(CurrentValue::getValue)
-                                                .collect(toImmutableList())));
-    }
-
-    /** Returns true if any CurrentValue in currentValuesMap has the given Status. */
-    private boolean anyParamsOfStatus(Status status) {
-        return mCurrentValuesMap.entrySet().stream()
-                .anyMatch(
-                        entry ->
-                                entry.getValue().stream()
-                                        .anyMatch(
-                                                currentValue ->
-                                                        currentValue.getStatus() == status));
-    }
-
-    private ListenableFuture<ConfirmationOutput<ConfirmationT>> executeOnTaskReadyToConfirm(
-            Map<String, List<ParamValue>> finalArguments) {
-        try {
-            return Objects.requireNonNull(mTaskHandler.getOnReadyToConfirmListener())
-                    .onReadyToConfirm(finalArguments);
-        } catch (StructConversionException | MissingRequiredArgException e) {
-            return Futures.immediateFailedFuture(e);
-        }
-    }
-
-    private ListenableFuture<ExecutionResult<OutputT>> executeOnTaskFinish(
-            Map<String, List<ParamValue>> finalArguments) {
-        ListenableFuture<ExecutionResult<OutputT>> executionResultFuture;
-        try {
-            executionResultFuture =
-                    mExternalSession.onFinishAsync(mActionSpec.buildArgument(finalArguments));
-        } catch (StructConversionException e) {
-            return Futures.immediateFailedFuture(e);
-        }
-        return Futures.transform(
-                executionResultFuture,
-                executionResult -> {
-                    this.mTaskStatus = ActionCapabilitySession.Status.COMPLETED;
-                    return executionResult;
-                },
-                mExecutor,
-                "set taskStatus to COMPLETED");
-    }
-
-    private ListenableFuture<FulfillmentResponse> getFulfillmentResponseForConfirmation(
-            Map<String, List<ParamValue>> finalArguments) {
-        return Futures.transform(
-                executeOnTaskReadyToConfirm(finalArguments),
-                result -> {
-                    FulfillmentResponse.Builder fulfillmentResponse =
-                            FulfillmentResponse.newBuilder();
-                    convertToConfirmationOutput(result)
-                            .ifPresent(fulfillmentResponse::setConfirmationData);
-                    return fulfillmentResponse.build();
-                },
-                mExecutor,
-                "create FulfillmentResponse from ConfirmationOutput");
-    }
-
-    private ListenableFuture<FulfillmentResponse> getFulfillmentResponseForExecution(
-            Map<String, List<ParamValue>> finalArguments) {
-        return Futures.transform(
-                executeOnTaskFinish(finalArguments),
-                result -> {
-                    FulfillmentResponse.Builder fulfillmentResponse =
-                            FulfillmentResponse.newBuilder();
-                    if (mTaskStatus == ActionCapabilitySession.Status.COMPLETED) {
-                        convertToExecutionOutput(result)
-                                .ifPresent(fulfillmentResponse::setExecutionOutput);
-                    }
-                    return fulfillmentResponse.build();
-                },
-                mExecutor,
-                "create FulfillmentResponse from ExecutionResult");
-    }
-
-    private List<IntentParameter> addStateToParamsContext(List<IntentParameter> params) {
-        List<IntentParameter> updatedList = new ArrayList<>();
-        params.stream()
-                .forEach(
-                        param -> {
-                            List<CurrentValue> vals = mCurrentValuesMap.get(param.getName());
-                            if (vals != null) {
-                                updatedList.add(
-                                        param.toBuilder()
-                                                .clearCurrentValue()
-                                                .addAllCurrentValue(vals)
-                                                .build());
-                            } else {
-                                updatedList.add(param);
-                            }
-                        });
-        return updatedList;
-    }
-
-    AppAction getAppAction() {
-        return mAppAction.toBuilder()
-                .clearParams()
-                .addAllParams(addStateToParamsContext(mAppAction.getParamsList()))
-                .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
-                .build();
-    }
-
-    /** Convert from java capabilities {@link ExecutionResult} to {@link StructuredOutput} proto. */
-    private Optional<StructuredOutput> convertToExecutionOutput(
-            ExecutionResult<OutputT> executionResult) {
-        OutputT output = executionResult.getOutput();
-        if (output == null) {
-            return Optional.empty();
-        }
-
-        return Optional.of(mActionSpec.convertOutputToProto(output));
-    }
-
-    /**
-     * Convert from java capabilities {@link ConfirmationOutput} to {@link StructuredOutput} proto.
-     */
-    private Optional<StructuredOutput> convertToConfirmationOutput(
-            ConfirmationOutput<ConfirmationT> confirmationOutput) {
-        ConfirmationT confirmation = confirmationOutput.getConfirmation();
-        if (confirmation == null || confirmation instanceof Void) {
-            return Optional.empty();
-        }
-
-        StructuredOutput.Builder confirmationOutputBuilder = StructuredOutput.newBuilder();
-        for (Map.Entry<String, Function<ConfirmationT, List<ParamValue>>> entry :
-                mTaskHandler.getConfirmationDataBindings().entrySet()) {
-            confirmationOutputBuilder.addOutputValues(
-                    StructuredOutput.OutputValue.newBuilder()
-                            .setName(entry.getKey())
-                            .addAllValues(entry.getValue().apply(confirmation))
-                            .build());
-        }
-        return Optional.of(confirmationOutputBuilder.build());
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.kt
new file mode 100644
index 0000000..c234681
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.kt
@@ -0,0 +1,448 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.annotation.GuardedBy
+import androidx.appactions.interaction.capabilities.core.BaseSession
+import androidx.appactions.interaction.capabilities.core.ConfirmationOutput
+import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.InitArg
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
+import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper
+import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
+import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal
+import androidx.appactions.interaction.capabilities.core.impl.TouchEventCallback
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec
+import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger
+import androidx.appactions.interaction.capabilities.core.impl.utils.LoggerInternal
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.InvalidResolverException
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingEntityConverterException
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingRequiredArgException
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingSearchActionConverterException
+import androidx.appactions.interaction.proto.AppActionsContext
+import androidx.appactions.interaction.proto.CurrentValue
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.FulfillmentResponse
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.proto.TouchEventMetadata
+import androidx.concurrent.futures.await
+import java.util.Collections
+import kotlin.jvm.Throws
+
+/**
+ * TaskOrchestrator is responsible for holding session state, and processing assistant / manual
+ * input updates to update session state.
+ *
+ * TaskOrchestrator is also responsible to communicating state updates to developer provided
+ * listeners.
+ *
+ * Only one request can be processed at a time.
+ */
+internal class TaskOrchestrator<ArgumentT, OutputT, ConfirmationT>(
+    private val actionSpec: ActionSpec<*, ArgumentT, OutputT>,
+    private val appAction: AppActionsContext.AppAction,
+    private val taskHandler: TaskHandler<ConfirmationT>,
+    private val externalSession: BaseSession<ArgumentT, OutputT>
+) {
+    /** Map of argument name to the [CurrentValue] which wraps the argument name and status . */
+    private val currentValuesMap: MutableMap<String, List<CurrentValue>> =
+        Collections.synchronizedMap(HashMap())
+
+    /**
+     * The callback that should be invoked when manual input processing finishes. This sends the
+     * processing results to the AppInteraction SDKs. Note, this field is not provided on
+     * construction because the callback is not available at the time when the developer creates the
+     * capability.
+     */
+    private var mTouchEventCallback: TouchEventCallback? = null
+
+    /** Current status of the overall task (i.e. status of the task). */
+    internal var status: ActionCapabilitySession.Status = ActionCapabilitySession.Status.UNINITIATED
+        private set
+
+    // Set a TouchEventCallback instance. This callback is invoked when state changes from manual
+    // input.
+    internal fun setTouchEventCallback(touchEventCallback: TouchEventCallback?) {
+        mTouchEventCallback = touchEventCallback
+    }
+
+    private val inProgressLock = Any()
+
+    @GuardedBy("inProgressLock") private var inProgress = false
+
+    /** Returns whether or not a request is currently being processed */
+    internal fun isIdle(): Boolean = synchronized(inProgressLock) { !inProgress }
+
+    internal val appDialogState: AppActionsContext.AppDialogState
+        get() =
+            AppActionsContext.AppDialogState.newBuilder()
+                .addAllParams(
+                    appAction.paramsList.map { intentParam ->
+                        val dialogParameterBuilder =
+                            AppActionsContext.DialogParameter.newBuilder().setName(intentParam.name)
+                        currentValuesMap[intentParam.name]?.let {
+                            dialogParameterBuilder.addAllCurrentValue(it)
+                        }
+                        dialogParameterBuilder.build()
+                    }
+                )
+                .setFulfillmentIdentifier(appAction.identifier)
+                .build()
+
+    /**
+     * processes the provided UpdateRequest asynchronously.
+     *
+     * Returns when the request handling is completed.
+     *
+     * An unhandled exception when handling an UpdateRequest will cause all future update requests
+     * to fail.
+     *
+     * This method should never be called when isIdle() returns false.
+     */
+    internal suspend fun processUpdateRequest(updateRequest: UpdateRequest) {
+        synchronized(inProgressLock) {
+            if (inProgress) {
+                throw IllegalStateException(
+                    "processUpdateRequest should never be called when the task orchestrator" +
+                        " isn't idle."
+                )
+            }
+            inProgress = true
+        }
+        try {
+            if (updateRequest.assistantRequest != null) {
+                processAssistantUpdateRequest(updateRequest.assistantRequest)
+            } else if (updateRequest.touchEventRequest != null) {
+                processTouchEventUpdateRequest(updateRequest.touchEventRequest)
+            } else {
+                throw IllegalArgumentException("unknown UpdateRequest type")
+            }
+        } finally {
+            synchronized(inProgressLock) { inProgress = false }
+        }
+    }
+
+    /** Processes an assistant update request. */
+    @Suppress("DEPRECATION")
+    private suspend fun processAssistantUpdateRequest(
+        assistantUpdateRequest: AssistantUpdateRequest
+    ) {
+        val argumentsWrapper = assistantUpdateRequest.argumentsWrapper
+        val callback = assistantUpdateRequest.callbackInternal
+        if (argumentsWrapper.requestMetadata == null) {
+            callback.onError(ErrorStatusInternal.INVALID_REQUEST_TYPE)
+            return
+        }
+        when (argumentsWrapper.requestMetadata.requestType()) {
+            FulfillmentRequest.Fulfillment.Type.UNRECOGNIZED,
+            FulfillmentRequest.Fulfillment.Type.UNKNOWN_TYPE ->
+                callback.onError(ErrorStatusInternal.INVALID_REQUEST_TYPE)
+            FulfillmentRequest.Fulfillment.Type.SYNC -> handleSync(argumentsWrapper, callback)
+            FulfillmentRequest.Fulfillment.Type.CONFIRM -> handleConfirm(callback)
+            FulfillmentRequest.Fulfillment.Type.CANCEL,
+            FulfillmentRequest.Fulfillment.Type.TERMINATE -> {
+                terminate()
+                callback.onSuccess(FulfillmentResponse.getDefaultInstance())
+            }
+        }
+    }
+
+    private suspend fun processTouchEventUpdateRequest(
+        touchEventUpdateRequest: TouchEventUpdateRequest
+    ) {
+        val paramValuesMap = touchEventUpdateRequest.paramValuesMap
+        if (
+            mTouchEventCallback == null ||
+                paramValuesMap.isEmpty() ||
+                status !== ActionCapabilitySession.Status.IN_PROGRESS
+        ) {
+            return
+        }
+        for ((argName, value) in paramValuesMap) {
+            currentValuesMap[argName] =
+                value.map { TaskCapabilityUtils.toCurrentValue(it, CurrentValue.Status.ACCEPTED) }
+        }
+
+        try {
+            if (!anyParamsOfStatus(CurrentValue.Status.DISAMBIG)) {
+                val fulfillmentValuesMap =
+                    TaskCapabilityUtils.paramValuesMapToFulfillmentValuesMap(
+                        currentPendingArguments
+                    )
+                processFulfillmentValues(fulfillmentValuesMap)
+            }
+            val fulfillmentResponse = maybeConfirmOrFinish()
+            LoggerInternal.log(CapabilityLogger.LogLevel.INFO, LOG_TAG, "Manual input success")
+            if (mTouchEventCallback != null) {
+                mTouchEventCallback!!.onSuccess(
+                    fulfillmentResponse,
+                    TouchEventMetadata.getDefaultInstance()
+                )
+            } else {
+                LoggerInternal.log(
+                    CapabilityLogger.LogLevel.ERROR,
+                    LOG_TAG,
+                    "Manual input null callback"
+                )
+            }
+        } catch (t: Throwable) {
+            LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, "Manual input fail")
+            if (mTouchEventCallback != null) {
+                mTouchEventCallback!!.onError(ErrorStatusInternal.TOUCH_EVENT_REQUEST_FAILURE)
+            } else {
+                LoggerInternal.log(
+                    CapabilityLogger.LogLevel.ERROR,
+                    LOG_TAG,
+                    "Manual input null callback"
+                )
+            }
+        }
+    }
+
+    // TODO: add cleanup logic if any
+    internal fun terminate() {
+        externalSession.onDestroy()
+        status = ActionCapabilitySession.Status.DESTROYED
+    }
+
+    /**
+     * If slot filling is incomplete, the future contains default FulfillmentResponse.
+     *
+     * Otherwise, the future contains a FulfillmentResponse containing BIC or BIO data.
+     */
+    @Throws(StructConversionException::class, MissingRequiredArgException::class)
+    private suspend fun maybeConfirmOrFinish(): FulfillmentResponse {
+        val finalArguments = currentAcceptedArguments
+        if (
+            anyParamsOfStatus(CurrentValue.Status.REJECTED) ||
+                !TaskCapabilityUtils.isSlotFillingComplete(finalArguments, appAction.paramsList)
+        ) {
+            return FulfillmentResponse.getDefaultInstance()
+        }
+        return if (taskHandler.onReadyToConfirmListener != null)
+            getFulfillmentResponseForConfirmation(finalArguments)
+        else getFulfillmentResponseForExecution(finalArguments)
+    }
+
+    private fun maybeInitializeTask() {
+        if (status === ActionCapabilitySession.Status.UNINITIATED) {
+            externalSession.onInit(InitArg())
+        }
+        status = ActionCapabilitySession.Status.IN_PROGRESS
+    }
+
+    /**
+     * Handles a SYNC request from assistant.
+     *
+     * Control-flow logic for a single task turn. Note, a task may start and finish in the same
+     * turn, so the logic should include onEnter, arg validation, and onExit.
+     */
+    private suspend fun handleSync(argumentsWrapper: ArgumentsWrapper, callback: CallbackInternal) {
+        maybeInitializeTask()
+        clearMissingArgs(argumentsWrapper)
+        try {
+            processFulfillmentValues(argumentsWrapper.paramValues)
+            val fulfillmentResponse = maybeConfirmOrFinish()
+            LoggerInternal.log(CapabilityLogger.LogLevel.INFO, LOG_TAG, "Task sync success")
+            callback.onSuccess(fulfillmentResponse)
+        } catch (t: Throwable) {
+            LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, "Task sync fail", t)
+            callback.onError(ErrorStatusInternal.SYNC_REQUEST_FAILURE)
+        }
+    }
+
+    /**
+     * Control-flow logic for a single task turn in which the user has confirmed in the previous
+     * turn.
+     */
+    private suspend fun handleConfirm(callback: CallbackInternal) {
+        val finalArguments = currentAcceptedArguments
+        try {
+            val fulfillmentResponse = getFulfillmentResponseForExecution(finalArguments)
+            LoggerInternal.log(CapabilityLogger.LogLevel.INFO, LOG_TAG, "Task confirm success")
+            callback.onSuccess(fulfillmentResponse)
+        } catch (t: Throwable) {
+            LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, "Task confirm fail")
+            callback.onError(ErrorStatusInternal.CONFIRMATION_REQUEST_FAILURE)
+        }
+    }
+
+    private fun clearMissingArgs(assistantArgs: ArgumentsWrapper) {
+        val argsCleared =
+            currentValuesMap.keys.filter { !assistantArgs.paramValues.containsKey(it) }
+        for (arg in argsCleared) {
+            currentValuesMap.remove(arg)
+            // TODO(b/234170829): notify listener#onReceived of the cleared arguments
+        }
+    }
+
+    /**
+     * Main processing chain for both assistant requests and manual input requests. All pending
+     * parameters contained in fulfillmentValuesMap are chained together in a serial fashion. We use
+     * Futures here to make sure long running app processing (such as argument grounding or argument
+     * validation) are executed asynchronously.
+     */
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    private suspend fun processFulfillmentValues(
+        fulfillmentValuesMap: Map<String, List<FulfillmentRequest.Fulfillment.FulfillmentValue>>
+    ) {
+        var currentResult = SlotProcessingResult(true, emptyList())
+        for ((name, fulfillmentValues) in fulfillmentValuesMap) {
+            currentResult =
+                maybeProcessSlotAndUpdateCurrentValues(currentResult, name, fulfillmentValues)
+        }
+    }
+
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    private suspend fun maybeProcessSlotAndUpdateCurrentValues(
+        previousResult: SlotProcessingResult,
+        slotKey: String,
+        newSlotValues: List<FulfillmentRequest.Fulfillment.FulfillmentValue>
+    ): SlotProcessingResult {
+        val currentSlotValues = currentValuesMap.getOrDefault(slotKey, emptyList())
+        val modifiedSlotValues =
+            TaskCapabilityUtils.getMaybeModifiedSlotValues(currentSlotValues, newSlotValues)
+        if (TaskCapabilityUtils.canSkipSlotProcessing(currentSlotValues, modifiedSlotValues)) {
+            return previousResult
+        }
+        val pendingArgs =
+            TaskCapabilityUtils.fulfillmentValuesToCurrentValues(
+                modifiedSlotValues,
+                CurrentValue.Status.PENDING
+            )
+        val currentResult = processSlot(slotKey, previousResult, pendingArgs)
+        currentValuesMap[slotKey] = currentResult.processedValues
+        return currentResult
+    }
+
+    /**
+     * Process pending param values for a slot.
+     *
+     * If the previous slot was accepted, go through grounding/validation with TaskSlotProcessor,
+     * otherwise just return the pending values as is.
+     */
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    private suspend fun processSlot(
+        name: String,
+        previousResult: SlotProcessingResult,
+        pendingArgs: List<CurrentValue>
+    ): SlotProcessingResult {
+        return if (!previousResult.isSuccessful) SlotProcessingResult(false, pendingArgs)
+        else TaskSlotProcessor.processSlot(name, pendingArgs, taskHandler.taskParamMap)
+    }
+
+    /**
+     * Retrieve all ParamValue from accepted slots in currentValuesMap.
+     *
+     * A slot is considered accepted if all CurrentValues in the slot has ACCEPTED status.
+     */
+    private val currentAcceptedArguments: Map<String, List<ParamValue>>
+        get() =
+            currentValuesMap
+                .filterValues { currentValues ->
+                    currentValues.all { it.status == CurrentValue.Status.ACCEPTED }
+                }
+                .mapValues { currentValue -> currentValue.value.map { it.value } }
+
+    /**
+     * Retrieve all ParamValue from pending slots in currentValuesMap.
+     *
+     * A slot is considered pending if any CurrentValues in the slot has PENDING status.
+     */
+    private val currentPendingArguments: Map<String, List<ParamValue>>
+        get() =
+            currentValuesMap
+                .filterValues { currentValues ->
+                    currentValues.any { it.status == CurrentValue.Status.PENDING }
+                }
+                .mapValues { currentValues -> currentValues.value.map { it.value } }
+
+    /** Returns true if any CurrentValue in currentValuesMap has the given Status. */
+    private fun anyParamsOfStatus(status: CurrentValue.Status) =
+        currentValuesMap.values.any { currentValues -> currentValues.any { it.status == status } }
+
+    @Throws(StructConversionException::class, MissingRequiredArgException::class)
+    private suspend fun getFulfillmentResponseForConfirmation(
+        finalArguments: Map<String, List<ParamValue>>
+    ): FulfillmentResponse {
+        val result = taskHandler.onReadyToConfirmListener!!.onReadyToConfirm(finalArguments).await()
+        val fulfillmentResponse = FulfillmentResponse.newBuilder()
+        convertToConfirmationOutput(result)?.let { fulfillmentResponse.confirmationData = it }
+        return fulfillmentResponse.build()
+    }
+
+    @Throws(StructConversionException::class)
+    private suspend fun getFulfillmentResponseForExecution(
+        finalArguments: Map<String, List<ParamValue>>
+    ): FulfillmentResponse {
+        val result = externalSession.onFinishAsync(actionSpec.buildArgument(finalArguments)).await()
+        status = ActionCapabilitySession.Status.COMPLETED
+        val fulfillmentResponse = FulfillmentResponse.newBuilder()
+        convertToExecutionOutput(result)?.let { value: FulfillmentResponse.StructuredOutput? ->
+            fulfillmentResponse.executionOutput = value
+        }
+        return fulfillmentResponse.build()
+    }
+
+    /**
+     * Convert from java capabilities [ExecutionResult] to [FulfillmentResponse.StructuredOutput]
+     * proto.
+     */
+    private fun convertToExecutionOutput(
+        executionResult: ExecutionResult<OutputT>
+    ): FulfillmentResponse.StructuredOutput? =
+        executionResult.output?.let { actionSpec.convertOutputToProto(it) }
+
+    /**
+     * Convert from java capabilities [ConfirmationOutput] to [FulfillmentResponse.StructuredOutput]
+     * proto.
+     */
+    private fun convertToConfirmationOutput(
+        confirmationOutput: ConfirmationOutput<ConfirmationT>
+    ): FulfillmentResponse.StructuredOutput? {
+        val confirmation = confirmationOutput.confirmation ?: return null
+        val confirmationOutputBuilder = FulfillmentResponse.StructuredOutput.newBuilder()
+        for ((key, value) in taskHandler.confirmationDataBindings) {
+            confirmationOutputBuilder.addOutputValues(
+                FulfillmentResponse.StructuredOutput.OutputValue.newBuilder()
+                    .setName(key)
+                    .addAllValues(value.apply(confirmation))
+                    .build()
+            )
+        }
+        return confirmationOutputBuilder.build()
+    }
+
+    companion object {
+        private const val LOG_TAG = "TaskOrchestrator"
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.java
deleted file mode 100644
index ae78e97..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.java
+++ /dev/null
@@ -1,349 +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.appactions.interaction.capabilities.core.task.impl;
-
-import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
-
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures;
-import androidx.appactions.interaction.capabilities.core.impl.converters.DisambigEntityConverter;
-import androidx.appactions.interaction.capabilities.core.impl.converters.SearchActionConverter;
-import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
-import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult;
-import androidx.appactions.interaction.capabilities.core.task.ValidationResult;
-import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.InvalidResolverException;
-import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingEntityConverterException;
-import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingSearchActionConverterException;
-import androidx.appactions.interaction.capabilities.core.values.SearchAction;
-import androidx.appactions.interaction.proto.CurrentValue;
-import androidx.appactions.interaction.proto.CurrentValue.Status;
-import androidx.appactions.interaction.proto.DisambiguationData;
-import androidx.appactions.interaction.proto.Entity;
-import androidx.appactions.interaction.proto.ParamValue;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.List;
-import java.util.Map;
-import java.util.Objects;
-import java.util.concurrent.Executor;
-
-/**
- * Contains static utility methods that handles processing argument slots for TaskCapabilityImpl.
- */
-final class TaskSlotProcessor {
-
-    private TaskSlotProcessor() {}
-
-    /** perform an in-app search for an ungrounded ParamValue */
-    private static <T> ListenableFuture<AppGroundingResult> ground(
-            ParamValue ungroundedParamValue, TaskParamBinding<T> binding, Executor executor) {
-        GenericResolverInternal<T> fieldResolver = binding.getResolver();
-        if (binding.getEntityConverter() == null) {
-            return Futures.immediateFailedFuture(
-                    new MissingEntityConverterException(
-                            "No entity converter found in the binding."));
-        }
-        if (binding.getSearchActionConverter() == null) {
-            return Futures.immediateFailedFuture(
-                    new MissingSearchActionConverterException(
-                            "No search action converter found in the binding."));
-        }
-        DisambigEntityConverter<T> entityConverter = binding.getEntityConverter();
-        SearchActionConverter<T> searchActionConverter = binding.getSearchActionConverter();
-        try {
-            SearchAction<T> searchAction =
-                    searchActionConverter.toSearchAction(ungroundedParamValue);
-            // Note, transformAsync is needed to catch checked exceptions. See
-            // https://yaqs.corp.google.com/eng/q/2565415714299052032.
-            return Futures.transformAsync(
-                    fieldResolver.invokeLookup(searchAction),
-                    (entitySearchResult) -> {
-                        try {
-                            return Futures.immediateFuture(
-                                    processEntitySearchResult(
-                                            entitySearchResult,
-                                            entityConverter,
-                                            ungroundedParamValue));
-                        } catch (StructConversionException e) {
-                            return Futures.immediateFailedFuture(e);
-                        }
-                    },
-                    executor,
-                    "processEntitySearchResult");
-        } catch (InvalidResolverException | StructConversionException e) {
-            return Futures.immediateFailedFuture(e);
-        }
-    }
-
-    /**
-     * Applies "wildcard capture" technique. For more details see
-     * https://docs.oracle.com/javase/tutorial/java/generics/capture.html
-     */
-    private static <T> ListenableFuture<ValidationResult> invokeValueChange(
-            List<ParamValue> updatedValue, TaskParamBinding<T> binding) {
-        try {
-            return binding.getResolver().notifyValueChange(updatedValue, binding.getConverter());
-        } catch (StructConversionException e) {
-            return Futures.immediateFailedFuture(e);
-        }
-    }
-
-    /**
-     * Processes all ParamValue for a single slot.
-     *
-     * @return a {@code ListenableFuture<SlotProcessingResult>} object.
-     */
-    static ListenableFuture<SlotProcessingResult> processSlot(
-            String name,
-            List<CurrentValue> pendingArgs,
-            Map<String, TaskParamBinding<?>> taskParamMap,
-            Executor executor) {
-        TaskParamBinding<?> taskParamBinding = taskParamMap.get(name);
-        if (taskParamBinding == null) {
-            // TODO(b/234655571) use slot metadata to ensure that we never auto accept values for
-            // reference slots.
-            return Futures.immediateFuture(
-                    SlotProcessingResult.create(
-                            Boolean.TRUE,
-                            pendingArgs.stream()
-                                    .map(
-                                            pendingArg ->
-                                                    TaskCapabilityUtils.toCurrentValue(
-                                                            pendingArg.getValue(), Status.ACCEPTED))
-                                    .collect(toImmutableList())));
-        }
-        List<ParamValue> groundedValues = Collections.synchronizedList(new ArrayList<>());
-        List<CurrentValue> ungroundedValues = Collections.synchronizedList(new ArrayList<>());
-
-        ListenableFuture<AppGroundingResult> groundingFuture =
-                Futures.immediateFuture(
-                        AppGroundingResult.ofSuccess(ParamValue.getDefaultInstance()));
-
-        for (CurrentValue pendingValue : pendingArgs) {
-            if (pendingValue.hasDisambiguationData()) {
-                // assistant-driven disambiguation
-                groundingFuture =
-                        consumeGroundingResult(
-                                chainAssistantGrounding(
-                                        groundingFuture, pendingValue, taskParamBinding, executor),
-                                groundedValues,
-                                ungroundedValues,
-                                executor);
-            } else if (taskParamBinding.getGroundingPredicate().invoke(pendingValue.getValue())) {
-                // app-driven disambiguation
-                groundingFuture =
-                        consumeGroundingResult(
-                                chainAppGrounding(
-                                        groundingFuture, pendingValue, taskParamBinding, executor),
-                                groundedValues,
-                                ungroundedValues,
-                                executor);
-            } else {
-                groundedValues.add(pendingValue.getValue());
-            }
-        }
-        return Futures.transformAsync(
-                groundingFuture,
-                (unused) -> {
-                    if (groundedValues.isEmpty()) {
-                        return Futures.immediateFuture(
-                                SlotProcessingResult.create(
-                                        /** isSuccessful= */
-                                        false, Collections.unmodifiableList(ungroundedValues)));
-                    }
-                    return Futures.transform(
-                            invokeValueChange(groundedValues, taskParamBinding),
-                            validationResult ->
-                                    processValidationResult(
-                                            validationResult, groundedValues, ungroundedValues),
-                            executor,
-                            "validation");
-                },
-                executor,
-                "slot processing result");
-    }
-
-    /**
-     * Consumes the result of grounding.
-     *
-     * <p>If grounding was successful (app-driven with 1 returned result) the grounded ParamValue is
-     * added to groundedValues.
-     *
-     * <p>otherwise the ungrounded CurrentValue is added to ungroundedValues.
-     */
-    static ListenableFuture<AppGroundingResult> consumeGroundingResult(
-            ListenableFuture<AppGroundingResult> resultFuture,
-            List<ParamValue> groundedValues,
-            List<CurrentValue> ungroundedValues,
-            Executor executor) {
-        return Futures.transform(
-                resultFuture,
-                appGroundingResult -> {
-                    switch (appGroundingResult.getKind()) {
-                        case SUCCESS:
-                            groundedValues.add(appGroundingResult.success());
-                            break;
-                        case FAILURE:
-                            ungroundedValues.add(appGroundingResult.failure());
-                    }
-                    return appGroundingResult;
-                },
-                executor,
-                "consume grounding result");
-    }
-
-    /** enqueues processing of a pending value that requires assistant-driven grounding. */
-    static ListenableFuture<AppGroundingResult> chainAssistantGrounding(
-            ListenableFuture<AppGroundingResult> groundingFuture,
-            CurrentValue pendingValue,
-            TaskParamBinding<?> taskParamBinding,
-            Executor executor) {
-        return Futures.transformAsync(
-                groundingFuture,
-                previousResult -> {
-                    switch (previousResult.getKind()) {
-                        case SUCCESS:
-                            return Futures.transform(
-                                    renderAssistantDisambigData(
-                                            pendingValue.getDisambiguationData(), taskParamBinding),
-                                    unused ->
-                                            AppGroundingResult.ofFailure(
-                                                    CurrentValue.newBuilder(pendingValue)
-                                                            .setStatus(Status.DISAMBIG)
-                                                            .build()),
-                                    executor,
-                                    "renderAssistantDisambigData");
-                        case FAILURE:
-                            return Futures.immediateFuture(
-                                    AppGroundingResult.ofFailure(pendingValue));
-                    }
-                    throw new IllegalStateException("unreachable");
-                },
-                executor,
-                "assistant grounding");
-    }
-
-    /** enqueues processing of a pending value that requires app-driven grounding. */
-    static ListenableFuture<AppGroundingResult> chainAppGrounding(
-            ListenableFuture<AppGroundingResult> groundingFuture,
-            CurrentValue pendingValue,
-            TaskParamBinding<?> taskParamBinding,
-            Executor executor) {
-        return Futures.transformAsync(
-                groundingFuture,
-                previousResult -> {
-                    switch (previousResult.getKind()) {
-                        case SUCCESS:
-                            return ground(pendingValue.getValue(), taskParamBinding, executor);
-                        case FAILURE:
-                            return Futures.immediateFuture(
-                                    AppGroundingResult.ofFailure(pendingValue));
-                    }
-                    throw new IllegalStateException("unreachable");
-                },
-                executor,
-                "app grounding");
-    }
-
-    /**
-     * Processes the EntitySearchResult from performing an entity search.
-     *
-     * @param entitySearchResult the EntitySearchResult returned from the app resolver.
-     * @param ungroundedValue the original ungrounded ParamValue.
-     */
-    private static <T> AppGroundingResult processEntitySearchResult(
-            EntitySearchResult<T> entitySearchResult,
-            DisambigEntityConverter<T> entityConverter,
-            ParamValue ungroundedValue)
-            throws StructConversionException {
-        switch (entitySearchResult.getPossibleValues().size()) {
-            case 0:
-                return AppGroundingResult.ofFailure(
-                        TaskCapabilityUtils.toCurrentValue(ungroundedValue, Status.REJECTED));
-            case 1:
-                Entity groundedEntity =
-                        entityConverter.convert(
-                                Objects.requireNonNull(
-                                        entitySearchResult.getPossibleValues().get(0)));
-                return AppGroundingResult.ofSuccess(
-                        TaskCapabilityUtils.groundedValueToParamValue(groundedEntity));
-            default:
-                List<Entity> disambigEntities =
-                        getDisambigEntities(
-                                entitySearchResult.getPossibleValues(), entityConverter);
-                return AppGroundingResult.ofFailure(
-                        TaskCapabilityUtils.getCurrentValueForDisambiguation(
-                                ungroundedValue, disambigEntities));
-        }
-    }
-
-    private static <T> List<Entity> getDisambigEntities(
-            List<T> possibleValues, DisambigEntityConverter<T> entityConverter)
-            throws StructConversionException {
-        List<Entity> disambigEntities = new ArrayList<>();
-        for (T entity : possibleValues) {
-            disambigEntities.add(entityConverter.convert(Objects.requireNonNull(entity)));
-        }
-        return Collections.unmodifiableList(disambigEntities);
-    }
-
-    /**
-     * Processes the ValidationResult from sending argument updates to onReceived.
-     *
-     * @param validationResult the ValidationResult returned from value listener.
-     * @param groundedValues a List of all grounded ParamValue.
-     * @param ungroundedValues a List of all ungrounded CurrentValue.
-     */
-    private static SlotProcessingResult processValidationResult(
-            ValidationResult validationResult,
-            List<ParamValue> groundedValues,
-            List<CurrentValue> ungroundedValues) {
-        List<CurrentValue> combinedValues = new ArrayList<>();
-        switch (validationResult.getKind()) {
-            case ACCEPTED:
-                combinedValues.addAll(
-                        TaskCapabilityUtils.paramValuesToCurrentValue(
-                                groundedValues, Status.ACCEPTED));
-                break;
-            case REJECTED:
-                combinedValues.addAll(
-                        TaskCapabilityUtils.paramValuesToCurrentValue(
-                                groundedValues, Status.REJECTED));
-                break;
-        }
-        combinedValues.addAll(ungroundedValues);
-        return SlotProcessingResult.create(
-                /* isSuccessful= */ ungroundedValues.isEmpty()
-                        && (validationResult.getKind() == ValidationResult.Kind.ACCEPTED),
-                Collections.unmodifiableList(combinedValues));
-    }
-
-    private static ListenableFuture<Void> renderAssistantDisambigData(
-            DisambiguationData disambiguationData, TaskParamBinding<?> binding) {
-        List<String> entityIds =
-                disambiguationData.getEntitiesList().stream()
-                        .map(Entity::getIdentifier)
-                        .collect(toImmutableList());
-        try {
-            return binding.getResolver().invokeEntityRender(entityIds);
-        } catch (InvalidResolverException e) {
-            return Futures.immediateFailedFuture(e);
-        }
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.kt
new file mode 100644
index 0000000..fc41c53
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.kt
@@ -0,0 +1,286 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.appactions.interaction.capabilities.core.impl.converters.DisambigEntityConverter
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException
+import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.InvalidResolverException
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingEntityConverterException
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingSearchActionConverterException
+import androidx.appactions.interaction.proto.CurrentValue
+import androidx.appactions.interaction.proto.DisambiguationData
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.concurrent.futures.await
+import kotlin.IllegalStateException
+import kotlin.String
+import kotlin.Throws
+
+/**
+ * Contains static utility methods that handles processing argument slots for TaskCapabilityImpl.
+ */
+internal object TaskSlotProcessor {
+    /**
+     * Processes all ParamValue for a single slot.
+     *
+     * @return a [SlotProcessingResult] object.
+     */
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    suspend fun processSlot(
+        name: String,
+        pendingArgs: List<CurrentValue>,
+        taskParamMap: Map<String, TaskParamBinding<*>>,
+    ): SlotProcessingResult {
+        // TODO(b/234655571) use slot metadata to ensure that we never auto accept values
+        // for reference slots.
+        val taskParamBinding =
+            taskParamMap[name]
+                ?: return SlotProcessingResult(
+                    true,
+                    pendingArgs.map {
+                        TaskCapabilityUtils.toCurrentValue(it.value, CurrentValue.Status.ACCEPTED)
+                    }
+                )
+        val groundedValues = mutableListOf<ParamValue>()
+        val ungroundedValues = mutableListOf<CurrentValue>()
+        var groundingResult = AppGroundingResult.ofSuccess(ParamValue.getDefaultInstance())
+        for (pendingValue in pendingArgs) {
+            if (pendingValue.hasDisambiguationData()) {
+                // assistant-driven disambiguation
+                groundingResult =
+                    consumeGroundingResult(
+                        chainAssistantGrounding(groundingResult, pendingValue, taskParamBinding),
+                        groundedValues,
+                        ungroundedValues
+                    )
+            } else if (taskParamBinding.groundingPredicate.invoke(pendingValue.value)) {
+                // app-driven disambiguation
+                groundingResult =
+                    consumeGroundingResult(
+                        chainAppGrounding(groundingResult, pendingValue, taskParamBinding),
+                        groundedValues,
+                        ungroundedValues
+                    )
+            } else {
+                groundedValues.add(pendingValue.value)
+            }
+        }
+        return if (groundedValues.isEmpty()) {
+            SlotProcessingResult(
+                /* isSuccessful= */ false,
+                ungroundedValues.toList(),
+            )
+        } else {
+            processValidationResult(
+                invokeValueChange(groundedValues, taskParamBinding),
+                groundedValues,
+                ungroundedValues
+            )
+        }
+    }
+
+    /** enqueues processing of a pending value that requires assistant-driven grounding. */
+    @Throws(InvalidResolverException::class)
+    private suspend fun chainAssistantGrounding(
+        groundingResult: AppGroundingResult,
+        pendingValue: CurrentValue,
+        taskParamBinding: TaskParamBinding<*>
+    ) =
+        when (groundingResult.kind) {
+            AppGroundingResult.Kind.SUCCESS -> {
+                renderAssistantDisambigData(pendingValue.disambiguationData, taskParamBinding)
+                AppGroundingResult.ofFailure(
+                    CurrentValue.newBuilder(pendingValue)
+                        .setStatus(CurrentValue.Status.DISAMBIG)
+                        .build()
+                )
+            }
+            AppGroundingResult.Kind.FAILURE -> AppGroundingResult.ofFailure(pendingValue)
+            else -> throw IllegalStateException("unreachable")
+        }
+
+    /** enqueues processing of a pending value that requires app-driven grounding. */
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    private suspend fun chainAppGrounding(
+        groundingResult: AppGroundingResult,
+        pendingValue: CurrentValue,
+        taskParamBinding: TaskParamBinding<*>,
+    ) =
+        when (groundingResult.kind) {
+            AppGroundingResult.Kind.SUCCESS -> ground(pendingValue.value, taskParamBinding)
+            AppGroundingResult.Kind.FAILURE -> AppGroundingResult.ofFailure(pendingValue)
+            else -> throw IllegalStateException("unreachable")
+        }
+
+    /**
+     * Consumes the result of grounding.
+     *
+     * If grounding was successful (app-driven with 1 returned result) the grounded ParamValue is
+     * added to groundedValues.
+     *
+     * otherwise the ungrounded CurrentValue is added to ungroundedValues.
+     */
+    private fun consumeGroundingResult(
+        groundingResult: AppGroundingResult,
+        groundedValues: MutableList<ParamValue>,
+        ungroundedValues: MutableList<CurrentValue>,
+    ): AppGroundingResult {
+        when (groundingResult.kind) {
+            AppGroundingResult.Kind.SUCCESS -> groundedValues.add(groundingResult.success())
+            AppGroundingResult.Kind.FAILURE -> ungroundedValues.add(groundingResult.failure())
+            else -> throw IllegalStateException("unreachable")
+        }
+        return groundingResult
+    }
+
+    /**
+     * Applies "wildcard capture" technique. For more details see
+     * [...](https://docs.oracle.com/javase/tutorial/java/generics/capture.html)
+     */
+    @Throws(StructConversionException::class)
+    private suspend fun <T> invokeValueChange(
+        updatedValue: List<ParamValue>,
+        binding: TaskParamBinding<T>
+    ): ValidationResult {
+        return binding.resolver.notifyValueChange(updatedValue, binding.converter).await()
+    }
+
+    @Throws(InvalidResolverException::class)
+    private suspend fun renderAssistantDisambigData(
+        disambiguationData: DisambiguationData,
+        binding: TaskParamBinding<*>
+    ) {
+        val entityIds = disambiguationData.entitiesList.map { it.identifier }
+        binding.resolver.invokeEntityRender(entityIds).await()
+    }
+
+    /**
+     * Processes the ValidationResult from sending argument updates to onReceived.
+     *
+     * @param validationResult the ValidationResult returned from value listener.
+     * @param groundedValues a List of all grounded ParamValue.
+     * @param ungroundedValues a List of all ungrounded CurrentValue.
+     */
+    private fun processValidationResult(
+        validationResult: ValidationResult,
+        groundedValues: List<ParamValue>,
+        ungroundedValues: List<CurrentValue>
+    ): SlotProcessingResult {
+        val combinedValues = mutableListOf<CurrentValue>()
+        when (validationResult.kind) {
+            ValidationResult.Kind.ACCEPTED ->
+                combinedValues.addAll(
+                    TaskCapabilityUtils.paramValuesToCurrentValue(
+                        groundedValues,
+                        CurrentValue.Status.ACCEPTED
+                    )
+                )
+            ValidationResult.Kind.REJECTED ->
+                combinedValues.addAll(
+                    TaskCapabilityUtils.paramValuesToCurrentValue(
+                        groundedValues,
+                        CurrentValue.Status.REJECTED
+                    )
+                )
+        }
+        combinedValues.addAll(ungroundedValues)
+        return SlotProcessingResult(
+            /* isSuccessful= */ ungroundedValues.isEmpty() &&
+                validationResult.kind === ValidationResult.Kind.ACCEPTED,
+            combinedValues.toList()
+        )
+    }
+
+    /** perform an in-app search for an ungrounded ParamValue */
+    @Throws(
+        MissingEntityConverterException::class,
+        MissingSearchActionConverterException::class,
+        StructConversionException::class,
+        InvalidResolverException::class,
+    )
+    private suspend fun <T> ground(
+        ungroundedParamValue: ParamValue,
+        binding: TaskParamBinding<T>,
+    ): AppGroundingResult {
+        if (binding.entityConverter == null) {
+            throw MissingEntityConverterException("No entity converter found in the binding.")
+        }
+        if (binding.searchActionConverter == null) {
+            throw MissingSearchActionConverterException(
+                "No search action converter found in the binding."
+            )
+        }
+        val entityConverter = binding.entityConverter
+        val searchActionConverter = binding.searchActionConverter
+        val searchAction = searchActionConverter.toSearchAction(ungroundedParamValue)
+        val entitySearchResult = binding.resolver.invokeLookup(searchAction).await()
+        return processEntitySearchResult<T>(
+            entitySearchResult,
+            entityConverter,
+            ungroundedParamValue
+        )
+    }
+
+    /**
+     * Processes the EntitySearchResult from performing an entity search.
+     *
+     * @param entitySearchResult the EntitySearchResult returned from the app resolver.
+     * @param ungroundedValue the original ungrounded ParamValue.
+     */
+    @Throws(StructConversionException::class)
+    private fun <T> processEntitySearchResult(
+        entitySearchResult: EntitySearchResult<T>,
+        entityConverter: DisambigEntityConverter<T>,
+        ungroundedValue: ParamValue
+    ): AppGroundingResult {
+        return when (entitySearchResult.possibleValues.size) {
+            0 ->
+                AppGroundingResult.ofFailure(
+                    TaskCapabilityUtils.toCurrentValue(
+                        ungroundedValue,
+                        CurrentValue.Status.REJECTED
+                    )
+                )
+            1 -> {
+                val groundedEntity = entityConverter.convert(entitySearchResult.possibleValues[0]!!)
+                AppGroundingResult.ofSuccess(
+                    TaskCapabilityUtils.groundedValueToParamValue(groundedEntity)
+                )
+            }
+            else -> {
+                val disambigEntities =
+                    entitySearchResult.possibleValues.map { entityConverter.convert(it) }
+                AppGroundingResult.ofFailure(
+                    TaskCapabilityUtils.getCurrentValueForDisambiguation(
+                        ungroundedValue,
+                        disambigEntities
+                    )
+                )
+            }
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TouchEventUpdateRequest.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TouchEventUpdateRequest.kt
index 176fb6d..d7b9a5c 100644
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TouchEventUpdateRequest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TouchEventUpdateRequest.kt
@@ -16,24 +16,23 @@
 
 package androidx.appactions.interaction.capabilities.core.task.impl
 
+import androidx.annotation.RestrictTo
 import androidx.appactions.interaction.proto.ParamValue
 
-/** Represents a fulfillment request coming from user tap. */
-internal data class TouchEventUpdateRequest(val paramValuesMap: Map<String, List<ParamValue>>) {
-
-    companion object {
-        /**
-         * merge two TouchEventUpdateRequest instances. Map entries in newRequest will take priority in
-         * case of conflict.
-         */
-        @JvmStatic
-        fun merge(
-            oldRequest: TouchEventUpdateRequest,
-            newRequest: TouchEventUpdateRequest,
-        ): TouchEventUpdateRequest {
-            val mergedParamValuesMap = oldRequest.paramValuesMap.toMutableMap()
-            mergedParamValuesMap.putAll(newRequest.paramValuesMap)
-            return TouchEventUpdateRequest(mergedParamValuesMap.toMap())
-        }
+/**
+ * Represents a fulfillment request coming from user tap.
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+data class TouchEventUpdateRequest(val paramValuesMap: Map<String, List<ParamValue>>) {
+    /**
+     * merge two TouchEventUpdateRequest instances. Map entries in newRequest will take priority in
+     * case of conflict.
+     */
+    fun mergeWith(newRequest: TouchEventUpdateRequest): TouchEventUpdateRequest {
+        val mergedParamValuesMap = this.paramValuesMap.toMutableMap()
+        mergedParamValuesMap.putAll(newRequest.paramValuesMap)
+        return TouchEventUpdateRequest(mergedParamValuesMap.toMap())
     }
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.java
deleted file mode 100644
index b9bf730..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.java
+++ /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.appactions.interaction.capabilities.core.task.impl;
-
-import com.google.auto.value.AutoOneOf;
-
-/** Contains either an AssistantUpdateRequest or a TouchEventUpdateRequest */
-@AutoOneOf(UpdateRequest.Kind.class)
-abstract class UpdateRequest {
-    static UpdateRequest of(AssistantUpdateRequest request) {
-        return AutoOneOf_UpdateRequest.assistant(request);
-    }
-
-    static UpdateRequest of(TouchEventUpdateRequest request) {
-        return AutoOneOf_UpdateRequest.touchEvent(request);
-    }
-
-    abstract Kind getKind();
-
-    abstract AssistantUpdateRequest assistant();
-
-    abstract TouchEventUpdateRequest touchEvent();
-
-    enum Kind {
-        ASSISTANT,
-        TOUCH_EVENT
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.kt b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.kt
new file mode 100644
index 0000000..60c3816
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.appactions.interaction.capabilities.core.task.impl
+
+import androidx.annotation.RestrictTo
+
+/**
+ * Contains either an AssistantUpdateRequest or a TouchEventUpdateRequest
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+class UpdateRequest
+private constructor(
+    val assistantRequest: AssistantUpdateRequest?,
+    val touchEventRequest: TouchEventUpdateRequest?,
+) {
+    constructor(assistantRequest: AssistantUpdateRequest) : this(assistantRequest, null)
+
+    constructor(touchEventRequest: TouchEventUpdateRequest) : this(null, touchEventRequest)
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
index 0737500..aeabac6 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/SingleTurnCapabilityTest.kt
@@ -20,6 +20,7 @@
 import androidx.appactions.interaction.capabilities.core.ActionCapability
 import androidx.appactions.interaction.capabilities.core.ActionExecutor
 import androidx.appactions.interaction.capabilities.core.ActionExecutorAsync
+import androidx.appactions.interaction.capabilities.core.ActionExecutorAsync.Companion.toActionExecutorAsync
 import androidx.appactions.interaction.capabilities.core.ExecutionResult
 import androidx.appactions.interaction.capabilities.core.HostProperties
 import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
@@ -29,6 +30,8 @@
 import androidx.appactions.interaction.capabilities.core.properties.EntityProperty
 import androidx.appactions.interaction.capabilities.core.properties.StringProperty
 import androidx.appactions.interaction.capabilities.core.testing.ArgumentUtils
+import androidx.appactions.interaction.capabilities.core.testing.buildCallbackInternalWithChannel
+import androidx.appactions.interaction.capabilities.core.testing.TestingUtils.CB_TIMEOUT
 import androidx.appactions.interaction.capabilities.core.testing.spec.Argument
 import androidx.appactions.interaction.capabilities.core.testing.spec.Output
 import androidx.appactions.interaction.capabilities.core.testing.spec.Property
@@ -37,19 +40,17 @@
 import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput.OutputValue
 import androidx.appactions.interaction.proto.ParamValue
 import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import org.mockito.Mockito.verify
-import org.mockito.kotlin.mock
 
 @RunWith(JUnit4::class)
 class SingleTurnCapabilityTest {
     val hostProperties = HostProperties.Builder().setMaxHostSizeDp(SizeF(300f, 500f)).build()
-    val mockCalback: CallbackInternal = mock()
 
-    @Ignore // b/271033076
     @Test
     fun oneShotCapability_successWithOutput() {
         val actionExecutor = object : ActionExecutor<Argument, Output> {
@@ -68,7 +69,7 @@
                 ).setOptionalStringField(
                     StringProperty.Builder().setProhibited(true).build(),
                 ).build(),
-                actionExecutor,
+                actionExecutor.toActionExecutorAsync(),
             )
         val expectedFulfillmentResponse: FulfillmentResponse =
             FulfillmentResponse.newBuilder().setExecutionOutput(
@@ -87,6 +88,7 @@
             ).build()
 
         val capabilitySession = capability.createSession(hostProperties)
+        val responseChannel = Channel<FulfillmentResponse>(1)
         capabilitySession.execute(
             ArgumentUtils.buildArgs(
                 mapOf(
@@ -95,10 +97,16 @@
                     ).build(),
                 ),
             ),
-            mockCalback,
+            buildCallbackInternalWithChannel(responseChannel, CB_TIMEOUT),
         )
 
-        verify(mockCalback).onSuccess(expectedFulfillmentResponse)
+        runBlocking {
+            withTimeout(CB_TIMEOUT) {
+                assertThat(
+                    responseChannel.receive(),
+                ).isEqualTo(expectedFulfillmentResponse)
+            }
+        }
     }
 
     @Test
@@ -114,7 +122,7 @@
                 Property.newBuilder().setRequiredEntityField(
                     EntityProperty.Builder().build(),
                 ).build(),
-                actionExecutor,
+                actionExecutor.toActionExecutorAsync(),
             )
         val session = capability.createSession(hostProperties)
         assertThat(session.uiHandle).isSameInstanceAs(actionExecutor)
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelperTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelperTest.kt
new file mode 100644
index 0000000..310078e
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/ListenableFutureHelperTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.impl.concurrent
+
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.channels.ReceiveChannel
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+import kotlin.coroutines.cancellation.CancellationException
+import kotlin.time.Duration.Companion.seconds
+
+@RunWith(JUnit4::class)
+class ListenableFutureHelperTest {
+    val TAG = "tag"
+
+    @Test
+    fun suspendToListenableFuture_smokeTest() {
+        val stringFuture = convertToListenableFuture<String>(TAG) { "hello" }
+        assertThat(stringFuture.get()).isEqualTo("hello")
+    }
+
+    @Test
+    fun suspendToListenableFuture_pollingTest() {
+        val stringChannel = Channel<String>(1)
+        val stringFuture =
+            convertToListenableFuture<String>(TAG) { getNextValueFromChannel(stringChannel) }
+        assertThat(stringFuture.isDone()).isFalse()
+
+        runBlocking { withTimeout(1.seconds) { stringChannel.send("hello") } }
+        assertThat(stringFuture.get()).isEqualTo("hello")
+    }
+
+    @Test
+    fun suspendToListenableFuture_cancellationTest() {
+        val stringChannel = Channel<String>(1)
+        val cancellationChannel = Channel<Boolean>(1)
+        val stringFuture =
+            convertToListenableFuture<String>(TAG) {
+                getNextValueFromChannel(stringChannel, cancellationChannel)
+            }
+        stringFuture.cancel(true)
+        assertThat(stringFuture.isCancelled()).isTrue()
+        runBlocking {
+            withTimeout(1.seconds) { assertThat(cancellationChannel.receive()).isTrue() }
+        }
+    }
+
+    private suspend fun <T> getNextValueFromChannel(
+        valueChannel: ReceiveChannel<T>,
+        cancellationChannel: SendChannel<Boolean>? = null,
+    ): T {
+        try {
+            return valueChannel.receive()
+        } catch (e: CancellationException) {
+            cancellationChannel?.send(true)
+            throw e
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
index 043ad48..62b6478 100644
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.kt
@@ -57,6 +57,8 @@
 import androidx.appactions.interaction.capabilities.core.values.ListItem
 import androidx.appactions.interaction.capabilities.core.values.SearchAction
 import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
+import androidx.appactions.interaction.proto.AppActionsContext.DialogParameter
 import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter
 import androidx.appactions.interaction.proto.CurrentValue
 import androidx.appactions.interaction.proto.DisambiguationData
@@ -527,18 +529,16 @@
         assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue()
         assertThat(session.state)
             .isEqualTo(
-                AppAction.newBuilder()
-                    .setName("actions.intent.TEST")
-                    .setIdentifier("id")
+                AppDialogState.newBuilder()
+                    .setFulfillmentIdentifier("id")
                     .addParams(
-                        IntentParameter.newBuilder()
+                        DialogParameter.newBuilder()
                             .setName("required")
-                            .setIsRequired(true)
                             .addCurrentValue(
                                 CurrentValue.newBuilder()
                                     .setValue(
                                         buildSearchActionParamValue(
-                                            "invalid",
+                                            "invalid"
                                         ),
                                     )
                                     .setStatus(
@@ -555,7 +555,7 @@
                                                     )
                                                     .setName(
                                                         "valid1",
-                                                    ),
+                                                    )
                                             )
                                             .addEntities(
                                                 Entity
@@ -569,11 +569,7 @@
                                             ),
                                     ),
                             ),
-                    )
-                    .setTaskInfo(
-                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true),
-                    )
-                    .build(),
+                    ).build()
             )
 
         // TURN 2.
@@ -592,13 +588,11 @@
         assertThat(turn2SuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue()
         assertThat(session.state)
             .isEqualTo(
-                AppAction.newBuilder()
-                    .setName("actions.intent.TEST")
-                    .setIdentifier("id")
+                AppDialogState.newBuilder()
+                    .setFulfillmentIdentifier("id")
                     .addParams(
-                        IntentParameter.newBuilder()
+                        DialogParameter.newBuilder()
                             .setName("required")
-                            .setIsRequired(true)
                             .addCurrentValue(
                                 CurrentValue.newBuilder()
                                     .setValue(
@@ -615,10 +609,7 @@
                                     ),
                             ),
                     )
-                    .setTaskInfo(
-                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true),
-                    )
-                    .build(),
+                    .build()
             )
     }
 
@@ -705,13 +696,11 @@
         assertThat(onFinishListItemCb.getFuture().isDone()).isFalse()
         assertThat(session.state)
             .isEqualTo(
-                AppAction.newBuilder()
-                    .setName("actions.intent.TEST")
-                    .setIdentifier("selectListItem")
+                AppDialogState.newBuilder()
+                    .setFulfillmentIdentifier("selectListItem")
                     .addParams(
-                        IntentParameter.newBuilder()
+                        DialogParameter.newBuilder()
                             .setName("listItem")
-                            .setIsRequired(true)
                             .addCurrentValue(
                                 CurrentValue.newBuilder()
                                     .setValue(
@@ -720,7 +709,7 @@
                                         ),
                                     )
                                     .setStatus(
-                                        CurrentValue.Status.DISAMBIG,
+                                        CurrentValue.Status.DISAMBIG
                                     )
                                     .setDisambiguationData(
                                         DisambiguationData
@@ -744,15 +733,11 @@
                             .build(),
                     )
                     .addParams(
-                        IntentParameter.newBuilder()
+                        DialogParameter.newBuilder()
                             .setName("string")
-                            .setIsRequired(true)
-                            .build(),
+                            .build()
                     )
-                    .setTaskInfo(
-                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true),
-                    )
-                    .build(),
+                    .build()
             )
 
         // second sync request, sending grounded ParamValue with identifier only
@@ -983,13 +968,14 @@
             return !paramValue.hasIdentifier()
         }
 
-        // TODO(b/269638788) migrate session state to AppDialogState message
-        @Suppress("DEPRECATION")
-        private fun getCurrentValues(argName: String, appAction: AppAction): List<CurrentValue> {
-            return appAction.getParamsList().stream()
-                .filter { intentParam -> intentParam.getName().equals(argName) }
+        private fun getCurrentValues(
+            argName: String,
+            appDialogState: AppDialogState
+        ): List<CurrentValue> {
+            return appDialogState.getParamsList().stream()
+                .filter { dialogParam -> dialogParam.getName().equals(argName) }
                 .findFirst()
-                .orElse(IntentParameter.getDefaultInstance())
+                .orElse(DialogParameter.getDefaultInstance())
                 .getCurrentValueList()
         }
 
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.java
deleted file mode 100644
index bf93a0d..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.java
+++ /dev/null
@@ -1,105 +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.appactions.interaction.capabilities.core.task.impl;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.appactions.interaction.capabilities.core.impl.converters.PropertyConverter;
-import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
-import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
-import androidx.appactions.interaction.proto.CurrentValue;
-import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue;
-import androidx.appactions.interaction.proto.ParamValue;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.ArrayList;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-
-@RunWith(JUnit4.class)
-public final class TaskCapabilityUtilsTest {
-
-    @Test
-    public void isSlotFillingComplete_allRequiredParamsFilled_returnsTrue() {
-        Map<String, List<ParamValue>> args = new HashMap<>();
-        args.put(
-                "required",
-                Collections.singletonList(
-                        ParamValue.newBuilder().setStringValue("Donald").build()));
-        List<IntentParameter> intentParameters = new ArrayList<>();
-        intentParameters.add(
-                PropertyConverter.getIntentParameter(
-                        "required", new StringProperty.Builder().setRequired(true).build()));
-
-        assertThat(TaskCapabilityUtils.isSlotFillingComplete(args, intentParameters)).isTrue();
-    }
-
-    @Test
-    public void isSlotFillingComplete_notAllRequiredParamsFilled_returnsFalse() {
-        List<IntentParameter> intentParameters = new ArrayList<>();
-        intentParameters.add(
-                PropertyConverter.getIntentParameter(
-                        "required", new StringProperty.Builder().setRequired(true).build()));
-
-        assertThat(
-                TaskCapabilityUtils.isSlotFillingComplete(Collections.emptyMap(), intentParameters))
-                .isFalse();
-    }
-
-    @Test
-    public void canSkipSlotProcessing_true() {
-        List<CurrentValue> currentValues =
-                Collections.singletonList(
-                        CurrentValue.newBuilder()
-                                .setValue(ParamValue.newBuilder().setBoolValue(true).build())
-                                .setStatus(CurrentValue.Status.ACCEPTED)
-                                .build());
-        List<FulfillmentValue> fulfillmentValues =
-                Collections.singletonList(
-                        FulfillmentValue.newBuilder()
-                                .setValue(ParamValue.newBuilder().setBoolValue(true).build())
-                                .build());
-        assertThat(TaskCapabilityUtils.canSkipSlotProcessing(currentValues, fulfillmentValues))
-                .isTrue();
-    }
-
-    @Test
-    public void canSkipSlotProcessing_false_sizeDifference() {
-        List<CurrentValue> currentValues =
-                Collections.singletonList(
-                        CurrentValue.newBuilder()
-                                .setValue(ParamValue.newBuilder().setStringValue("a").build())
-                                .setStatus(CurrentValue.Status.ACCEPTED)
-                                .build());
-        List<FulfillmentValue> fulfillmentValues = new ArrayList<>();
-        fulfillmentValues.add(
-                FulfillmentValue.newBuilder()
-                        .setValue(ParamValue.newBuilder().setStringValue("a").build())
-                        .build());
-        fulfillmentValues.add(
-                FulfillmentValue.newBuilder()
-                        .setValue(ParamValue.newBuilder().setStringValue("b").build())
-                        .build());
-        assertThat(TaskCapabilityUtils.canSkipSlotProcessing(currentValues, fulfillmentValues))
-                .isFalse();
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.kt
new file mode 100644
index 0000000..54f25db
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.appactions.interaction.capabilities.core.task.impl
+
+import androidx.appactions.interaction.capabilities.core.impl.converters.PropertyConverter
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty
+import androidx.appactions.interaction.proto.AppActionsContext
+import androidx.appactions.interaction.proto.CurrentValue
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.ParamValue
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TaskCapabilityUtilsTest {
+    @Test
+    fun isSlotFillingComplete_allRequiredParamsFilled_returnsTrue() {
+        val args: MutableMap<String, List<ParamValue>> = HashMap()
+        args["required"] = listOf(ParamValue.newBuilder().setStringValue("Donald").build())
+        val intentParameters: MutableList<AppActionsContext.IntentParameter> = ArrayList()
+        intentParameters.add(
+            PropertyConverter.getIntentParameter(
+                "required",
+                StringProperty.Builder().setRequired(true).build()
+            )
+        )
+        assertThat(TaskCapabilityUtils.isSlotFillingComplete(args, intentParameters)).isTrue()
+    }
+
+    @Test
+    fun isSlotFillingComplete_notAllRequiredParamsFilled_returnsFalse() {
+        val intentParameters: MutableList<AppActionsContext.IntentParameter> = ArrayList()
+        intentParameters.add(
+            PropertyConverter.getIntentParameter(
+                "required",
+                StringProperty.Builder().setRequired(true).build()
+            )
+        )
+        assertThat(TaskCapabilityUtils.isSlotFillingComplete(emptyMap(), intentParameters))
+            .isFalse()
+    }
+
+    @Test
+    fun canSkipSlotProcessing_true() {
+        val currentValues =
+            listOf(
+                CurrentValue.newBuilder()
+                    .setValue(ParamValue.newBuilder().setBoolValue(true).build())
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .build()
+            )
+        val fulfillmentValues =
+            listOf(
+                FulfillmentRequest.Fulfillment.FulfillmentValue.newBuilder()
+                    .setValue(ParamValue.newBuilder().setBoolValue(true).build())
+                    .build()
+            )
+        assertThat(TaskCapabilityUtils.canSkipSlotProcessing(currentValues, fulfillmentValues))
+            .isTrue()
+    }
+
+    @Test
+    fun canSkipSlotProcessing_false_sizeDifference() {
+        val currentValues =
+            listOf(
+                CurrentValue.newBuilder()
+                    .setValue(ParamValue.newBuilder().setStringValue("a").build())
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .build()
+            )
+        val fulfillmentValues: MutableList<FulfillmentRequest.Fulfillment.FulfillmentValue> =
+            ArrayList()
+        fulfillmentValues.add(
+            FulfillmentRequest.Fulfillment.FulfillmentValue.newBuilder()
+                .setValue(ParamValue.newBuilder().setStringValue("a").build())
+                .build()
+        )
+        fulfillmentValues.add(
+            FulfillmentRequest.Fulfillment.FulfillmentValue.newBuilder()
+                .setValue(ParamValue.newBuilder().setStringValue("b").build())
+                .build()
+        )
+        assertThat(TaskCapabilityUtils.canSkipSlotProcessing(currentValues, fulfillmentValues))
+            .isFalse()
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.java
deleted file mode 100644
index bf6d0d8..0000000
--- a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.java
+++ /dev/null
@@ -1,400 +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.appactions.interaction.capabilities.core.task.impl;
-
-import static androidx.appactions.interaction.capabilities.core.testing.ArgumentUtils.buildSearchActionParamValue;
-
-import static com.google.common.truth.Truth.assertThat;
-
-import androidx.annotation.NonNull;
-import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures;
-import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
-import androidx.appactions.interaction.capabilities.core.task.AppEntityResolver;
-import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult;
-import androidx.appactions.interaction.capabilities.core.task.InventoryResolver;
-import androidx.appactions.interaction.capabilities.core.task.ValidationResult;
-import androidx.appactions.interaction.capabilities.core.task.ValueListener;
-import androidx.appactions.interaction.capabilities.core.testing.spec.SettableFutureWrapper;
-import androidx.appactions.interaction.capabilities.core.values.SearchAction;
-import androidx.appactions.interaction.proto.CurrentValue;
-import androidx.appactions.interaction.proto.CurrentValue.Status;
-import androidx.appactions.interaction.proto.DisambiguationData;
-import androidx.appactions.interaction.proto.Entity;
-import androidx.appactions.interaction.proto.ParamValue;
-import androidx.appactions.interaction.protobuf.Struct;
-import androidx.appactions.interaction.protobuf.Value;
-
-import com.google.common.util.concurrent.ListenableFuture;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-import org.junit.runners.JUnit4;
-
-import java.util.Arrays;
-import java.util.Collections;
-import java.util.HashMap;
-import java.util.List;
-import java.util.Map;
-import java.util.function.Consumer;
-
-@RunWith(JUnit4.class)
-public final class TaskSlotProcessorTest {
-
-    private <T> GenericResolverInternal<T> createAssistantDisambigResolver(
-            ValidationResult validationResult,
-            Consumer<T> valueConsumer,
-            Consumer<List<String>> renderConsumer) {
-        return GenericResolverInternal.fromInventoryResolver(
-                new InventoryResolver<T>() {
-                    @NonNull
-                    @Override
-                    public ListenableFuture<ValidationResult> onReceivedAsync(T value) {
-                        valueConsumer.accept(value);
-                        return Futures.immediateFuture(validationResult);
-                    }
-
-                    @NonNull
-                    @Override
-                    public ListenableFuture<Void> renderChoices(@NonNull List<String> entityIDs) {
-                        renderConsumer.accept(entityIDs);
-                        return Futures.immediateVoidFuture();
-                    }
-                });
-    }
-
-    private <T> GenericResolverInternal<T> createValueResolver(
-            ValidationResult validationResult, Consumer<T> valueConsumer) {
-        return GenericResolverInternal.fromValueListener(
-                new ValueListener<T>() {
-                    @Override
-                    public ListenableFuture<ValidationResult> onReceivedAsync(T value) {
-                        valueConsumer.accept(value);
-                        return Futures.immediateFuture(validationResult);
-                    }
-                });
-    }
-
-    private <T> GenericResolverInternal<T> createValueResolver(ValidationResult validationResult) {
-        return createValueResolver(validationResult, (unused) -> {});
-    }
-
-    private <T> GenericResolverInternal<T> createValueListResolver(
-            ValidationResult validationResult, Consumer<List<T>> valueConsumer) {
-        return GenericResolverInternal.fromValueListListener(
-                new ValueListener<List<T>>() {
-                    @Override
-                    public ListenableFuture<ValidationResult> onReceivedAsync(List<T> value) {
-                        valueConsumer.accept(value);
-                        return Futures.immediateFuture(validationResult);
-                    }
-                });
-    }
-
-    private <T> GenericResolverInternal<T> createAppEntityResolver(
-            ValidationResult validationResult,
-            Consumer<T> valueConsumer,
-            EntitySearchResult<T> appSearchResult,
-            Consumer<SearchAction<T>> appSearchConsumer) {
-        return GenericResolverInternal.fromAppEntityResolver(
-                new AppEntityResolver<T>() {
-                    @NonNull
-                    @Override
-                    public ListenableFuture<ValidationResult> onReceivedAsync(T value) {
-                        valueConsumer.accept(value);
-                        return Futures.immediateFuture(validationResult);
-                    }
-
-                    @NonNull
-                    @Override
-                    public ListenableFuture<EntitySearchResult<T>> lookupAndRender(
-                            @NonNull SearchAction<T> searchAction) {
-                        appSearchConsumer.accept(searchAction);
-                        return Futures.immediateFuture(appSearchResult);
-                    }
-                });
-    }
-
-    @Test
-    public void processSlot_singleValue_accepted() throws Exception {
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "singularValue",
-                        (paramValue) -> false,
-                        createValueResolver(ValidationResult.newAccepted()),
-                        TypeConverters::toStringValue,
-                        null,
-                        null);
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("singularValue", binding);
-        List<ParamValue> args =
-                Collections.singletonList(
-                        ParamValue.newBuilder().setIdentifier("testValue").build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot(
-                                "singularValue",
-                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
-                                taskParamMap,
-                                Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isTrue();
-        assertThat(result.processedValues())
-                .containsExactly(
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(0))
-                                .setStatus(Status.ACCEPTED)
-                                .build());
-    }
-
-    @Test
-    public void processSlot_singleValue_rejected() throws Exception {
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "singularValue",
-                        (paramValue) -> false,
-                        createValueResolver(ValidationResult.newRejected()),
-                        TypeConverters::toStringValue,
-                        null,
-                        null);
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("singularValue", binding);
-        List<ParamValue> args =
-                Collections.singletonList(
-                        ParamValue.newBuilder().setIdentifier("testValue").build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot(
-                                "singularValue",
-                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
-                                taskParamMap,
-                                Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isFalse();
-        assertThat(result.processedValues())
-                .containsExactly(
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(0))
-                                .setStatus(Status.REJECTED)
-                                .build());
-    }
-
-    @Test
-    public void processSlot_repeatedValue_accepted() throws Exception {
-        SettableFutureWrapper<List<String>> lastReceivedArgs = new SettableFutureWrapper<>();
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "repeatedValue",
-                        (paramValue) -> false,
-                        createValueListResolver(
-                                ValidationResult.newAccepted(), lastReceivedArgs::set),
-                        TypeConverters::toStringValue,
-                        null,
-                        null);
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("repeatedValue", binding);
-        List<ParamValue> args =
-                Arrays.asList(
-                        ParamValue.newBuilder().setIdentifier("testValue1").build(),
-                        ParamValue.newBuilder().setIdentifier("testValue2").build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot(
-                                "repeatedValue",
-                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
-                                taskParamMap,
-                                Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isTrue();
-        assertThat(result.processedValues())
-                .containsExactly(
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(0))
-                                .setStatus(Status.ACCEPTED)
-                                .build(),
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(1))
-                                .setStatus(Status.ACCEPTED)
-                                .build());
-        assertThat(lastReceivedArgs.getFuture().get()).containsExactly("testValue1", "testValue2");
-    }
-
-    @Test
-    public void processSlot_repeatedValue_rejected() throws Exception {
-        SettableFutureWrapper<List<String>> lastReceivedArgs = new SettableFutureWrapper<>();
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "repeatedValue",
-                        (paramValue) -> false,
-                        createValueListResolver(
-                                ValidationResult.newRejected(), lastReceivedArgs::set),
-                        TypeConverters::toStringValue,
-                        null,
-                        null);
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("repeatedValue", binding);
-        List<ParamValue> args =
-                Arrays.asList(
-                        ParamValue.newBuilder().setIdentifier("testValue1").build(),
-                        ParamValue.newBuilder().setIdentifier("testValue2").build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot(
-                                "repeatedValue",
-                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
-                                taskParamMap,
-                                Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isFalse();
-        assertThat(result.processedValues())
-                .containsExactly(
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(0))
-                                .setStatus(Status.REJECTED)
-                                .build(),
-                        CurrentValue.newBuilder()
-                                .setValue(args.get(1))
-                                .setStatus(Status.REJECTED)
-                                .build());
-        assertThat(lastReceivedArgs.getFuture().get()).containsExactly("testValue1", "testValue2");
-    }
-
-    @Test
-    public void listValues_oneAccepted_oneAssistantDisambig_invokesRendererAndOnReceived()
-            throws Exception {
-        SettableFutureWrapper<String> onReceivedCb = new SettableFutureWrapper<>();
-        SettableFutureWrapper<List<String>> renderCb = new SettableFutureWrapper<>();
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "assistantDrivenSlot",
-                        (paramValue) -> !paramValue.hasIdentifier(),
-                        createAssistantDisambigResolver(
-                                ValidationResult.newAccepted(), onReceivedCb::set, renderCb::set),
-                        TypeConverters::toStringValue,
-                        null,
-                        null);
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("assistantDrivenSlot", binding);
-        CurrentValue previouslyAccepted =
-                CurrentValue.newBuilder()
-                        .setStatus(Status.ACCEPTED)
-                        .setValue(
-                                ParamValue.newBuilder()
-                                        .setIdentifier("id")
-                                        .setStructValue(
-                                                Struct.newBuilder()
-                                                        .putFields(
-                                                                "id",
-                                                                Value.newBuilder()
-                                                                        .setStringValue("1234")
-                                                                        .build())))
-                        .build();
-        List<CurrentValue> values =
-                Arrays.asList(
-                        previouslyAccepted,
-                        CurrentValue.newBuilder()
-                                .setStatus(Status.PENDING)
-                                .setDisambiguationData(
-                                        DisambiguationData.newBuilder()
-                                                .addEntities(
-                                                        Entity.newBuilder()
-                                                                .setIdentifier("entity-1"))
-                                                .addEntities(
-                                                        Entity.newBuilder()
-                                                                .setIdentifier("entity-2")))
-                                .build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot(
-                                "assistantDrivenSlot", values, taskParamMap, Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isFalse();
-        assertThat(onReceivedCb.getFuture().get()).isEqualTo("id");
-        assertThat(renderCb.getFuture().get()).containsExactly("entity-1", "entity-2");
-        assertThat(result.processedValues())
-                .containsExactly(
-                        previouslyAccepted,
-                        CurrentValue.newBuilder()
-                                .setStatus(Status.DISAMBIG)
-                                .setDisambiguationData(
-                                        DisambiguationData.newBuilder()
-                                                .addEntities(
-                                                        Entity.newBuilder()
-                                                                .setIdentifier("entity-1"))
-                                                .addEntities(
-                                                        Entity.newBuilder()
-                                                                .setIdentifier("entity-2")))
-                                .build());
-    }
-
-    @Test
-    public void singularValue_appDisambigRejected_onReceivedNotCalled() throws Exception {
-        SettableFutureWrapper<String> onReceivedCb = new SettableFutureWrapper<>();
-        SettableFutureWrapper<SearchAction<String>> appSearchCb = new SettableFutureWrapper<>();
-        EntitySearchResult<String> entitySearchResult = EntitySearchResult.empty();
-        GenericResolverInternal<String> resolver =
-                createAppEntityResolver(
-                        ValidationResult.newAccepted(), // should not be invoked.
-                        onReceivedCb::set,
-                        entitySearchResult, // app-grounding returns REJECTED in all cases
-                        appSearchCb::set);
-        TaskParamBinding<String> binding =
-                new TaskParamBinding<>(
-                        "appDrivenSlot",
-                        (paramValue) -> true, // always invoke app-grounding in all cases
-                        resolver,
-                        TypeConverters::toStringValue, // Not invoked
-                        (unused) -> Entity.getDefaultInstance(),
-                        (unused) ->
-                                SearchAction.<String>newBuilder()
-                                        .setQuery("A")
-                                        .setObject("nested")
-                                        .build());
-        Map<String, TaskParamBinding<?>> taskParamMap = new HashMap<>();
-        taskParamMap.put("appDrivenSlot", binding);
-        List<CurrentValue> values =
-                Collections.singletonList(
-                        CurrentValue.newBuilder()
-                                .setStatus(Status.PENDING)
-                                .setValue(buildSearchActionParamValue("A"))
-                                .build());
-
-        SlotProcessingResult result =
-                TaskSlotProcessor.processSlot("appDrivenSlot", values, taskParamMap, Runnable::run)
-                        .get();
-
-        assertThat(result.isSuccessful()).isFalse();
-        assertThat(onReceivedCb.getFuture().isDone()).isFalse();
-        assertThat(appSearchCb.getFuture().isDone()).isTrue();
-        assertThat(appSearchCb.getFuture().get())
-                .isEqualTo(
-                        SearchAction.<String>newBuilder()
-                                .setQuery("A")
-                                .setObject("nested")
-                                .build());
-        assertThat(result.processedValues())
-                .containsExactly(
-                        CurrentValue.newBuilder()
-                                .setStatus(Status.REJECTED)
-                                .setValue(buildSearchActionParamValue("A"))
-                                .build());
-    }
-}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt
new file mode 100644
index 0000000..38a6cde
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.kt
@@ -0,0 +1,380 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.task.impl
+
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters
+import androidx.appactions.interaction.capabilities.core.task.AppEntityResolver
+import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult
+import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult.Companion.empty
+import androidx.appactions.interaction.capabilities.core.task.InventoryResolver
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult.Companion.newAccepted
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult.Companion.newRejected
+import androidx.appactions.interaction.capabilities.core.task.ValueListener
+import androidx.appactions.interaction.capabilities.core.testing.ArgumentUtils
+import androidx.appactions.interaction.capabilities.core.testing.spec.SettableFutureWrapper
+import androidx.appactions.interaction.capabilities.core.values.SearchAction
+import androidx.appactions.interaction.proto.CurrentValue
+import androidx.appactions.interaction.proto.DisambiguationData
+import androidx.appactions.interaction.proto.Entity
+import androidx.appactions.interaction.proto.ParamValue
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.ListenableFuture
+import kotlinx.coroutines.runBlocking
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class TaskSlotProcessorTest {
+    private fun <T> createAssistantDisambigResolver(
+        validationResult: ValidationResult,
+        valueConsumer: (T) -> Unit,
+        renderConsumer: (List<String>) -> Unit,
+    ): GenericResolverInternal<T> {
+        return GenericResolverInternal.fromInventoryResolver(
+            object : InventoryResolver<T> {
+                override fun onReceivedAsync(value: T): ListenableFuture<ValidationResult> {
+                    valueConsumer.invoke(value)
+                    return Futures.immediateFuture(validationResult)
+                }
+
+                override fun renderChoices(entityIDs: List<String>): ListenableFuture<Void> {
+                    renderConsumer.invoke(entityIDs)
+                    return Futures.immediateVoidFuture()
+                }
+            }
+        )
+    }
+
+    private fun <T> createValueResolver(
+        validationResult: ValidationResult,
+        valueConsumer: (T) -> Unit,
+    ): GenericResolverInternal<T> {
+        return GenericResolverInternal.fromValueListener(
+            object : ValueListener<T> {
+                override fun onReceivedAsync(value: T): ListenableFuture<ValidationResult> {
+                    valueConsumer.invoke(value)
+                    return Futures.immediateFuture(validationResult)
+                }
+            }
+        )
+    }
+
+    private fun <T> createValueResolver(
+        validationResult: ValidationResult
+    ): GenericResolverInternal<T> {
+        return createValueResolver(validationResult) { _: T -> }
+    }
+
+    private fun <T> createValueListResolver(
+        validationResult: ValidationResult,
+        valueConsumer: (List<T>) -> Unit,
+    ): GenericResolverInternal<T> {
+        return GenericResolverInternal.fromValueListListener(
+            object : ValueListener<List<T>> {
+                override fun onReceivedAsync(value: List<T>): ListenableFuture<ValidationResult> {
+                    valueConsumer.invoke(value)
+                    return Futures.immediateFuture(validationResult)
+                }
+            }
+        )
+    }
+
+    private fun <T> createAppEntityResolver(
+        validationResult: ValidationResult,
+        valueConsumer: (T) -> Unit,
+        appSearchResult: EntitySearchResult<T>,
+        appSearchConsumer: (SearchAction<T>) -> Unit,
+    ): GenericResolverInternal<T> {
+        return GenericResolverInternal.fromAppEntityResolver(
+            object : AppEntityResolver<T> {
+                override fun onReceivedAsync(value: T): ListenableFuture<ValidationResult> {
+                    valueConsumer.invoke(value)
+                    return Futures.immediateFuture(validationResult)
+                }
+
+                override fun lookupAndRender(
+                    searchAction: SearchAction<T>
+                ): ListenableFuture<EntitySearchResult<T>> {
+                    appSearchConsumer.invoke(searchAction)
+                    return Futures.immediateFuture(appSearchResult)
+                }
+            }
+        )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun processSlot_singleValue_accepted(): Unit = runBlocking {
+        val binding =
+            TaskParamBinding(
+                "singularValue",
+                { false },
+                createValueResolver(newAccepted()),
+                { TypeConverters.toStringValue(it) },
+                null,
+                null
+            )
+        val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+        taskParamMap["singularValue"] = binding
+        val args = listOf(ParamValue.newBuilder().setIdentifier("testValue").build())
+        val (isSuccessful, processedValues) =
+            TaskSlotProcessor.processSlot(
+                "singularValue",
+                TaskCapabilityUtils.paramValuesToCurrentValue(args, CurrentValue.Status.PENDING),
+                taskParamMap
+            )
+        assertThat(isSuccessful).isTrue()
+        assertThat(processedValues)
+            .containsExactly(
+                CurrentValue.newBuilder()
+                    .setValue(args[0])
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .build()
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun processSlot_singleValue_rejected(): Unit = runBlocking {
+        val binding =
+            TaskParamBinding(
+                "singularValue",
+                { false },
+                createValueResolver(newRejected()),
+                { TypeConverters.toStringValue(it) },
+                null,
+                null
+            )
+        val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+        taskParamMap["singularValue"] = binding
+        val args = listOf(ParamValue.newBuilder().setIdentifier("testValue").build())
+        val (isSuccessful, processedValues) =
+            TaskSlotProcessor.processSlot(
+                "singularValue",
+                TaskCapabilityUtils.paramValuesToCurrentValue(args, CurrentValue.Status.PENDING),
+                taskParamMap
+            )
+        assertThat(isSuccessful).isFalse()
+        assertThat(processedValues)
+            .containsExactly(
+                CurrentValue.newBuilder()
+                    .setValue(args[0])
+                    .setStatus(CurrentValue.Status.REJECTED)
+                    .build()
+            )
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun processSlot_repeatedValue_accepted(): Unit = runBlocking {
+        val lastReceivedArgs = SettableFutureWrapper<List<String?>>()
+        val binding =
+            TaskParamBinding(
+                "repeatedValue",
+                { false },
+                createValueListResolver(newAccepted()) { lastReceivedArgs.set(it) },
+                { TypeConverters.toStringValue(it) },
+                null,
+                null
+            )
+        val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+        taskParamMap["repeatedValue"] = binding
+        val args =
+            listOf(
+                ParamValue.newBuilder().setIdentifier("testValue1").build(),
+                ParamValue.newBuilder().setIdentifier("testValue2").build()
+            )
+        val (isSuccessful, processedValues) =
+            TaskSlotProcessor.processSlot(
+                "repeatedValue",
+                TaskCapabilityUtils.paramValuesToCurrentValue(args, CurrentValue.Status.PENDING),
+                taskParamMap
+            )
+        assertThat(isSuccessful).isTrue()
+        assertThat(processedValues)
+            .containsExactly(
+                CurrentValue.newBuilder()
+                    .setValue(args[0])
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .build(),
+                CurrentValue.newBuilder()
+                    .setValue(args[1])
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .build()
+            )
+        assertThat(lastReceivedArgs.future.get()).containsExactly("testValue1", "testValue2")
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun processSlot_repeatedValue_rejected(): Unit = runBlocking {
+        val lastReceivedArgs = SettableFutureWrapper<List<String>>()
+        val binding =
+            TaskParamBinding(
+                "repeatedValue",
+                { false },
+                createValueListResolver(newRejected()) { lastReceivedArgs.set(it) },
+                { TypeConverters.toStringValue(it) },
+                null,
+                null
+            )
+        val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+        taskParamMap["repeatedValue"] = binding
+        val args =
+            listOf(
+                ParamValue.newBuilder().setIdentifier("testValue1").build(),
+                ParamValue.newBuilder().setIdentifier("testValue2").build()
+            )
+        val (isSuccessful, processedValues) =
+            TaskSlotProcessor.processSlot(
+                "repeatedValue",
+                TaskCapabilityUtils.paramValuesToCurrentValue(args, CurrentValue.Status.PENDING),
+                taskParamMap
+            )
+        assertThat(isSuccessful).isFalse()
+        assertThat(processedValues)
+            .containsExactly(
+                CurrentValue.newBuilder()
+                    .setValue(args[0])
+                    .setStatus(CurrentValue.Status.REJECTED)
+                    .build(),
+                CurrentValue.newBuilder()
+                    .setValue(args[1])
+                    .setStatus(CurrentValue.Status.REJECTED)
+                    .build()
+            )
+        assertThat(lastReceivedArgs.future.get()).containsExactly("testValue1", "testValue2")
+    }
+
+    @Test
+    @Throws(Exception::class)
+    fun listValues_oneAccepted_oneAssistantDisambig_invokesRendererAndOnReceived(): Unit =
+        runBlocking {
+            val onReceivedCb = SettableFutureWrapper<String>()
+            val renderCb = SettableFutureWrapper<List<String?>>()
+            val binding =
+                TaskParamBinding(
+                    "assistantDrivenSlot",
+                    { !it.hasIdentifier() },
+                    createAssistantDisambigResolver(newAccepted(), { onReceivedCb.set(it) }) {
+                        renderCb.set(it)
+                    },
+                    { TypeConverters.toStringValue(it) },
+                    null,
+                    null
+                )
+            val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+            taskParamMap["assistantDrivenSlot"] = binding
+            val previouslyAccepted =
+                CurrentValue.newBuilder()
+                    .setStatus(CurrentValue.Status.ACCEPTED)
+                    .setValue(
+                        ParamValue.newBuilder()
+                            .setIdentifier("id")
+                            .setStructValue(
+                                Struct.newBuilder()
+                                    .putFields(
+                                        "id",
+                                        Value.newBuilder().setStringValue("1234").build()
+                                    )
+                            )
+                    )
+                    .build()
+            val values =
+                listOf(
+                    previouslyAccepted,
+                    CurrentValue.newBuilder()
+                        .setStatus(CurrentValue.Status.PENDING)
+                        .setDisambiguationData(
+                            DisambiguationData.newBuilder()
+                                .addEntities(Entity.newBuilder().setIdentifier("entity-1"))
+                                .addEntities(Entity.newBuilder().setIdentifier("entity-2"))
+                        )
+                        .build()
+                )
+            val (isSuccessful, processedValues) =
+                TaskSlotProcessor.processSlot("assistantDrivenSlot", values, taskParamMap)
+            assertThat(isSuccessful).isFalse()
+            assertThat(onReceivedCb.future.get()).isEqualTo("id")
+            assertThat(renderCb.future.get()).containsExactly("entity-1", "entity-2")
+            assertThat(processedValues)
+                .containsExactly(
+                    previouslyAccepted,
+                    CurrentValue.newBuilder()
+                        .setStatus(CurrentValue.Status.DISAMBIG)
+                        .setDisambiguationData(
+                            DisambiguationData.newBuilder()
+                                .addEntities(Entity.newBuilder().setIdentifier("entity-1"))
+                                .addEntities(Entity.newBuilder().setIdentifier("entity-2"))
+                        )
+                        .build()
+                )
+        }
+
+    @Test
+    @Throws(Exception::class)
+    fun singularValue_appDisambigRejected_onReceivedNotCalled(): Unit = runBlocking {
+        val onReceivedCb = SettableFutureWrapper<String>()
+        val appSearchCb = SettableFutureWrapper<SearchAction<String>>()
+        val entitySearchResult = empty<String>()
+        val resolver =
+            createAppEntityResolver(
+                newAccepted(),
+                { result: String -> onReceivedCb.set(result) },
+                entitySearchResult
+            ) { result: SearchAction<String> ->
+                appSearchCb.set(result)
+            }
+        val binding =
+            TaskParamBinding(
+                "appDrivenSlot",
+                { true }, // always invoke app-grounding in all cases
+                resolver,
+                { TypeConverters.toStringValue(it) }, // Not invoked
+                { Entity.getDefaultInstance() }
+            ) {
+                SearchAction.newBuilder<String>().setQuery("A").setObject("nested").build()
+            }
+        val taskParamMap: MutableMap<String, TaskParamBinding<*>> = HashMap()
+        taskParamMap["appDrivenSlot"] = binding
+        val values =
+            listOf(
+                CurrentValue.newBuilder()
+                    .setStatus(CurrentValue.Status.PENDING)
+                    .setValue(ArgumentUtils.buildSearchActionParamValue("A"))
+                    .build()
+            )
+        val (isSuccessful, processedValues) =
+            TaskSlotProcessor.processSlot("appDrivenSlot", values, taskParamMap)
+        assertThat(isSuccessful).isFalse()
+        assertThat(onReceivedCb.future.isDone).isFalse()
+        assertThat(appSearchCb.future.isDone).isTrue()
+        assertThat(appSearchCb.future.get())
+            .isEqualTo(SearchAction.newBuilder<String>().setQuery("A").setObject("nested").build())
+        assertThat(processedValues)
+            .containsExactly(
+                CurrentValue.newBuilder()
+                    .setStatus(CurrentValue.Status.REJECTED)
+                    .setValue(ArgumentUtils.buildSearchActionParamValue("A"))
+                    .build()
+            )
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.kt b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.kt
new file mode 100644
index 0000000..49e2769
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.capabilities.core.testing
+
+import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
+import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal
+import androidx.appactions.interaction.proto.FulfillmentResponse
+import kotlinx.coroutines.channels.SendChannel
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+
+/**
+ * Returns a CallbackInternal instance which will forward the FulfillmentResponse it receives
+ * to a SendChannel and closes the channel afterwards.
+ */
+fun buildCallbackInternalWithChannel(
+    responseChannel: SendChannel<FulfillmentResponse>,
+    sendTimeoutMs: Long,
+): CallbackInternal = object : CallbackInternal {
+    override fun onSuccess(
+        fulfillmentResponse: FulfillmentResponse,
+    ) {
+        runBlocking {
+            withTimeout(sendTimeoutMs) {
+                responseChannel.send(fulfillmentResponse)
+            }
+        }
+        responseChannel.close()
+    }
+    override fun onError(errorStatus: ErrorStatusInternal) {
+        responseChannel.close()
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-fitness/api/current.txt b/appactions/interaction/interaction-capabilities-fitness/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-fitness/api/public_plus_experimental_current.txt b/appactions/interaction/interaction-capabilities-fitness/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-fitness/api/res-current.txt b/appactions/interaction/interaction-capabilities-fitness/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/api/res-current.txt
diff --git a/appactions/interaction/interaction-capabilities-fitness/api/restricted_current.txt b/appactions/interaction/interaction-capabilities-fitness/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/appactions/interaction/interaction-capabilities-fitness/build.gradle b/appactions/interaction/interaction-capabilities-fitness/build.gradle
new file mode 100644
index 0000000..94d8298
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * Copyright (C) 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    // Add dependencies here
+}
+
+android {
+    namespace "androidx.appactions.interaction.capabilities.fitness"
+}
+
+androidx {
+    name = "androidx.appactions.interaction:interaction-capabilities-fitness"
+    type = LibraryType.PUBLISHED_LIBRARY
+    inceptionYear = "2023"
+    description = "Capability library for health and fitness apps integrating with virtual assistant."
+}
diff --git a/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/androidx-appactions-interaction-interaction-capabilities-fitness-documentation.md b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/androidx-appactions-interaction-interaction-capabilities-fitness-documentation.md
new file mode 100644
index 0000000..da534a3
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-fitness/src/main/java/androidx/appactions/interaction/androidx-appactions-interaction-interaction-capabilities-fitness-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+androidx.appactions.interaction interaction-capablities-fitness
+
+# Package androidx.appactions.interaction.capabilities.fitness
+
+Insert package level documentation here
diff --git a/appactions/interaction/interaction-capabilities-productivity/build.gradle b/appactions/interaction/interaction-capabilities-productivity/build.gradle
index fff3438..21b8526 100644
--- a/appactions/interaction/interaction-capabilities-productivity/build.gradle
+++ b/appactions/interaction/interaction-capabilities-productivity/build.gradle
@@ -24,11 +24,9 @@
 
 dependencies {
     api(libs.kotlinStdlib)
-    implementation(libs.protobufLite)
     implementation("org.jetbrains.kotlin:kotlin-reflect:1.8.10")
     implementation("androidx.annotation:annotation:1.1.0")
     implementation(project(":appactions:interaction:interaction-capabilities-core"))
-    implementation(project(":appactions:interaction:interaction-proto"))
 }
 
 android {
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTImer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
similarity index 98%
rename from appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTImer.kt
rename to appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
index a9a7f52..b196e5f 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTImer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/PauseTimer.kt
@@ -28,8 +28,8 @@
 import androidx.appactions.interaction.capabilities.core.values.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.values.Timer
 import androidx.appactions.interaction.proto.ParamValue
-import com.google.protobuf.Struct
-import com.google.protobuf.Value
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
 /** PauseTimer.kt in interaction-capabilities-productivity */
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
index 23eccd2..d62d99a 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResetTimer.kt
@@ -28,8 +28,8 @@
 import androidx.appactions.interaction.capabilities.core.values.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.values.Timer
 import androidx.appactions.interaction.proto.ParamValue
-import com.google.protobuf.Struct
-import com.google.protobuf.Value
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
 /** ResetTimer.kt in interaction-capabilities-productivity */
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
index 18a4a76..367ecce 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/ResumeTimer.kt
@@ -28,8 +28,8 @@
 import androidx.appactions.interaction.capabilities.core.values.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.values.Timer
 import androidx.appactions.interaction.proto.ParamValue
-import com.google.protobuf.Struct
-import com.google.protobuf.Value
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
 /** ResumeTimer.kt in interaction-capabilities-productivity */
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
index 36fbeb4..74bec9e 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StartTimer.kt
@@ -30,8 +30,8 @@
 import androidx.appactions.interaction.capabilities.core.values.GenericErrorStatus
 import androidx.appactions.interaction.capabilities.core.values.SuccessStatus
 import androidx.appactions.interaction.proto.ParamValue
-import com.google.protobuf.Struct
-import com.google.protobuf.Value
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
 import java.time.Duration
 import java.util.Optional
 
diff --git a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
index c934ef1..24e9407 100644
--- a/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
+++ b/appactions/interaction/interaction-capabilities-productivity/src/main/java/androidx/appactions/interaction/capabilities/productivity/StopTimer.kt
@@ -28,8 +28,8 @@
 import androidx.appactions.interaction.capabilities.core.values.SuccessStatus
 import androidx.appactions.interaction.capabilities.core.values.Timer
 import androidx.appactions.interaction.proto.ParamValue
-import com.google.protobuf.Struct
-import com.google.protobuf.Value
+import androidx.appactions.interaction.protobuf.Struct
+import androidx.appactions.interaction.protobuf.Value
 import java.util.Optional
 
 /** StopTimer.kt in interaction-capabilities-productivity */
diff --git a/appactions/interaction/interaction-service-proto/build.gradle b/appactions/interaction/interaction-service-proto/build.gradle
index 5fcc43c..38a00154 100644
--- a/appactions/interaction/interaction-service-proto/build.gradle
+++ b/appactions/interaction/interaction-service-proto/build.gradle
@@ -24,8 +24,7 @@
 }
 
 dependencies {
-    // TODO(b/268709908): Bump this to version 1.52.0 and make available from libs.grpcProtobufLite
-    implementation("io.grpc:grpc-protobuf-lite:1.45.1") {
+    implementation(libs.grpcProtobufLite) {
         // Ensure we only bundle grpc-protobuf-lite. Any of its dependencies should be added
         // as `compileOnly` dependencies below.
         exclude group: 'com.google.protobuf'
@@ -44,8 +43,8 @@
     // any library that bundles interaction-service-proto.
     compileOnly(libs.protobufLite)
     compileOnly(libs.grpcStub)
+    compileOnly(libs.jsr250)
     compileOnly("androidx.annotation:annotation:1.1.0")
-    compileOnly("javax.annotation:javax.annotation-api:1.3.2")
 }
 
 protobuf {
@@ -55,7 +54,7 @@
     // Configure the codegen plugins for GRPC.
     plugins {
         grpc {
-            artifact = 'io.grpc:protoc-gen-grpc-java:1.52.0'
+            artifact = libs.grpcProtobufCompiler.get()
         }
     }
 
diff --git a/appactions/interaction/interaction-service/build.gradle b/appactions/interaction/interaction-service/build.gradle
index acac500..c6d43133 100644
--- a/appactions/interaction/interaction-service/build.gradle
+++ b/appactions/interaction/interaction-service/build.gradle
@@ -38,25 +38,31 @@
     bundleInside(project(":appactions:interaction:interaction-service-proto"))
 
     implementation(project(":appactions:interaction:interaction-capabilities-core"))
+    implementation("androidx.annotation:annotation:1.1.0")
+    implementation("androidx.concurrent:concurrent-futures:1.1.0")
+    implementation("androidx.wear.tiles:tiles:1.1.0")
     implementation(libs.grpcAndroid)
     implementation(libs.grpcBinder)
     implementation(libs.grpcStub)
     implementation(libs.kotlinStdlib)
-    implementation("androidx.annotation:annotation:1.1.0")
-    implementation("androidx.concurrent:concurrent-futures:1.1.0")
-    implementation("androidx.wear.tiles:tiles:1.1.0")
-    implementation("javax.annotation:javax.annotation-api:1.3.2")
+    implementation(libs.jsr250)
 
+    testImplementation(project(":appactions:interaction:interaction-capabilities-core"))
     testImplementation(project(":appactions:interaction:interaction-service-proto"))
-    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.grpcTesting)
     testImplementation(libs.junit)
+    testImplementation(libs.kotlinCoroutinesTest)
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.kotlinTest)
+    testImplementation(libs.mockitoKotlin)
+    testImplementation(libs.protobufLite)
     testImplementation(libs.robolectric)
     testImplementation(libs.testExtJunit)
     testImplementation(libs.testExtTruth)
     testImplementation(libs.testCore)
     testImplementation(libs.testRunner)
     testImplementation(libs.truth)
-    testImplementation(libs.protobufLite)
+    testImplementation("androidx.concurrent:concurrent-futures-ktx:1.1.0")
 }
 
 android {
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.java b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.java
deleted file mode 100644
index fd3f218..0000000
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.java
+++ /dev/null
@@ -1,183 +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.appactions.interaction.service;
-
-import android.app.Service;
-import android.content.Intent;
-import android.os.IBinder;
-import android.util.Log;
-
-import androidx.annotation.CallSuper;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.appactions.interaction.capabilities.core.ActionCapability;
-import androidx.appactions.interaction.service.proto.AppInteractionServiceGrpc;
-
-import java.io.IOException;
-import java.util.ArrayList;
-import java.util.List;
-
-import io.grpc.Server;
-import io.grpc.binder.AndroidComponentAddress;
-import io.grpc.binder.BinderServerBuilder;
-import io.grpc.binder.IBinderReceiver;
-import io.grpc.binder.SecurityPolicies;
-import io.grpc.binder.SecurityPolicy;
-import io.grpc.binder.ServerSecurityPolicy;
-
-/**
- * Base service class for the AppInteractionService SDK. This sets up the GRPC on-device server for
- * communication with Assistant.
- */
-// TODO(b/267772921): Rewrite public facing class to Kotlin
-public abstract class AppInteractionService extends Service {
-
-    private static final String TAG = "AppInteractionService";
-    private ServerLifecycle mBinderSupplier;
-
-    public AppInteractionService() {}
-
-    /**
-     * Called by the system once after the Assistant binds to the service.
-     *
-     * @return the list of capabilities that this service supports.
-     */
-    @NonNull
-    protected abstract List<ActionCapability> registerCapabilities();
-
-    /**
-     * Sets a custom {@link SecurityPolicy} for the gRPC service. This gives control over which
-     * clients are allowed to bind to your service.
-     *
-     * <p>Overriding this method is <b>not</b> the preferred method for security enforcement. We
-     * recommend developers override {@link #getAllowedApps()} for security needs. Implementing your
-     * own security policy requires significant care, and an understanding of the details and
-     * pitfalls of Android security. If you choose to do so, we <b>strongly</b> recommend you get
-     * such a change reviewed by Android security experts.
-     */
-    @NonNull
-    protected SecurityPolicy getSecurityPolicy() {
-        return SecurityPolicies.anyOf(
-                getSecurityPolicyFromAllowedList(getAllowedApps()).toArray(new SecurityPolicy[0]));
-    }
-
-    /**
-     * Returns a list of the apps {@link AppVerificationInfo} that are allowed to interact with the
-     * app's bound service. This gives control over which clients are allowed to communicate with
-     * the service.
-     *
-     * <p>This is the default method for enforcing security and must be overridden. Developers
-     * should return an empty list should they choose to define their own security by way of
-     * overriding {@link #getSecurityPolicyFromAllowedList}.
-     */
-    @NonNull
-    protected abstract List<AppVerificationInfo> getAllowedApps();
-
-    /**
-     * Sets a custom {@link SecurityPolicy} for the gRPC service given the client's allowed pairs of
-     * package names with corresponding Sha256 signatures. This gives control over which clients are
-     * allowed to bind to your service.
-     *
-     * <p>A SecurityPolicy is returned per supported Assistant. Such as "Google Assistant", "Bixby",
-     * etc.
-     */
-    @NonNull
-    private List<SecurityPolicy> getSecurityPolicyFromAllowedList(
-            List<AppVerificationInfo> verificationInfoList) {
-
-        List<SecurityPolicy> policies = new ArrayList<>();
-        if (verificationInfoList == null || verificationInfoList.isEmpty()) {
-            policies.add(SecurityPolicies.internalOnly());
-            return policies;
-        }
-        for (AppVerificationInfo verificationInfo : verificationInfoList) {
-            policies.add(
-                    SecurityPolicies.oneOfSignatureSha256Hash(
-                            this.getPackageManager(),
-                            verificationInfo.getPackageName(),
-                            verificationInfo.getSignatures()));
-        }
-        return policies;
-    }
-
-    @Override
-    @CallSuper
-    public void onCreate() {
-        super.onCreate();
-        IBinderReceiver binderReceiver = new IBinderReceiver();
-        ServerSecurityPolicy serverSecurityPolicy =
-                ServerSecurityPolicy.newBuilder()
-                        .servicePolicy(AppInteractionServiceGrpc.SERVICE_NAME, getSecurityPolicy())
-                        .build();
-        Server server =
-                BinderServerBuilder.forAddress(
-                                AndroidComponentAddress.forContext(this), binderReceiver)
-                        .securityPolicy(serverSecurityPolicy)
-                        .intercept(new RemoteViewsOverMetadataInterceptor())
-                        .addService(AppInteractionServiceFactory.create(this))
-                        .build();
-
-        mBinderSupplier = new ServerLifecycle(server, binderReceiver);
-    }
-
-    @Override
-    @NonNull
-    public IBinder onBind(@Nullable Intent intent) {
-        return mBinderSupplier.get();
-    }
-
-    @Override
-    @CallSuper
-    public void onDestroy() {
-        if (mBinderSupplier != null) {
-            mBinderSupplier.shutdown();
-        }
-        super.onDestroy();
-    }
-
-    static final class ServerLifecycle {
-        private final Server mServer;
-        private final IBinderReceiver mReceiver;
-        private boolean mStarted;
-
-        ServerLifecycle(Server server, IBinderReceiver receiver) {
-            this.mServer = server;
-            this.mReceiver = receiver;
-        }
-
-        public IBinder get() {
-            synchronized (this) {
-                if (!mStarted) {
-                    try {
-                        mStarted = true;
-                        mServer.start();
-                    } catch (IOException ioe) {
-                        Log.e(TAG, "Unable to start server " + mServer, ioe);
-                    }
-                }
-                return mReceiver.get();
-            }
-        }
-
-        public void shutdown() {
-            if (mStarted) {
-                mServer.shutdownNow();
-                mStarted = false;
-            }
-        }
-    }
-}
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.kt b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.kt
new file mode 100644
index 0000000..3018079
--- /dev/null
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionService.kt
@@ -0,0 +1,163 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.service
+
+import android.app.Service
+import android.content.Intent
+import android.os.IBinder
+import android.util.Log
+import androidx.annotation.CallSuper
+import androidx.appactions.interaction.capabilities.core.ActionCapability
+import androidx.appactions.interaction.service.proto.AppInteractionServiceGrpc
+import io.grpc.Server
+import io.grpc.binder.AndroidComponentAddress
+import io.grpc.binder.BinderServerBuilder
+import io.grpc.binder.IBinderReceiver
+import io.grpc.binder.SecurityPolicies
+import io.grpc.binder.SecurityPolicy
+import io.grpc.binder.ServerSecurityPolicy
+import java.io.IOException
+
+/**
+ * Base service class for the AppInteractionService SDK. This sets up the GRPC on-device server for
+ * communication with Assistant.
+ */
+abstract class AppInteractionService : Service() {
+    private var binderSupplier: ServerLifecycle? = null
+
+    /**
+     * Called by the system once after the Assistant binds to the service.
+     *
+     * @return the list of capabilities that this service supports.
+     */
+    abstract val registeredCapabilities: List<ActionCapability>
+
+    /**
+     * A list of [AppVerificationInfo] which define who is allowed to interact with the app's bound
+     * service. This gives control over which clients are allowed to communicate with the service.
+     *
+     * This is the default method for enforcing security and must be overridden. Developers should
+     * return an empty list should they choose to define their own security by way of overriding
+     * [.getSecurityPolicyFromAllowedList].
+     */
+    protected abstract val allowedApps: List<AppVerificationInfo>
+
+    /**
+     * Sets a custom [SecurityPolicy] for the gRPC service. This gives control over which clients
+     * are allowed to bind to your service.
+     *
+     * Overriding this property is **not** the preferred method for security enforcement. We
+     * recommend developers override [allowedApps] for security needs. Implementing your own
+     * security policy requires significant care, and an understanding of the details and pitfalls
+     * of Android security. If you choose to do so, we **strongly** recommend you get such a change
+     * reviewed by Android security experts.
+     */
+    protected open val securityPolicy: SecurityPolicy
+        get() =
+            SecurityPolicies.anyOf(*getSecurityPolicyFromAllowedList(allowedApps).toTypedArray())
+
+    /**
+     * Sets a custom [SecurityPolicy] for the gRPC service given the client's allowed pairs of
+     * package names with corresponding Sha256 signatures. This gives control over which clients are
+     * allowed to bind to your service.
+     *
+     * A SecurityPolicy is returned per supported Assistant. Such as "Google Assistant", "Bixby",
+     * etc.
+     */
+    private fun getSecurityPolicyFromAllowedList(
+        verificationInfoList: List<AppVerificationInfo>
+    ): List<SecurityPolicy> {
+        val policies: MutableList<SecurityPolicy> = ArrayList()
+        if (verificationInfoList.isEmpty()) {
+            policies.add(SecurityPolicies.internalOnly())
+            return policies
+        }
+        for (verificationInfo in verificationInfoList) {
+            policies.add(
+                SecurityPolicies.oneOfSignatureSha256Hash(
+                    this.packageManager,
+                    verificationInfo.packageName,
+                    verificationInfo.signatures
+                )
+            )
+        }
+        return policies
+    }
+
+    @CallSuper
+    override fun onCreate() {
+        super.onCreate()
+        val binderReceiver = IBinderReceiver()
+        val serverSecurityPolicy =
+            ServerSecurityPolicy.newBuilder()
+                .servicePolicy(AppInteractionServiceGrpc.SERVICE_NAME, securityPolicy)
+                .build()
+        val server =
+            BinderServerBuilder.forAddress(AndroidComponentAddress.forContext(this), binderReceiver)
+                .securityPolicy(serverSecurityPolicy)
+                .intercept(RemoteViewsOverMetadataInterceptor())
+                .addService(AppInteractionServiceFactory.create(this))
+                .build()
+        binderSupplier = ServerLifecycle(server, binderReceiver)
+    }
+
+    override fun onBind(intent: Intent?): IBinder {
+        return binderSupplier!!.get()!!
+    }
+
+    @CallSuper
+    override fun onDestroy() {
+        if (binderSupplier != null) {
+            binderSupplier!!.shutdown()
+        }
+        super.onDestroy()
+    }
+
+    internal class ServerLifecycle(
+        private val server: Server,
+        private val receiver: IBinderReceiver
+    ) {
+        private var isServerStarted = false
+
+        fun get(): IBinder? {
+            synchronized(this) {
+                if (!isServerStarted) {
+                    try {
+                        isServerStarted = true
+                        server.start()
+                    } catch (ioe: IOException) {
+                        Log.e(TAG, "Unable to start server $server", ioe)
+                    }
+                }
+                return receiver.get()
+            }
+        }
+
+        fun shutdown() {
+            synchronized(this) {
+                if (isServerStarted) {
+                    server.shutdownNow()
+                    isServerStarted = false
+                }
+            }
+        }
+    }
+
+    companion object {
+        private const val TAG = "AppInteractionService"
+    }
+}
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.java b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.java
deleted file mode 100644
index 59a15f6..0000000
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.java
+++ /dev/null
@@ -1,44 +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.appactions.interaction.service;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-
-import io.grpc.BindableService;
-
-/**
- * Factory for returning a BindableService from AppInteractionService.
- *
- * <p>This class is public because it's used in the testing library.
- *
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public final class AppInteractionServiceFactory {
-
-    /**
-     * Creates a new instance of the gRPC service from the developer's AppInteractionService
-     * (android service)
-     */
-    @NonNull
-    public static BindableService create(@NonNull AppInteractionService appInteractionService) {
-        return new AppInteractionServiceGrpcImpl(appInteractionService);
-    }
-
-    private AppInteractionServiceFactory() {}
-}
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.kt b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.kt
new file mode 100644
index 0000000..5db6482
--- /dev/null
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceFactory.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.service
+
+import androidx.annotation.RestrictTo
+import io.grpc.BindableService
+
+/**
+ * Factory for returning a [BindableService] from an []AppInteractionService].
+ *
+ * @hide
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+object AppInteractionServiceFactory {
+    /**
+     * Creates a new instance of the gRPC service from the developer's []AppInteractionService]
+     * (android service).
+     */
+    fun create(appInteractionService: AppInteractionService): BindableService {
+        return AppInteractionServiceGrpcImpl(appInteractionService)
+    }
+}
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImpl.java b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImpl.java
index d2cd565..bbc565b 100644
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImpl.java
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImpl.java
@@ -16,7 +16,51 @@
 
 package androidx.appactions.interaction.service;
 
+import android.util.Log;
+import android.util.SizeF;
+import android.widget.RemoteViews;
+import android.widget.RemoteViewsService.RemoteViewsFactory;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ActionCapability;
+import androidx.appactions.interaction.capabilities.core.HostProperties;
+import androidx.appactions.interaction.capabilities.core.LibInfo;
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession;
+import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper;
+import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal;
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.FutureCallback;
+import androidx.appactions.interaction.capabilities.core.impl.concurrent.Futures;
+import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger;
+import androidx.appactions.interaction.capabilities.core.impl.utils.LoggerInternal;
+import androidx.appactions.interaction.proto.AppActionsContext;
+import androidx.appactions.interaction.proto.FulfillmentRequest;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+import androidx.appactions.interaction.proto.GroundingRequest;
+import androidx.appactions.interaction.proto.GroundingResponse;
+import androidx.appactions.interaction.proto.Version;
 import androidx.appactions.interaction.service.proto.AppInteractionServiceGrpc.AppInteractionServiceImplBase;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.CollectionRequest;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.CollectionResponse;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.RemoteViewsInfo;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.Request;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.Response;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.StartSessionRequest;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.StartSessionResponse;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.Status.Code;
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.UiUpdate;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+import io.grpc.Status;
+import io.grpc.StatusException;
+import io.grpc.StatusRuntimeException;
+import io.grpc.stub.StreamObserver;
 
 /**
  * Implementation of {@link AppInteractionServiceImplBase} generated from the GRPC proto file. This
@@ -24,11 +68,504 @@
  */
 final class AppInteractionServiceGrpcImpl extends AppInteractionServiceImplBase {
 
-    // TODO(b/268069897): Migrate ActionsServiceGrpcImpl.
-    @SuppressWarnings("unused")
-    private final AppInteractionService mAppInteractionService;
+    private static final String TAG = "ActionsServiceGrpcImpl";
+
+    static final String ERROR_NO_SESSION = "Session not available";
+    static final String ERROR_NO_FULFILLMENT_REQUEST = "Fulfillment request missing";
+    static final String ERROR_NO_ACTION_CAPABILITY = "ActionCapability was not found";
+    static final String ERROR_SESSION_ENDED = "Session already ended";
+    private static final String ERROR_NO_COLLECTION_SUPPORT =
+            "Session doesn't support collection view";
+    private static final String ERROR_NO_UI = "No UI set";
+    private static final String ERROR_MULTIPLE_UI_TYPES =
+            "Multiple UI types used in current session";
+
+    final AppInteractionService mAppInteractionService;
+    List<ActionCapability> mRegisteredCapabilities = new ArrayList<>();
+
+    static {
+        LoggerInternal.setLogger(
+                new CapabilityLogger() {
+                    @Override
+                    public void log(
+                            @NonNull LogLevel logLevel,
+                            @NonNull String logTag,
+                            @NonNull String message) {
+                        switch (logLevel) {
+                            case ERROR:
+                                Log.e(logTag, message);
+                                break;
+                            case WARN:
+                                Log.w(logTag, message);
+                                break;
+                            case INFO:
+                                Log.i(logTag, message);
+                                break;
+                        }
+                    }
+
+                    @Override
+                    public void log(
+                            @NonNull LogLevel logLevel,
+                            @NonNull String logTag,
+                            @NonNull String message,
+                            @NonNull Throwable throwable) {
+                        switch (logLevel) {
+                            case ERROR:
+                                Log.e(logTag, message, throwable);
+                                break;
+                            case WARN:
+                                Log.w(logTag, message, throwable);
+                                break;
+                            case INFO:
+                                Log.i(logTag, message, throwable);
+                                break;
+                        }
+                    }
+                });
+    }
 
     AppInteractionServiceGrpcImpl(AppInteractionService mAppInteractionService) {
         this.mAppInteractionService = mAppInteractionService;
     }
-}
+
+    @Override
+    public StreamObserver<StartSessionRequest> startUpSession(
+            StreamObserver<StartSessionResponse> responseObserver) {
+        return new StartSessionRequestObserver(responseObserver);
+    }
+
+    private final class StartSessionRequestObserver implements StreamObserver<StartSessionRequest> {
+        private final StreamObserver<StartSessionResponse> mStartSessionResponseObserver;
+        // Every AppInteractionService connection is defined by this streaming RPC connection.
+        // There should only be one session tied to each gRPC impl instance.
+        private String mCurrentSessionId = null;
+
+        StartSessionRequestObserver(StreamObserver<StartSessionResponse> responseObserver) {
+            this.mStartSessionResponseObserver = responseObserver;
+        }
+
+        @Override
+        public void onNext(StartSessionRequest request) {
+            if (mCurrentSessionId != null) {
+                return;
+            }
+            mCurrentSessionId = request.getSessionIdentifier();
+            if (mRegisteredCapabilities.isEmpty()) {
+                mRegisteredCapabilities = mAppInteractionService.getRegisteredCapabilities();
+            }
+            Optional<ActionCapability> targetCapability =
+                    mRegisteredCapabilities.stream()
+                            .filter(cap -> request.getIdentifier().equals(cap.getId()))
+                            .findFirst();
+            if (!targetCapability.isPresent()) {
+                mStartSessionResponseObserver.onError(
+                        new StatusRuntimeException(
+                                Status.FAILED_PRECONDITION.withDescription(
+                                        ERROR_NO_ACTION_CAPABILITY)));
+                return;
+            }
+            HostProperties hostProperties =
+                    new HostProperties.Builder()
+                            .setMaxHostSizeDp(new SizeF(
+                                    request.getHostProperties().getHostViewHeightDp(),
+                                    request.getHostProperties().getHostViewWidthDp()))
+                            .build();
+            ActionCapabilitySession session = targetCapability.get().createSession(hostProperties);
+            SessionManager.INSTANCE.putSession(mCurrentSessionId, session);
+            mStartSessionResponseObserver.onNext(StartSessionResponse.getDefaultInstance());
+        }
+
+        @Override
+        public void onError(Throwable t) {
+            synchronized (mStartSessionResponseObserver) {
+                mStartSessionResponseObserver.onError(t);
+            }
+            if (mCurrentSessionId != null) {
+                destroySession(mCurrentSessionId);
+            }
+            mCurrentSessionId = null;
+        }
+
+        @Override
+        public void onCompleted() {
+            synchronized (mStartSessionResponseObserver) {
+                mStartSessionResponseObserver.onCompleted();
+            }
+            if (mCurrentSessionId != null) {
+                destroySession(mCurrentSessionId);
+            }
+            mCurrentSessionId = null;
+        }
+    }
+
+    @Override
+    public void sendRequestFulfillment(Request request, StreamObserver<Response> responseObserver) {
+        if (request.getFulfillmentRequest().getFulfillmentsList().isEmpty()) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(
+                                    ERROR_NO_FULFILLMENT_REQUEST)));
+            return;
+        }
+        FulfillmentRequest.Fulfillment selectedFulfillment =
+                request.getFulfillmentRequest().getFulfillments(0);
+        Optional<ActionCapability> capability =
+                mRegisteredCapabilities.stream()
+                        .filter(cap -> selectedFulfillment.getIdentifier().equals(cap.getId()))
+                        .findFirst();
+        if (!capability.isPresent()) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(
+                                    ERROR_NO_ACTION_CAPABILITY)));
+            return;
+        }
+        String sessionId = selectedFulfillment.getSessionInfo().getSessionIdentifier();
+        ActionCapabilitySession currentSession =
+                SessionManager.INSTANCE.getSession(sessionId);
+        if (currentSession == null) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_NO_SESSION)));
+            return;
+        }
+        if (currentSession.getStatus() == ActionCapabilitySession.Status.COMPLETED
+                || currentSession.getStatus() == ActionCapabilitySession.Status.DESTROYED) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_SESSION_ENDED)));
+            return;
+        }
+        Futures.addCallback(
+                executeFulfillmentRequest(currentSession, selectedFulfillment),
+                new FutureCallback<FulfillmentResponse>() {
+                    @Override
+                    public void onSuccess(FulfillmentResponse fulfillmentResponse) {
+                        Response.Builder responseBuilder =
+                                convertFulfillmentResponse(fulfillmentResponse, capability.get())
+                                        .toBuilder();
+                        UiCache uiCache = UiSessions.INSTANCE.getUiCacheOrNull(
+                                sessionId);
+                        if (uiCache != null && uiCache.hasUnreadUiResponse()) {
+                            responseBuilder.setUiUpdate(UiUpdate.getDefaultInstance());
+                            if (!uiCache.getCachedChangedViewIds().isEmpty()) {
+                                responseBuilder.setCollectionUpdate(
+                                        AppInteractionServiceProto.CollectionUpdate.newBuilder()
+                                                .addAllViewIds(uiCache.getCachedChangedViewIds()));
+                            }
+                            uiCache.resetUnreadUiResponse();
+                        }
+                        respondAndComplete(responseBuilder.build(), responseObserver);
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        Throwable outputThrowable;
+                        if (t instanceof CapabilityExecutionException) {
+                            outputThrowable =
+                                    convertToGrpcException((CapabilityExecutionException) t);
+                        } else if (t instanceof StatusRuntimeException
+                                || t instanceof StatusException) {
+                            outputThrowable = t;
+                        } else {
+                            outputThrowable =
+                                    new StatusRuntimeException(
+                                            Status.INTERNAL.withDescription(
+                                                    t.getMessage()).withCause(t));
+                        }
+                        responseObserver.onError(outputThrowable);
+                        // Assistant will terminate the connection, which will reach
+                        // startUpSession.onError(t) / onCompleted()
+                    }
+                },
+                Runnable::run);
+    }
+
+    @Override
+    public void requestUi(
+            AppInteractionServiceProto.UiRequest req,
+            StreamObserver<AppInteractionServiceProto.UiResponse> responseObserver) {
+        String sessionId = req.getSessionIdentifier();
+        ActionCapabilitySession currentSession = SessionManager.INSTANCE
+                .getSession(sessionId);
+        if (currentSession == null) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_NO_SESSION)));
+            return;
+        }
+        if (currentSession.getStatus() == ActionCapabilitySession.Status.COMPLETED) {
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_SESSION_ENDED)));
+            return;
+        }
+        UiCache uiCache = UiSessions.INSTANCE.getUiCacheOrNull(sessionId);
+        if (uiCache == null) {
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(Status.INTERNAL.withDescription(ERROR_NO_UI)));
+            return;
+        }
+
+        TileLayoutInternal tileLayout = uiCache.getCachedTileLayout();
+        SizeF remoteViewsSize = uiCache.getCachedRemoteViewsSize();
+        RemoteViews remoteViews = uiCache.getCachedRemoteViews();
+        if (tileLayout != null && remoteViews != null) {
+            // TODO(b/272379825): Decide if this is really an invalid state.
+            // both types of UI are present, this is a misused of API. We will treat it as error.
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.INTERNAL.withDescription(ERROR_MULTIPLE_UI_TYPES)));
+            return;
+        }
+        if (tileLayout != null) {
+            respondAndComplete(
+                    AppInteractionServiceProto.UiResponse.newBuilder()
+                            .setTileLayout(tileLayout.toProto())
+                            .build(),
+                    responseObserver);
+            return;
+        }
+        if (remoteViews != null && remoteViewsSize != null) {
+            RemoteViewsOverMetadataInterceptor.setRemoteViews(remoteViews);
+            respondAndComplete(
+                    AppInteractionServiceProto.UiResponse.newBuilder()
+                            .setRemoteViewsInfo(
+                                    RemoteViewsInfo.newBuilder()
+                                            .setWidthDp(remoteViewsSize.getWidth())
+                                            .setHeightDp(remoteViewsSize.getHeight()))
+                            .build(),
+                    responseObserver);
+            return;
+        }
+        destroySession(req.getSessionIdentifier());
+        responseObserver.onError(
+                new StatusRuntimeException(Status.INTERNAL.withDescription(ERROR_NO_UI)));
+    }
+
+    @Override
+    public void requestCollection(
+            CollectionRequest req, StreamObserver<CollectionResponse> responseObserver) {
+        String sessionId = req.getSessionIdentifier();
+        ActionCapabilitySession currentSession = SessionManager.INSTANCE
+                .getSession(sessionId);
+        if (currentSession == null) {
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_NO_SESSION)));
+            return;
+        }
+        if (currentSession.getStatus() == ActionCapabilitySession.Status.COMPLETED) {
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.FAILED_PRECONDITION.withDescription(ERROR_SESSION_ENDED)));
+            return;
+        }
+        UiCache uiCache = UiSessions.INSTANCE.getUiCacheOrNull(sessionId);
+        if (uiCache == null) {
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(Status.INTERNAL.withDescription(ERROR_NO_UI)));
+            return;
+        }
+        RemoteViewsFactory factory = uiCache.onGetViewFactoryInternal(req.getViewId());
+        if (factory == null) {
+            destroySession(req.getSessionIdentifier());
+            responseObserver.onError(
+                    new StatusRuntimeException(
+                            Status.UNIMPLEMENTED.withDescription(ERROR_NO_COLLECTION_SUPPORT)));
+            return;
+        }
+        switch (req.getRequestDataCase()) {
+            case ON_DESTROY: {
+                requestCollectionOnDestroy(factory, responseObserver);
+                break;
+            }
+            case GET_COUNT: {
+                requestCollectionGetCount(factory, responseObserver);
+                break;
+            }
+            case GET_VIEW_AT: {
+                requestCollectionGetViewAt(factory, responseObserver,
+                        req.getGetViewAt().getPosition());
+                break;
+            }
+
+            case GET_LOADING_VIEW: {
+                requestCollectionGetLoadingView(factory, responseObserver);
+                break;
+            }
+            case GET_VIEW_TYPE_COUNT: {
+                requestCollectionGetViewTypeCount(factory, responseObserver);
+                break;
+            }
+            case GET_ITEM_ID: {
+                requestCollectionGetItemId(factory, responseObserver,
+                        req.getGetItemId().getPosition());
+                break;
+            }
+            case HAS_STABLE_IDS: {
+                requestCollectionHasStableIds(factory, responseObserver);
+                break;
+            }
+            default: {
+                // ignore it
+                Log.d(TAG, "received CollectionRequest with unknown RequestData case.");
+                responseObserver.onCompleted();
+                break;
+            }
+        }
+    }
+
+    @Override
+    public void requestGrounding(
+            GroundingRequest request, StreamObserver<GroundingResponse> responseObserver) {
+        // TODO(b/268265068): Implement grounding API
+    }
+
+    private void requestCollectionOnDestroy(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer) {
+        factory.onDestroy();
+        respondAndComplete(CollectionResponse.getDefaultInstance(), observer);
+    }
+
+    private void requestCollectionGetCount(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer) {
+        respondAndComplete(
+                CollectionResponse.newBuilder()
+                        .setGetCount(CollectionResponse.GetCount.newBuilder()
+                                .setCount(factory.getCount()))
+                        .build(),
+                observer);
+    }
+
+    private void requestCollectionGetViewAt(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer, int position) {
+        RemoteViews view = factory.getViewAt(position);
+        if (view != null) {
+            RemoteViewsOverMetadataInterceptor.setRemoteViews(view);
+        }
+        respondAndComplete(CollectionResponse.getDefaultInstance(), observer);
+    }
+
+    private void requestCollectionGetLoadingView(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer) {
+        RemoteViews loadingView = factory.getLoadingView();
+        if (loadingView != null) {
+            RemoteViewsOverMetadataInterceptor.setRemoteViews(loadingView);
+        }
+        respondAndComplete(CollectionResponse.getDefaultInstance(), observer);
+    }
+
+    private void requestCollectionGetViewTypeCount(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer) {
+        respondAndComplete(
+                CollectionResponse.newBuilder()
+                        .setGetViewTypeCount(
+                                CollectionResponse.GetViewTypeCount.newBuilder()
+                                        .setViewTypeCount(factory.getViewTypeCount()))
+                        .build(),
+                observer);
+    }
+
+    private void requestCollectionGetItemId(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer, int position) {
+        respondAndComplete(
+                CollectionResponse.newBuilder()
+                        .setGetItemId(
+                                CollectionResponse.GetItemId.newBuilder()
+                                        .setItemId(factory.getItemId(position)))
+                        .build(),
+                observer);
+    }
+
+    private void requestCollectionHasStableIds(
+            RemoteViewsFactory factory, StreamObserver<CollectionResponse> observer) {
+        respondAndComplete(
+                CollectionResponse.newBuilder()
+                        .setHasStableIds(
+                                CollectionResponse.HasStableIds.newBuilder()
+                                        .setHasStableIds(factory.hasStableIds()))
+                        .build(),
+                observer);
+    }
+
+    @NonNull
+    private Version convertToAppActionsContextVersion(@NonNull LibInfo.Version libInfoVersion) {
+        Version.Builder builder = Version.newBuilder()
+                .setMajor(libInfoVersion.getMajor())
+                .setMinor(libInfoVersion.getMinor())
+                .setPatch(libInfoVersion.getPatch());
+        if (libInfoVersion.getPreReleaseId() != null) {
+            builder.setPrereleaseId(libInfoVersion.getPreReleaseId());
+        }
+        return builder.build();
+    }
+
+    void destroySession(@NonNull String sessionId) {
+        ActionCapabilitySession session = SessionManager.INSTANCE.getSession(sessionId);
+        if (session != null) {
+            session.destroy();
+        }
+        SessionManager.INSTANCE.removeSession(sessionId);
+    }
+
+    @NonNull
+    StatusRuntimeException convertToGrpcException(CapabilityExecutionException e) {
+        if (e.getErrorStatus() == ErrorStatusInternal.TIMEOUT) {
+            return new StatusRuntimeException(
+                    Status.DEADLINE_EXCEEDED.withDescription(e.getMessage()).withCause(e));
+        }
+        return new StatusRuntimeException(
+                Status.INTERNAL.withDescription(e.getMessage()).withCause(e));
+    }
+
+    @NonNull
+    Response convertFulfillmentResponse(
+            @NonNull FulfillmentResponse fulfillmentResponse,
+            @NonNull ActionCapability capability) {
+        AppActionsContext.AppAction appAction = capability.getAppAction();
+        boolean isDialogSession = appAction.getTaskInfo().getSupportsPartialFulfillment();
+        Version version = convertToAppActionsContextVersion(
+                new LibInfo(mAppInteractionService.getApplicationContext()).getVersion());
+        Response.Builder responseBuilder =
+                // TODO(b/269638788): Add DialogState to the Response proto.
+                Response.newBuilder()
+                        .setFulfillmentResponse(fulfillmentResponse)
+                        .setAppActionsContext(
+                                AppActionsContext.newBuilder()
+                                        .addActions(appAction)
+                                        .setVersion(version)
+                                        .build());
+        if (!isDialogSession) {
+            responseBuilder.setEndingStatus(
+                    AppInteractionServiceProto.Status.newBuilder()
+                            .setStatusCode(Code.COMPLETE)
+                            .build());
+        }
+        return responseBuilder.build();
+    }
+
+    @NonNull
+    ListenableFuture<FulfillmentResponse> executeFulfillmentRequest(
+            @NonNull ActionCapabilitySession session,
+            @NonNull FulfillmentRequest.Fulfillment fulfillmentRequest) {
+        return CallbackToFutureAdapter.getFuture(
+                completer -> {
+                    session.execute(
+                            ArgumentsWrapper.create(fulfillmentRequest),
+                            new ActionCapabilityCallback(completer));
+                    return "executing action capability";
+                });
+    }
+
+    static <T> void respondAndComplete(T response, StreamObserver<T> responseObserver) {
+        responseObserver.onNext(response);
+        responseObserver.onCompleted();
+    }
+}
\ No newline at end of file
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/SessionManager.kt b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/SessionManager.kt
index f68fc64..db4d7fe 100644
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/SessionManager.kt
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/SessionManager.kt
@@ -18,6 +18,7 @@
 
 import androidx.annotation.GuardedBy
 import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
+import java.util.IdentityHashMap
 import javax.annotation.concurrent.ThreadSafe
 
 /** Global object for managing capability sessions. */
@@ -29,8 +30,20 @@
     @GuardedBy("lock")
     private val sessions = mutableMapOf<String, ActionCapabilitySession>()
 
+    /**
+     * stores a map of uiHandle reference to sessionId, in order to get/set UiCache entries with
+     * sessionId key.
+     *
+     * if sessionId is the key, we can release the lock as soon as the ActionExecutor finishes.
+     */
+    @GuardedBy("lock")
+    private val uiHandleToSessionId = IdentityHashMap<Any, String>()
+
     fun putSession(sessionId: String, session: ActionCapabilitySession) {
-        synchronized(lock) { sessions[sessionId] = session }
+        synchronized(lock) {
+            sessions[sessionId] = session
+            uiHandleToSessionId[session.uiHandle] = sessionId
+        }
     }
 
     fun getSession(sessionId: String): ActionCapabilitySession? {
@@ -39,7 +52,17 @@
         }
     }
 
+    fun getLatestSessionIdFromUiHandle(uiHandle: Any): String? {
+        synchronized(lock) {
+            return uiHandleToSessionId[uiHandle]
+        }
+    }
+
     fun removeSession(sessionId: String) {
-        synchronized(lock) { sessions.remove(sessionId) }
+        synchronized(lock) {
+            val session = sessions[sessionId]
+            session?.let { uiHandleToSessionId.remove(it.uiHandle) }
+            sessions.remove(sessionId)
+        }
     }
 }
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/TileLayoutInternal.kt b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/TileLayoutInternal.kt
index 2604f05..982ba94 100644
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/TileLayoutInternal.kt
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/TileLayoutInternal.kt
@@ -17,10 +17,10 @@
 package androidx.appactions.interaction.service
 
 import androidx.annotation.RestrictTo
+import androidx.appactions.interaction.protobuf.ByteString
 import androidx.appactions.interaction.service.proto.AppInteractionServiceProto
 import androidx.wear.tiles.LayoutElementBuilders
 import androidx.wear.tiles.ResourceBuilders
-import androidx.appactions.interaction.protobuf.ByteString
 
 /**
  * Holder for TileLayout response.
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiCache.java b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiCache.java
index d512a50..5683769 100644
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiCache.java
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiCache.java
@@ -46,25 +46,24 @@
     private final Object mLock = new Object();
     @GuardedBy("mLock")
     private final Map<Integer, RemoteViewsFactory> mCachedRemoteViewsFactories = new HashMap<>();
-    private final Object mUiHandle;
 
     @GuardedBy("mLock")
-    @Nullable private RemoteViews mCachedRemoteViews;
+    @Nullable
+    private RemoteViews mCachedRemoteViews;
     @GuardedBy("mLock")
-    @Nullable private SizeF mCachedRemoteViewsSize;
+    @Nullable
+    private SizeF mCachedRemoteViewsSize;
     @GuardedBy("mLock")
-    @Nullable private TileLayoutInternal mCachedTileLayout;
+    @Nullable
+    private TileLayoutInternal mCachedTileLayout;
     @GuardedBy("mLock")
-    @Nullable private Set<Integer> mCachedChangedViewIds = new HashSet<>();
+    @Nullable
+    private Set<Integer> mCachedChangedViewIds = new HashSet<>();
     // Needs to be reset after latest UiResponse has been rendered. That way can know there
     // is new UI that has been sent by app that must now be rendered on this turn.
     @GuardedBy("mLock")
     private boolean mUnreadUiResponse;
 
-    UiCache(Object uiHandle) {
-        this.mUiHandle = uiHandle;
-    }
-
     /**
      * Caches a UiResponse for this particular {@link BaseSession}.
      */
@@ -80,26 +79,21 @@
         }
     }
 
-    @NonNull
-    Object getUiHandle() {
-        return mUiHandle;
-    }
-
-    @NonNull
+    @Nullable
     RemoteViewsFactory onGetViewFactoryInternal(int viewId) {
         synchronized (mLock) {
             return mCachedRemoteViewsFactories.get(viewId);
         }
     }
 
-    @NonNull
+    @Nullable
     RemoteViews getCachedRemoteViews() {
         synchronized (mLock) {
             return mCachedRemoteViews;
         }
     }
 
-    @NonNull
+    @Nullable
     SizeF getCachedRemoteViewsSize() {
         synchronized (mLock) {
             return mCachedRemoteViewsSize;
@@ -113,7 +107,7 @@
         }
     }
 
-    @NonNull
+    @Nullable
     TileLayoutInternal getCachedTileLayout() {
         synchronized (mLock) {
             return mCachedTileLayout;
@@ -129,6 +123,10 @@
     void resetUnreadUiResponse() {
         synchronized (mLock) {
             mUnreadUiResponse = false;
+            mCachedRemoteViews = null;
+            mCachedRemoteViewsSize = null;
+            mCachedTileLayout = null;
+            mCachedChangedViewIds = new HashSet<>();
         }
     }
 
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiSessions.kt b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiSessions.kt
index 5908f8a..f6f514e 100644
--- a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiSessions.kt
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/UiSessions.kt
@@ -33,37 +33,44 @@
 @ThreadSafe
 internal object UiSessions {
     private val lock = Any()
-    @GuardedBy("lock") private val uiCacheList = mutableListOf<UiCache>()
+    @GuardedBy("lock")
+    private val sessionIdToUiCache = mutableMapOf<String, UiCache>()
 
-    fun removeUiCache(uiHandle: Any): Boolean {
+    fun removeUiCache(sessionId: String): Boolean {
         synchronized(lock) {
-            return uiCacheList.remove(getUiCacheOrNull(uiHandle))
+            return sessionIdToUiCache.remove(sessionId) != null
         }
     }
 
-    fun getOrCreateUiCache(uiHandle: Any): UiCache {
+    fun getOrCreateUiCache(sessionId: String): UiCache {
         synchronized(lock) {
-            return uiCacheList.find { it.uiHandle === uiHandle } ?: createUiCache(uiHandle)
+            return sessionIdToUiCache[sessionId] ?: createUiCache(sessionId)
         }
     }
 
-    fun getUiCacheOrNull(uiHandle: Any): UiCache? {
+    fun getUiCacheOrNull(sessionId: String): UiCache? {
         synchronized(lock) {
-            return uiCacheList.find { it.uiHandle === uiHandle }
+            return sessionIdToUiCache[sessionId]
         }
     }
 
-    private fun createUiCache(uiHandle: Any): UiCache {
-        val uiSession = UiCache(uiHandle)
-        uiCacheList.add(uiSession)
-        return uiSession
+    private fun createUiCache(sessionId: String): UiCache {
+        val uiCache = UiCache()
+        synchronized(lock) {
+            sessionIdToUiCache[sessionId] = uiCache
+        }
+        return uiCache
     }
 }
 
 /** Return a UI associated with this [BaseSession]. */
 fun BaseSession<*, *>.updateUi(uiResponse: UiResponse) =
-    UiSessions.getOrCreateUiCache(this).updateUiInternal(uiResponse)
+    UiSessions.getOrCreateUiCache(
+        SessionManager.getLatestSessionIdFromUiHandle(this)!!
+    ).updateUiInternal(uiResponse)
 
 /** Return a UI associated with this [ActionExecutor]. */
 fun ActionExecutor<*, *>.updateUi(uiResponse: UiResponse) =
-    UiSessions.getOrCreateUiCache(this).updateUiInternal(uiResponse)
+    UiSessions.getOrCreateUiCache(
+        SessionManager.getLatestSessionIdFromUiHandle(this)!!
+    ).updateUiInternal(uiResponse)
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImplTest.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImplTest.kt
new file mode 100644
index 0000000..8cfa1da
--- /dev/null
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppInteractionServiceGrpcImplTest.kt
@@ -0,0 +1,447 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.appactions.interaction.service
+
+import android.content.Context
+import androidx.appactions.interaction.capabilities.core.ActionCapability
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
+import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction
+import androidx.appactions.interaction.proto.AppActionsContext.AppDialogState
+import androidx.appactions.interaction.proto.FulfillmentRequest
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.SessionInfo
+import androidx.appactions.interaction.proto.FulfillmentResponse
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput.OutputValue
+import androidx.appactions.interaction.service.AppInteractionServiceGrpcImpl.ERROR_NO_ACTION_CAPABILITY
+import androidx.appactions.interaction.service.AppInteractionServiceGrpcImpl.ERROR_NO_FULFILLMENT_REQUEST
+import androidx.appactions.interaction.service.AppInteractionServiceGrpcImpl.ERROR_NO_SESSION
+import androidx.appactions.interaction.service.AppInteractionServiceGrpcImpl.ERROR_SESSION_ENDED
+import androidx.appactions.interaction.service.proto.AppInteractionServiceGrpc
+import androidx.appactions.interaction.service.proto.AppInteractionServiceGrpc.AppInteractionServiceStub
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.Request
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.StartSessionRequest
+import androidx.appactions.interaction.service.proto.AppInteractionServiceProto.StartSessionResponse
+import androidx.concurrent.futures.await
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.argumentCaptor
+import com.nhaarman.mockitokotlin2.doAnswer
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.never
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
+import io.grpc.BindableService
+import io.grpc.ManagedChannel
+import io.grpc.Server
+import io.grpc.ServerInterceptor
+import io.grpc.ServerInterceptors
+import io.grpc.Status
+import io.grpc.StatusRuntimeException
+import io.grpc.binder.SecurityPolicies
+import io.grpc.binder.SecurityPolicy
+import io.grpc.inprocess.InProcessChannelBuilder
+import io.grpc.inprocess.InProcessServerBuilder
+import io.grpc.stub.StreamObserver
+import io.grpc.testing.GrpcCleanupRule
+import java.io.IOException
+import java.util.Collections
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+// TODO(b/271929200) Implement tests for the 2 UI related RPCs
+@RunWith(AndroidJUnit4::class)
+@kotlinx.coroutines.ExperimentalCoroutinesApi
+class AppInteractionServiceGrpcImplTest {
+
+    @get:Rule val grpcCleanup = GrpcCleanupRule()
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+    private val remoteViewsInterceptor: ServerInterceptor = RemoteViewsOverMetadataInterceptor()
+    private val testServerName = InProcessServerBuilder.generateName()
+    private val testBiiName = "actions.intent.SAMPLE_BII_NAME"
+    private val sessionId = "session-123"
+    private val capabilityId = "capability-123"
+    private val defaultStartSessionRequest =
+        StartSessionRequest.newBuilder()
+            .setIntentName(testBiiName)
+            .setIdentifier(capabilityId)
+            .setSessionIdentifier(sessionId)
+            .build()
+    private val testFulfillmentRequest =
+        FulfillmentRequest.newBuilder()
+            .addFulfillments(
+                Fulfillment.newBuilder()
+                    .setName(testBiiName)
+                    .setIdentifier(capabilityId)
+                    .setSessionInfo(SessionInfo.newBuilder().setSessionIdentifier(sessionId))
+                    .build()
+            )
+            .build()
+    private val testFulfillmentResponse =
+        FulfillmentResponse.newBuilder()
+            .setExecutionOutput(
+                StructuredOutput.newBuilder()
+                    .addOutputValues(OutputValue.newBuilder().setName("bio_arg1"))
+            )
+            .build()
+    private var capability1 = mock<ActionCapability>()
+
+    @Before
+    fun before() {
+        capability1 = mock()
+        whenever(capability1.id).thenReturn(capabilityId)
+        whenever(capability1.getAppAction()).thenReturn(AppAction.getDefaultInstance())
+        val mockActionCapabilitySession = createMockSession()
+        whenever(capability1.createSession(any())).thenReturn(mockActionCapabilitySession)
+    }
+
+    @Test
+    fun startUpSession_validRequest_shouldGetValidStartSessionResponse(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+
+                // Set up gRPC response capture
+                val startUpSessionCallback = CompletableDeferred<Unit>()
+                val startSessionResponseObserver = mock<StreamObserver<StartSessionResponse>>()
+                whenever(startSessionResponseObserver.onNext(any())) doAnswer
+                    {
+                        startUpSessionCallback.complete(Unit)
+                        Unit
+                    }
+
+                // Send startup request
+                val startSessionRequestObserver = stub.startUpSession(startSessionResponseObserver)
+                startSessionRequestObserver.onNext(defaultStartSessionRequest)
+
+                // Assert startup response
+                startUpSessionCallback.await()
+                val responseCaptor = argumentCaptor<StartSessionResponse>()
+                verify(startSessionResponseObserver).onNext(responseCaptor.capture())
+                val startSessionResponse = responseCaptor.firstValue
+                assertThat(startSessionResponse)
+                    .isEqualTo(StartSessionResponse.getDefaultInstance())
+                verify(startSessionResponseObserver, times(1)).onNext(any())
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun startUpSession_shouldFailWhenNoStaticCapability(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val startSessionResponseObserver = mock<StreamObserver<StartSessionResponse>>()
+
+                // Send startup request
+                val invalidStartSessionRequest =
+                    StartSessionRequest.newBuilder()
+                        .setIntentName(testBiiName)
+                        .setIdentifier("UNKNOWN_FULFILLMENT_ID")
+                        .setSessionIdentifier(sessionId)
+                        .build()
+                val startSessionRequestObserver = stub.startUpSession(startSessionResponseObserver)
+                startSessionRequestObserver.onNext(invalidStartSessionRequest)
+
+                // Assert.
+                val exceptionCaptor = argumentCaptor<StatusRuntimeException>()
+                verify(startSessionResponseObserver).onError(exceptionCaptor.capture())
+                assertThat(Status.fromThrowable(exceptionCaptor.firstValue).code)
+                    .isEqualTo(Status.Code.FAILED_PRECONDITION)
+                assertThat(Status.fromThrowable(exceptionCaptor.firstValue).description)
+                    .isEqualTo(ERROR_NO_ACTION_CAPABILITY)
+                verify(capability1, never()).createSession(any())
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun sendRequestFulfillment_shouldGetValidResponse(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val futureStub = AppInteractionServiceGrpc.newFutureStub(channel)
+                assertStartupSession(stub)
+                verify(capability1, times(1)).createSession(any())
+
+                // Send fulfillment request
+                val request =
+                    Request.newBuilder().setFulfillmentRequest(testFulfillmentRequest).build()
+                val responseFuture = futureStub.sendRequestFulfillment(request)
+
+                val response = responseFuture.await()
+                assertThat(response).isNotNull()
+                assertThat(response.fulfillmentResponse).isEqualTo(testFulfillmentResponse)
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun sendRequestFulfillment_shouldFailWhenNoFulfillment(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val futureStub = AppInteractionServiceGrpc.newFutureStub(channel)
+                assertStartupSession(stub)
+                verify(capability1, times(1)).createSession(any())
+
+                // Ensure a failed future is returned when missing fulfillment
+                val requestWithMissingFulfillment =
+                    Request.newBuilder()
+                        .setFulfillmentRequest(FulfillmentRequest.getDefaultInstance())
+                        .build()
+                val exception =
+                    assertFailsWith<StatusRuntimeException> {
+                        futureStub.sendRequestFulfillment(requestWithMissingFulfillment).await()
+                    }
+                assertThat(Status.fromThrowable(exception).code)
+                    .isEqualTo(Status.Code.FAILED_PRECONDITION)
+                assertThat(Status.fromThrowable(exception).description)
+                    .isEqualTo(ERROR_NO_FULFILLMENT_REQUEST)
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun sendRequestFulfillment_shouldFailWhenNoStaticCapability(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val futureStub = AppInteractionServiceGrpc.newFutureStub(channel)
+                assertStartupSession(stub)
+                verify(capability1, times(1)).createSession(any())
+
+                val requestWithUnknownFulfillmentId =
+                    Request.newBuilder()
+                        .setFulfillmentRequest(
+                            FulfillmentRequest.newBuilder()
+                                .addFulfillments(
+                                    Fulfillment.newBuilder()
+                                        .setIdentifier("UNKNOWN_FULFILLMENT_ID")
+                                        .setSessionInfo(
+                                            SessionInfo.newBuilder()
+                                                .setIsNewSession(true)
+                                                .setSessionIdentifier(sessionId)
+                                        )
+                                )
+                        )
+                        .build()
+                val exception =
+                    assertFailsWith<StatusRuntimeException> {
+                        futureStub.sendRequestFulfillment(requestWithUnknownFulfillmentId).await()
+                    }
+                assertThat(Status.fromThrowable(exception).code)
+                    .isEqualTo(Status.Code.FAILED_PRECONDITION)
+                assertThat(Status.fromThrowable(exception).description)
+                    .isEqualTo(ERROR_NO_ACTION_CAPABILITY)
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun sendRequestFulfillment_shouldFailWhenNoSession(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val futureStub = AppInteractionServiceGrpc.newFutureStub(channel)
+                assertStartupSession(stub)
+                verify(capability1, times(1)).createSession(any())
+
+                val requestWithUnknownFulfillmentId =
+                    Request.newBuilder()
+                        .setFulfillmentRequest(
+                            FulfillmentRequest.newBuilder()
+                                .addFulfillments(
+                                    Fulfillment.newBuilder()
+                                        .setIdentifier(capabilityId)
+                                        .setSessionInfo(
+                                            SessionInfo.newBuilder()
+                                                .setIsNewSession(true)
+                                                .setSessionIdentifier("UNKNOWN_SESSION_ID")
+                                        )
+                                )
+                        )
+                        .build()
+                val exception =
+                    assertFailsWith<StatusRuntimeException> {
+                        futureStub.sendRequestFulfillment(requestWithUnknownFulfillmentId).await()
+                    }
+                assertThat(Status.fromThrowable(exception).code)
+                    .isEqualTo(Status.Code.FAILED_PRECONDITION)
+                assertThat(Status.fromThrowable(exception).description).isEqualTo(ERROR_NO_SESSION)
+            }
+        }
+        server.shutdownNow()
+    }
+
+    @Test
+    fun sendRequestFulfillment_shouldFailWhenSessionEnded(): Unit = runBlocking {
+        val server =
+            createInProcessServer(
+                AppInteractionServiceGrpcImpl(FakeAppInteractionService(listOf(capability1))),
+                remoteViewsInterceptor
+            )
+        coroutineScope {
+            launch {
+                val channel = createInProcessChannel()
+                val stub = AppInteractionServiceGrpc.newStub(channel)
+                val futureStub = AppInteractionServiceGrpc.newFutureStub(channel)
+
+                // Verify capability session is created
+                val mockSession = createMockSession()
+                whenever(mockSession.status).thenReturn(ActionCapabilitySession.Status.COMPLETED)
+                whenever(capability1.createSession(any())).thenReturn(mockSession)
+                assertStartupSession(stub)
+                verify(capability1, times(1)).createSession(any())
+
+                // Send request to completed session.
+                val requestToEndedSession =
+                    Request.newBuilder().setFulfillmentRequest(testFulfillmentRequest).build()
+                val exception =
+                    assertFailsWith<StatusRuntimeException> {
+                        futureStub.sendRequestFulfillment(requestToEndedSession).await()
+                    }
+                assertThat(Status.fromThrowable(exception).code)
+                    .isEqualTo(Status.Code.FAILED_PRECONDITION)
+                assertThat(Status.fromThrowable(exception).description)
+                    .isEqualTo(ERROR_SESSION_ENDED)
+            }
+        }
+        server.shutdownNow()
+    }
+
+    private suspend fun assertStartupSession(stub: AppInteractionServiceStub) {
+        // Set up gRPC response capture
+        val startUpSession = CompletableDeferred<Unit>()
+        val startSessionResponseObserver = mock<StreamObserver<StartSessionResponse>>()
+        whenever(startSessionResponseObserver.onNext(any())) doAnswer
+            {
+                startUpSession.complete(Unit)
+                Unit
+            }
+
+        // Send startup request
+        val startSessionRequestObserver = stub.startUpSession(startSessionResponseObserver)
+        startSessionRequestObserver.onNext(defaultStartSessionRequest)
+
+        // Assert startup response
+        startUpSession.await()
+        val responseCaptor = argumentCaptor<StartSessionResponse>()
+        verify(startSessionResponseObserver).onNext(responseCaptor.capture())
+        val startSessionResponse = responseCaptor.firstValue
+        assertThat(startSessionResponse).isEqualTo(StartSessionResponse.getDefaultInstance())
+        verify(startSessionResponseObserver, times(1)).onNext(any())
+    }
+
+    @Throws(IOException::class)
+    private fun createInProcessServer(
+        service: BindableService,
+        vararg interceptors: ServerInterceptor
+    ): Server {
+        return grpcCleanup.register(
+            InProcessServerBuilder.forName(testServerName)
+                .directExecutor()
+                .addService(ServerInterceptors.intercept(service, *interceptors))
+                .build()
+                .start()
+        )
+    }
+
+    private fun createInProcessChannel(): ManagedChannel {
+        return grpcCleanup.register(
+            InProcessChannelBuilder.forName(testServerName).directExecutor().build()
+        )
+    }
+
+    private fun createMockSession(): ActionCapabilitySession {
+        val mockSession = mock<ActionCapabilitySession>()
+        whenever(mockSession.execute(any(), any())).thenAnswer { invocation ->
+            (invocation.arguments[1] as CallbackInternal).onSuccess(testFulfillmentResponse)
+        }
+        whenever(mockSession.state).thenReturn(AppDialogState.getDefaultInstance())
+        whenever(mockSession.status).thenReturn(ActionCapabilitySession.Status.UNINITIATED)
+        whenever(mockSession.uiHandle).thenReturn(Any())
+        return mockSession
+    }
+
+    private inner class FakeAppInteractionService(capabilities: List<ActionCapability>) :
+        AppInteractionService() {
+        override val registeredCapabilities: MutableList<ActionCapability> =
+            capabilities.toMutableList()
+
+        override val securityPolicy: SecurityPolicy = SecurityPolicies.internalOnly()
+
+        override val allowedApps: List<AppVerificationInfo> = Collections.emptyList()
+
+        override fun getApplicationContext(): Context {
+            return context
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppVerificationInfoTest.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppVerificationInfoTest.kt
index 6e63e1c3..20c792f 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppVerificationInfoTest.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/AppVerificationInfoTest.kt
@@ -26,7 +26,7 @@
 
     @Test
     fun builderPattern() {
-        var verificationInfo: AppVerificationInfo =
+        val verificationInfo: AppVerificationInfo =
             AppVerificationInfo.Builder()
                 .setPackageName("packageName")
                 .addSignature(listOf(ByteArray(5)))
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/SessionManagerTest.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/SessionManagerTest.kt
index 5d3fb70..c33dfc9 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/SessionManagerTest.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/SessionManagerTest.kt
@@ -39,8 +39,8 @@
 
             override fun setTouchEventCallback(callback: TouchEventCallback) {}
 
-            override val state: AppActionsContext.AppAction
-                get() = AppActionsContext.AppAction.getDefaultInstance()
+            override val state: AppActionsContext.AppDialogState
+                get() = AppActionsContext.AppDialogState.getDefaultInstance()
 
             override val status: ActionCapabilitySession.Status
                 get() = ActionCapabilitySession.Status.IN_PROGRESS
@@ -57,8 +57,8 @@
 
             override fun setTouchEventCallback(callback: TouchEventCallback) {}
 
-            override val state: AppActionsContext.AppAction
-                get() = AppActionsContext.AppAction.getDefaultInstance()
+            override val state: AppActionsContext.AppDialogState
+                get() = AppActionsContext.AppDialogState.getDefaultInstance()
 
             override val status: ActionCapabilitySession.Status
                 get() = ActionCapabilitySession.Status.IN_PROGRESS
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiCacheTest.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiCacheTest.kt
index fba1a32..4cf3bbe 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiCacheTest.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiCacheTest.kt
@@ -57,7 +57,7 @@
 
     @Test
     fun unreadUiResponseFlag_lifecycle() {
-        val uiCache = UiCache(capabilitySession)
+        val uiCache = UiCache()
         assertThat(uiCache.hasUnreadUiResponse()).isFalse()
 
         // Test set unread flag.
@@ -67,11 +67,12 @@
         // Test reset.
         uiCache.resetUnreadUiResponse()
         assertThat(uiCache.hasUnreadUiResponse()).isFalse()
+        assertEmptyCache(uiCache)
     }
 
     @Test
     fun remoteViewsUiResponse_noFactoryNoChangedViews() {
-        val uiCache = UiCache(capabilitySession)
+        val uiCache = UiCache()
         assertEmptyCache(uiCache)
 
         uiCache.updateUiInternal(remoteViewsUiResponse)
@@ -84,7 +85,7 @@
 
     @Test
     fun remoteViewsUiResponse_withFactory() {
-        val uiCache = UiCache(capabilitySession)
+        val uiCache = UiCache()
         assertEmptyCache(uiCache)
 
         uiCache.updateUiInternal(remoteViewsUiResponseWithFactory)
@@ -96,7 +97,7 @@
 
     @Test
     fun remoteViewsUiResponse_withChangeView() {
-        val uiCache = UiCache(capabilitySession)
+        val uiCache = UiCache()
         assertEmptyCache(uiCache)
 
         uiCache.updateUiInternal(remoteViewsUiResponseWithChangeId)
diff --git a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiSessionsTest.kt b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiSessionsTest.kt
index ce816a0..1ed52d5 100644
--- a/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiSessionsTest.kt
+++ b/appactions/interaction/interaction-service/src/test/java/androidx/appactions/interaction/service/UiSessionsTest.kt
@@ -22,19 +22,29 @@
 import androidx.appactions.interaction.capabilities.core.ActionExecutor
 import androidx.appactions.interaction.capabilities.core.BaseSession
 import androidx.appactions.interaction.capabilities.core.ExecutionResult
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilitySession
 import androidx.appactions.interaction.service.test.R
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.wear.tiles.LayoutElementBuilders
 import androidx.wear.tiles.ResourceBuilders
 import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.doReturn
+import com.nhaarman.mockitokotlin2.mock
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 class UiSessionsTest {
 
-    private val capabilitySession = object : BaseSession<String, String> {}
+    private val externalSession = object : BaseSession<String, String> {}
+    private val actionCapabilitySession = mock<ActionCapabilitySession>() {
+        on { this.uiHandle } doReturn(externalSession)
+    }
+    private val sessionId = "fakeSessionId"
+
     private val context: Context = ApplicationProvider.getApplicationContext()
     private val remoteViewsFactoryId = 123
     private val changeViewId = 111
@@ -52,24 +62,34 @@
                     .addContent(
                         LayoutElementBuilders.Column.Builder()
                             .addContent(
-                                LayoutElementBuilders.Text.Builder().setText("LA8JE92").build()
+                                LayoutElementBuilders.Text.Builder().setText("LA8JE92").build(),
                             )
-                            .build()
+                            .build(),
                     )
-                    .build()
+                    .build(),
             )
             .build()
     private val resources = ResourceBuilders.Resources.Builder().setVersion("1234").build()
     private val tileLayoutUiResponse: UiResponse =
         UiResponse.TileLayoutBuilder().setTileLayout(layout, resources).build()
 
+    @Before
+    fun setup() {
+        SessionManager.putSession(sessionId, actionCapabilitySession)
+    }
+    @After
+    fun cleanup() {
+        UiSessions.removeUiCache(sessionId)
+        SessionManager.removeSession(sessionId)
+    }
+
     @Test
     fun sessionExtensionMethod_createsCache() {
-        assertThat(UiSessions.getUiCacheOrNull(capabilitySession)).isNull()
+        assertThat(UiSessions.getUiCacheOrNull(sessionId)).isNull()
 
-        capabilitySession.updateUi(remoteViewsUiResponse)
+        externalSession.updateUi(remoteViewsUiResponse)
 
-        val uiCache = UiSessions.getUiCacheOrNull(capabilitySession)
+        val uiCache = UiSessions.getUiCacheOrNull(sessionId)
         assertThat(uiCache).isNotNull()
         assertThat(uiCache?.hasUnreadUiResponse()).isTrue()
         assertThat(uiCache?.cachedChangedViewIds).containsExactly(changeViewId)
@@ -79,46 +99,56 @@
 
     @Test
     fun removeUiCache_removesWhatWasPreviouslyCreated() {
-        assertThat(UiSessions.getUiCacheOrNull(capabilitySession)).isNull()
+        assertThat(UiSessions.getUiCacheOrNull(sessionId)).isNull()
 
         // Invoke extension method.
-        capabilitySession.updateUi(remoteViewsUiResponse)
+        externalSession.updateUi(remoteViewsUiResponse)
 
-        val uiCache = UiSessions.getUiCacheOrNull(capabilitySession)
+        val uiCache = UiSessions.getUiCacheOrNull(sessionId)
         assertThat(uiCache).isNotNull()
         assertThat(uiCache?.hasUnreadUiResponse()).isTrue()
 
         // Test removing.
-        assertThat(UiSessions.removeUiCache(capabilitySession)).isTrue()
-        assertThat(UiSessions.getUiCacheOrNull(capabilitySession)).isNull()
+        assertThat(UiSessions.removeUiCache(sessionId)).isTrue()
+        assertThat(UiSessions.getUiCacheOrNull(sessionId)).isNull()
     }
 
     @Test
     fun getOrCreateUiCache_explicitTest() {
-        val uiCache = UiSessions.getOrCreateUiCache(capabilitySession)
+        val uiCache = UiSessions.getOrCreateUiCache(sessionId)
         assertThat(uiCache).isNotNull()
 
         // Calling a second time does not create a new cache instance.
-        val uiCache2 = UiSessions.getOrCreateUiCache(capabilitySession)
+        val uiCache2 = UiSessions.getOrCreateUiCache(sessionId)
         assertThat(uiCache).isEqualTo(uiCache2)
     }
 
     @Test
     fun multipleSession_haveTheirOwnCache() {
-        val capabilitySession1 = object : BaseSession<String, String> {}
-        val capabilitySession2 = object : BaseSession<String, String> {}
+        val externalSession1 = object : BaseSession<String, String> {}
+        val capabilitySession1 = mock<ActionCapabilitySession> {
+            on { this.uiHandle } doReturn(externalSession1)
+        }
+        val sessionId1 = "fakeSessionId1"
+        val externalSession2 = object : BaseSession<String, String> {}
+        val capabilitySession2 = mock<ActionCapabilitySession> {
+            on { this.uiHandle } doReturn(externalSession2)
+        }
+        val sessionId2 = "fakeSessionId2"
+        SessionManager.putSession(sessionId1, capabilitySession1)
+        SessionManager.putSession(sessionId2, capabilitySession2)
 
-        capabilitySession1.updateUi(remoteViewsUiResponse)
+        externalSession1.updateUi(remoteViewsUiResponse)
 
-        val uiCache1 = UiSessions.getUiCacheOrNull(capabilitySession1)
+        val uiCache1 = UiSessions.getUiCacheOrNull(sessionId1)
         assertThat(uiCache1).isNotNull()
         assertThat(uiCache1?.hasUnreadUiResponse()).isTrue()
         assertThat(uiCache1?.cachedRemoteViews).isEqualTo(remoteViews)
         assertThat(uiCache1?.cachedTileLayout).isNull()
 
-        capabilitySession2.updateUi(tileLayoutUiResponse)
+        externalSession2.updateUi(tileLayoutUiResponse)
 
-        val uiCache2 = UiSessions.getUiCacheOrNull(capabilitySession2)
+        val uiCache2 = UiSessions.getUiCacheOrNull(sessionId2)
         assertThat(uiCache2).isNotNull()
         assertThat(uiCache2?.hasUnreadUiResponse()).isTrue()
         assertThat(uiCache2?.cachedTileLayout).isNotNull()
@@ -127,8 +157,10 @@
         // Assert that UiCache2 response still marked unread.
         uiCache1?.resetUnreadUiResponse()
         assertThat(uiCache2?.hasUnreadUiResponse()).isTrue()
-        assertThat(uiCache1?.cachedRemoteViews).isEqualTo(remoteViews)
-        assertThat(uiCache1?.cachedTileLayout).isNull()
+        assertThat(uiCache2?.cachedTileLayout).isNotNull()
+
+        SessionManager.removeSession(sessionId1)
+        SessionManager.removeSession(sessionId2)
     }
 
     @Test
@@ -136,14 +168,21 @@
         val actionExecutor = ActionExecutor<String, String> {
             ExecutionResult.getDefaultInstance()
         }
+        val session = mock<ActionCapabilitySession> {
+            on { this.uiHandle } doReturn(actionExecutor)
+        }
+        val sessionId = "actionExecutorSessionId"
+        SessionManager.putSession(sessionId, session)
 
         actionExecutor.updateUi(remoteViewsUiResponse)
 
-        val uiCache = UiSessions.getUiCacheOrNull(actionExecutor)
+        val uiCache = UiSessions.getUiCacheOrNull(sessionId)
         assertThat(uiCache).isNotNull()
         assertThat(uiCache?.hasUnreadUiResponse()).isTrue()
         assertThat(uiCache?.cachedChangedViewIds).containsExactly(changeViewId)
         assertThat(uiCache?.cachedRemoteViewsSize).isEqualTo(SizeF(10f, 15f))
         assertThat(uiCache?.cachedRemoteViews).isEqualTo(remoteViews)
+
+        SessionManager.removeSession(sessionId)
     }
 }
diff --git a/appcompat/OWNERS b/appcompat/OWNERS
index 43a6878..880f490 100644
--- a/appcompat/OWNERS
+++ b/appcompat/OWNERS
@@ -1,6 +1,10 @@
 # Bug component: 461199
 clarabayarri@google.com
 
+# For UI
+aelias@google.com
+ryanmentley@google.com
+
 # For text related files
 nona@google.com
 siyamed@google.com
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index 44bd7a4..b381126 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -32,7 +32,7 @@
     implementation("androidx.lifecycle:lifecycle-viewmodel:2.5.1")
     implementation("androidx.profileinstaller:profileinstaller:1.2.1")
     implementation("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
-    api("androidx.savedstate:savedstate:1.2.0")
+    api("androidx.savedstate:savedstate:1.2.1")
 
     // Due to experimental annotations used in core.
     compileOnly(libs.kotlinStdlib)
diff --git a/appcompat/appcompat/src/androidTest/AndroidManifest.xml b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
index 2abfc0d..cf53763 100644
--- a/appcompat/appcompat/src/androidTest/AndroidManifest.xml
+++ b/appcompat/appcompat/src/androidTest/AndroidManifest.xml
@@ -156,6 +156,11 @@
             android:theme="@style/Theme.AppCompat.Light"/>
 
         <activity
+            android:name="androidx.appcompat.widget.AppCompatEditTextImeFocusActivity"
+            android:label="@string/app_compat_edit_text_ime_focus_activity"
+            android:theme="@style/Theme.TextColors"/>
+
+        <activity
             android:name="androidx.appcompat.widget.AppCompatButtonAutoSizeActivity"
             android:label="@string/app_compat_button_auto_size_activity"
             android:theme="@style/Theme.AppCompat.Light"/>
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java
new file mode 100644
index 0000000..6783008
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusActivity.java
@@ -0,0 +1,76 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appcompat.widget;
+
+import android.view.ViewGroup;
+import android.view.inputmethod.InputMethodManager;
+
+import androidx.annotation.RequiresApi;
+import androidx.appcompat.test.R;
+import androidx.appcompat.testutils.BaseTestActivity;
+
+public class AppCompatEditTextImeFocusActivity extends BaseTestActivity {
+
+    ViewGroup mLayout;
+    AppCompatEditText mEditText1;
+    AppCompatEditText mEditText2;
+    InputMethodManager mInputMethodManager;
+
+    @Override
+    protected int getContentViewLayoutResId() {
+        return R.layout.appcompat_edittext_ime_focus_activity;
+    }
+
+    @RequiresApi(23)
+    public void initActivity() {
+        mLayout = findViewById(R.id.ime_layout);
+        mEditText1 = new AppCompatEditText(this);
+        mEditText2 = new AppCompatEditText(this);
+        mInputMethodManager = getSystemService(InputMethodManager.class);
+    }
+
+    public void addFirstEditorAndRequestFocus() {
+        mEditText1.requestFocus();
+        mLayout.addView(mEditText1);
+    }
+
+    public void addSecondEditor() {
+        mLayout.addView(mEditText2);
+    }
+
+    @RequiresApi(19)
+    public boolean isSecondEditorLayout() {
+        return mEditText2.isLaidOut();
+    }
+
+    public void switchToSecondEditor() {
+        mEditText2.requestFocus();
+        mLayout.removeView(mEditText1);
+    }
+
+    public boolean isFirstEditorActive() {
+        return mInputMethodManager.isActive(mEditText1);
+    }
+
+    public boolean isSecondEditorActive() {
+        return mInputMethodManager.isActive(mEditText2);
+    }
+
+    public void removeSecondEditor() {
+        mLayout.removeView(mEditText2);
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java
new file mode 100644
index 0000000..284cca4
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/java/androidx/appcompat/widget/AppCompatEditTextImeFocusTest.java
@@ -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.appcompat.widget;
+
+
+import static org.junit.Assert.assertTrue;
+
+import androidx.test.core.app.ActivityScenario;
+import androidx.test.ext.junit.rules.ActivityScenarioRule;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
+import androidx.testutils.PollingCheck;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.concurrent.TimeUnit;
+
+@RunWith(AndroidJUnit4.class)
+@LargeTest
+public class AppCompatEditTextImeFocusTest {
+
+    private static final long TIMEOUT = TimeUnit.SECONDS.toMillis(5);
+    private static final int STRESS_TEST_COUNTS = 500;
+
+    @Rule
+    public final ActivityScenarioRule<AppCompatEditTextImeFocusActivity> mActivityScenarioRule =
+            new ActivityScenarioRule<>(AppCompatEditTextImeFocusActivity.class);
+
+    @Test
+    @SdkSuppress(minSdkVersion = 30)
+    public void detachServed_withDifferentNextServed_stressTest() {
+        for (int i = 0; i < STRESS_TEST_COUNTS; i++) {
+            detachServed_withDifferentNextServed();
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 30)
+    private void detachServed_withDifferentNextServed() {
+        final ActivityScenario<AppCompatEditTextImeFocusActivity> activityScenario =
+                mActivityScenarioRule.getScenario();
+
+        // Initialize activity components and add first editor into layout
+        activityScenario.onActivity(activity -> {
+            activity.initActivity();
+            activity.addFirstEditorAndRequestFocus();
+        });
+
+        // The first editor should be active for input method.
+        activityScenario.onActivity(
+                activity -> PollingCheck.waitFor(TIMEOUT, activity::isFirstEditorActive));
+
+        // Add second editor into layout and ensure it is laid out.
+        activityScenario.onActivity(AppCompatEditTextImeFocusActivity::addSecondEditor);
+        activityScenario.onActivity(
+                activity -> PollingCheck.waitFor(TIMEOUT, activity::isSecondEditorLayout));
+
+        // Second editor request focus and detach the served view(first editor).
+        // Verify second editor should be active.
+        activityScenario.onActivity(activity -> {
+            activity.switchToSecondEditor();
+            assertTrue(activity.isSecondEditorActive());
+            activity.removeSecondEditor();
+        });
+    }
+}
diff --git a/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml
new file mode 100644
index 0000000..5767e18
--- /dev/null
+++ b/appcompat/appcompat/src/androidTest/res/layout/appcompat_edittext_ime_focus_activity.xml
@@ -0,0 +1,24 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<LinearLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    android:id="@+id/ime_layout"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:orientation="vertical">
+</LinearLayout>
\ No newline at end of file
diff --git a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
index e38440a..a73602f 100644
--- a/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
+++ b/appcompat/appcompat/src/androidTest/res/values/donottranslate-strings.xml
@@ -64,6 +64,7 @@
     <string name="app_compat_text_view_auto_size_activity">AppCompat text view auto-size</string>
     <string name="app_compat_text_view_emoji_activity">AppCompat text view emoji</string>
     <string name="app_compat_edit_text_activity">AppCompat edit text</string>
+    <string name="app_compat_edit_text_ime_focus_activity">AppCompat edit text ime focus</string>
     <string name="app_compat_edit_text_receive_content_activity">
         AppCompat edit text receive content activity
     </string>
diff --git a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
index 042db5b..770f713 100644
--- a/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
+++ b/appcompat/appcompat/src/main/java/androidx/appcompat/widget/AppCompatEditText.java
@@ -32,6 +32,7 @@
 import android.view.DragEvent;
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
+import android.view.inputmethod.InputMethodManager;
 import android.view.textclassifier.TextClassifier;
 import android.widget.EditText;
 import android.widget.TextView;
@@ -301,6 +302,20 @@
                 super.getCustomSelectionActionModeCallback());
     }
 
+    @Override
+    protected void onDetachedFromWindow() {
+        super.onDetachedFromWindow();
+        if (Build.VERSION.SDK_INT >= 30 && Build.VERSION.SDK_INT < 33) {
+            final InputMethodManager imm = (InputMethodManager) getContext().getSystemService(
+                    Context.INPUT_METHOD_SERVICE);
+            // Calling isActive() here implied a checkFocus() call to update the active served
+            // view for input method. This is a backport for mServedView was detached, but the
+            // next served view gets mistakenly cleared as well.
+            // https://android.googlesource.com/platform/frameworks/base/+/734613a500fb
+            imm.isActive(this);
+        }
+    }
+
     @UiThread
     @NonNull
     @RequiresApi(26)
diff --git a/benchmark/baseline-profile-gradle-plugin/build.gradle b/benchmark/baseline-profile-gradle-plugin/build.gradle
index 34e7a03..ebf6019 100644
--- a/benchmark/baseline-profile-gradle-plugin/build.gradle
+++ b/benchmark/baseline-profile-gradle-plugin/build.gradle
@@ -62,7 +62,7 @@
 
 androidx {
     name = "Android Baseline Profile Gradle Plugin"
-    publish = Publish.SNAPSHOT_ONLY
+    publish = Publish.SNAPSHOT_AND_RELEASE
     type = LibraryType.GRADLE_PLUGIN
     inceptionYear = "2022"
     description = "Android Baseline Profile Gradle Plugin"
@@ -73,4 +73,4 @@
         failOnWarning.set(true)
         enableStricterValidation.set(true)
     }
-}
\ No newline at end of file
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
index 85b31d3..db9d280 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPlugin.kt
@@ -16,11 +16,13 @@
 
 package androidx.baselineprofile.gradle.apptarget
 
+import androidx.baselineprofile.gradle.utils.AgpPlugin
+import androidx.baselineprofile.gradle.utils.AgpPluginId
 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX
-import androidx.baselineprofile.gradle.utils.checkAgpVersion
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
 import androidx.baselineprofile.gradle.utils.createExtendedBuildTypes
-import androidx.baselineprofile.gradle.utils.isGradleSyncRunning
-import com.android.build.api.variant.ApplicationAndroidComponentsExtension
+import com.android.build.api.dsl.ApplicationExtension
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 
@@ -32,92 +34,77 @@
  * test that generate the baseline profile on the device (producer).
  */
 class BaselineProfileAppTargetPlugin : Plugin<Project> {
+    override fun apply(project: Project) = BaselineProfileAppTargetAgpPlugin(project).onApply()
+}
 
-    override fun apply(project: Project) {
-        var foundAppPlugin = false
-        project.pluginManager.withPlugin("com.android.application") {
-            foundAppPlugin = true
-            configureWithAndroidPlugin(project = project)
-        }
-        var foundLibraryPlugin = false
-        project.pluginManager.withPlugin("com.android.library") {
-            foundLibraryPlugin = true
-        }
+private class BaselineProfileAppTargetAgpPlugin(private val project: Project) : AgpPlugin(
+    project = project,
+    supportedAgpPlugins = setOf(
+        AgpPluginId.ID_ANDROID_APPLICATION_PLUGIN,
+        AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN
+    ),
+    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
+    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+) {
+    override fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) {
 
-        // Only used to verify that the android application plugin has been applied.
-        // Note that we don't want to throw any exception if gradle sync is in progress.
-        project.afterEvaluate {
-            if (!project.isGradleSyncRunning()) {
-                if (!foundAppPlugin) {
-
-                    // Check whether the library plugin was applied instead. If that's the case
-                    // it's possible the developer meant to generate a baseline profile for a
-                    // library and we can give further information.
-                    throw IllegalStateException(
-                        if (!foundLibraryPlugin) {
-                            """
-                    The module ${project.name} does not have the `com.android.application` plugin
-                    applied. The `androidx.baselineprofile.apptarget` plugin supports only
-                    android application modules. Please review your build.gradle to ensure this
-                    plugin is applied to the correct module.
-                    """.trimIndent()
-                        } else {
-                            """
-                    The module ${project.name} does not have the `com.android.application` plugin
-                    but has the `com.android.library` plugin. If you're trying to generate a
-                    baseline profile for a library, you'll need to apply the
-                    `androidx.baselineprofile.apptarget` to an android application that
-                    has the `com.android.application` plugin applied. This should be a sample app
-                    running the code of the library for which you want to generate the profile.
-                    Please review your build.gradle to ensure this plugin is applied to the
-                    correct module.
-                    """.trimIndent()
-                        }
-                    )
-                }
-                project.logger.debug(
-                    """
-                    [BaselineProfileAppTargetPlugin] afterEvaluate check: app plugin was applied
-                    """.trimIndent()
-                )
-            }
-        }
+        // If no supported plugin was found throw an exception.
+        throw IllegalStateException(
+            """
+            The module ${project.name} does not have the `com.android.application` plugin
+            applied. The `androidx.baselineprofile.apptarget` plugin supports only
+            android application modules. Please review your build.gradle to ensure this
+            plugin is applied to the correct module.
+            """.trimIndent()
+        )
     }
 
-    private fun configureWithAndroidPlugin(project: Project) {
+    override fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) {
 
-        // Checks that the required AGP version is applied to this project.
-        project.checkAgpVersion()
+        // If the library plugin was found throw an exception. It's possible the developer meant
+        // to generate a baseline profile for a library and we can give further information.
+        if (pluginIds.contains(AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN)) {
+            throw IllegalStateException(
+                """
+            The module ${project.name} does not have the `com.android.application` plugin
+            but has the `com.android.library` plugin. If you're trying to generate a
+            baseline profile for a library, you'll need to apply the
+            `androidx.baselineprofile.apptarget` to an android application that
+            has the `com.android.application` plugin applied. This should be a sample app
+            running the code of the library for which you want to generate the profile.
+            Please review your build.gradle to ensure this plugin is applied to the
+            correct module.
+            """.trimIndent()
+            )
+        }
 
-        // Create the non obfuscated release build types from the existing release ones.
-        // We want to extend all the current release build types based on isDebuggable flag.
+        // Otherwise, just log the plugin was applied.
         project
-            .extensions
-            .getByType(ApplicationAndroidComponentsExtension::class.java)
-            .finalizeDsl { applicationExtension ->
+            .logger
+            .debug("[BaselineProfileAppTargetPlugin] afterEvaluate check: app plugin was applied")
+    }
 
-                val debugBuildType = applicationExtension.buildTypes.getByName("debug")
-
-                // Creates the baseline profile build types
-                createExtendedBuildTypes(
-                    project = project,
-                    extension = applicationExtension,
-                    newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
-                    filterBlock = {
-                        // Create baseline profile build types only for non debuggable builds.
-                        !it.isDebuggable
-                    },
-                    configureBlock = {
-                        isJniDebuggable = false
-                        isDebuggable = false
-                        isMinifyEnabled = false
-                        isShrinkResources = false
-                        isProfileable = true
-                        signingConfig = debugBuildType.signingConfig
-                        enableAndroidTestCoverage = false
-                        enableUnitTestCoverage = false
-                    }
-                )
+    override fun onApplicationFinalizeDsl(extension: ApplicationExtension) {
+        // Creates baseline profile build types extending the currently existing ones.
+        // They're named `<BUILD_TYPE_BASELINE_PROFILE_PREFIX><originalBuildTypeName>`.
+        createExtendedBuildTypes(
+            project = project,
+            extension = extension,
+            newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
+            filterBlock = {
+                // Create baseline profile build types only for non debuggable builds.
+                !it.isDebuggable
+            },
+            configureBlock = {
+                isJniDebuggable = false
+                isDebuggable = false
+                isMinifyEnabled = false
+                isShrinkResources = false
+                isProfileable = true
+                signingConfig = extension.buildTypes.getByName("debug").signingConfig
+                enableAndroidTestCoverage = false
+                enableUnitTestCoverage = false
             }
+        )
     }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/ConfigurationManager.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/ConfigurationManager.kt
new file mode 100644
index 0000000..17f904f
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/ConfigurationManager.kt
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.configuration
+
+import androidx.baselineprofile.gradle.configuration.attribute.BaselineProfilePluginVersionAttr
+import androidx.baselineprofile.gradle.utils.ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION
+import androidx.baselineprofile.gradle.utils.ATTRIBUTE_TARGET_JVM_ENVIRONMENT
+import androidx.baselineprofile.gradle.utils.ATTRIBUTE_USAGE_BASELINE_PROFILE
+import androidx.baselineprofile.gradle.utils.agpVersion
+import androidx.baselineprofile.gradle.utils.camelCase
+import com.android.build.api.AndroidPluginVersion
+import com.android.build.api.attributes.AgpVersionAttr
+import com.android.build.api.attributes.BuildTypeAttr
+import com.android.build.api.attributes.ProductFlavorAttr
+import org.gradle.api.Project
+import org.gradle.api.artifacts.Configuration
+import org.gradle.api.attributes.AttributeContainer
+import org.gradle.api.attributes.Usage
+import org.gradle.api.attributes.java.TargetJvmEnvironment
+
+/**
+ * Helps creating configurations for the baseline profile plugin, handling the attributes used
+ * between the different modules to exchange artifacts.
+ */
+internal class ConfigurationManager(private val project: Project) {
+
+    private fun AttributeContainer.usage(value: String) {
+        attribute(
+            Usage.USAGE_ATTRIBUTE,
+            project.objects.named(Usage::class.java, value)
+        )
+    }
+
+    private fun AttributeContainer.buildType(value: String) {
+        attribute(
+            BuildTypeAttr.ATTRIBUTE,
+            project.objects.named(BuildTypeAttr::class.java, value)
+        )
+    }
+
+    private fun AttributeContainer.targetJvmEnvironment(value: String) {
+        attribute(
+            TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
+            project.objects.named(TargetJvmEnvironment::class.java, value)
+        )
+    }
+
+    private fun AttributeContainer.agpVersion(value: String) {
+        attribute(
+            AgpVersionAttr.ATTRIBUTE,
+            project.objects.named(AgpVersionAttr::class.java, value)
+        )
+    }
+
+    private fun AttributeContainer.baselineProfilePluginVersion(value: String) {
+        attribute(
+            BaselineProfilePluginVersionAttr.ATTRIBUTE,
+            project.objects.named(BaselineProfilePluginVersionAttr::class.java, value)
+        )
+    }
+
+    @Suppress("UnstableApiUsage")
+    private fun AttributeContainer.productFlavors(productFlavors: List<Pair<String, String>>) {
+        productFlavors.forEach { (flavorName, flavorValue) ->
+            attribute(
+                ProductFlavorAttr.of(flavorName),
+                project.objects.named(ProductFlavorAttr::class.java, flavorValue)
+            )
+        }
+    }
+
+    fun maybeCreate(
+        nameParts: List<String>,
+        canBeResolved: Boolean,
+        canBeConsumed: Boolean,
+        extendFromConfigurations: List<Configuration>? = null,
+        buildType: String?,
+        productFlavors: List<Pair<String, String>>?,
+        usage: String? = ATTRIBUTE_USAGE_BASELINE_PROFILE,
+        targetJvmEnvironment: String? = ATTRIBUTE_TARGET_JVM_ENVIRONMENT,
+        bpPluginVersion: String? = ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION,
+        agpVersion: String? = project.agpVersion().versionString()
+    ): Configuration {
+        return project.configurations.maybeCreate(camelCase(*(nameParts.toTypedArray()))).apply {
+
+            isCanBeResolved = canBeResolved
+            isCanBeConsumed = canBeConsumed
+
+            if (extendFromConfigurations != null) setExtendsFrom(extendFromConfigurations)
+
+            attributes {
+                if (buildType != null) it.buildType(buildType)
+                if (productFlavors != null) it.productFlavors(productFlavors)
+                if (usage != null) it.usage(usage)
+                if (targetJvmEnvironment != null) it.targetJvmEnvironment(targetJvmEnvironment)
+                if (agpVersion != null) it.agpVersion(agpVersion)
+                if (bpPluginVersion != null) it.baselineProfilePluginVersion(bpPluginVersion)
+            }
+        }
+    }
+
+    private fun AndroidPluginVersion.versionString(): String {
+        val preview = if (!previewType.isNullOrBlank()) {
+            "-$previewType$preview"
+        } else {
+            ""
+        }
+        return "$major.$minor.$micro$preview"
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/attributes/BaselineProfilePluginVersionAttr.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/attribute/BaselineProfilePluginVersionAttr.kt
similarity index 79%
rename from benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/attributes/BaselineProfilePluginVersionAttr.kt
rename to benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/attribute/BaselineProfilePluginVersionAttr.kt
index 81ed515..ffd7e3d 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/attributes/BaselineProfilePluginVersionAttr.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/configuration/attribute/BaselineProfilePluginVersionAttr.kt
@@ -14,9 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.baselineprofile.gradle.attributes
+package androidx.baselineprofile.gradle.configuration.attribute
 
-import androidx.baselineprofile.gradle.attributes.BaselineProfilePluginVersionAttr.Companion.ATTRIBUTE
+import androidx.baselineprofile.gradle.configuration.attribute.BaselineProfilePluginVersionAttr.Companion.ATTRIBUTE
+import org.gradle.api.Named
 import org.gradle.api.attributes.Attribute
 
 /**
@@ -25,7 +26,7 @@
  * There should only be one build type attribute associated to each
  * [org.gradle.api.artifacts.Configuration] object. The key should be [ATTRIBUTE].
  */
-interface BaselineProfilePluginVersionAttr : org.gradle.api.Named {
+internal interface BaselineProfilePluginVersionAttr : Named {
     companion object {
         @JvmField
         val ATTRIBUTE: Attribute<BaselineProfilePluginVersionAttr> =
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
index 2684330..0ae17d0 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerExtension.kt
@@ -16,34 +16,180 @@
 
 package androidx.baselineprofile.gradle.consumer
 
+import javax.inject.Inject
 import org.gradle.api.Action
-import org.gradle.api.Incubating
+import org.gradle.api.NamedDomainObjectContainer
 import org.gradle.api.Project
+import org.gradle.api.model.ObjectFactory
 
 /**
  * Allows specifying settings for the Baseline Profile Consumer Plugin.
  */
-open class BaselineProfileConsumerExtension {
+abstract class BaselineProfileConsumerExtension @Inject constructor(
+    objectFactory: ObjectFactory
+) : BaselineProfileVariantConfiguration {
 
     companion object {
         private const val EXTENSION_NAME = "baselineProfile"
 
-        internal fun registerExtension(project: Project): BaselineProfileConsumerExtension {
-            val ext = project.extensions.findByType(BaselineProfileConsumerExtension::class.java)
+        internal fun register(project: Project): BaselineProfileConsumerExtension {
+            val ext = project
+                .extensions
+                .findByType(BaselineProfileConsumerExtension::class.java)
             if (ext != null) {
                 return ext
             }
             return project
-                .extensions.create(EXTENSION_NAME, BaselineProfileConsumerExtension::class.java)
+                .extensions
+                .create(EXTENSION_NAME, BaselineProfileConsumerExtension::class.java)
         }
     }
 
+    val variants: NamedDomainObjectContainer<BaselineProfileVariantConfigurationImpl> =
+        objectFactory.domainObjectContainer(BaselineProfileVariantConfigurationImpl::class.java)
+
+    // Shortcut to access the "main" variant.
+    private val main: BaselineProfileVariantConfiguration = variants.create("main") {
+
+        // These are the default global settings.
+        it.mergeIntoMain = null
+        it.baselineProfileOutputDir = "generated/baselineProfiles"
+        it.enableR8BaselineProfileRewrite = false
+        it.saveInSrc = true
+        it.automaticGenerationDuringBuild = false
+    }
+
+    /**
+     * Controls the global [BaselineProfileVariantConfiguration.enableR8BaselineProfileRewrite].
+     * Note that this value is overridden by per variant configurations.
+     */
+    override var enableR8BaselineProfileRewrite: Boolean?
+        get() = main.enableR8BaselineProfileRewrite
+        set(value) {
+            main.enableR8BaselineProfileRewrite = value
+        }
+
+    /**
+     * Controls the global [BaselineProfileVariantConfiguration.saveInSrc].
+     * Note that this value is overridden by per variant configurations.
+     */
+    override var saveInSrc: Boolean?
+        get() = main.saveInSrc
+        set(value) {
+            main.saveInSrc = value
+        }
+
+    /**
+     * Controls the global [BaselineProfileVariantConfiguration.automaticGenerationDuringBuild].
+     * Note that this value is overridden by per variant configurations.
+     */
+    override var automaticGenerationDuringBuild: Boolean?
+        get() = main.automaticGenerationDuringBuild
+        set(value) {
+            main.automaticGenerationDuringBuild = value
+        }
+
+    /**
+     * Controls the global [BaselineProfileVariantConfiguration.baselineProfileOutputDir].
+     * Note that this value is overridden by per variant configurations.
+     */
+    override var baselineProfileOutputDir: String?
+        get() = main.baselineProfileOutputDir
+        set(value) {
+            main.baselineProfileOutputDir = value
+        }
+
+    /**
+     * Controls the global [BaselineProfileVariantConfiguration.mergeIntoMain].
+     * Note that this value is overridden by per variant configurations.
+     */
+    override var mergeIntoMain: Boolean?
+        get() = main.mergeIntoMain
+        set(value) {
+            main.mergeIntoMain = value
+        }
+
+    /**
+     * Applies the global [BaselineProfileVariantConfiguration.filter].
+     * This function is just a shortcut for `baselineProfiles.variants.main.filters { }`
+     */
+    override fun filter(action: FilterRules.() -> (Unit)) = main.filter(action)
+
+    /**
+     * Applies the global [BaselineProfileVariantConfiguration.filter].
+     * This function is just a shortcut for `baselineProfiles.variants.main.filters { }`
+     */
+    override fun filter(action: Action<FilterRules>) = main.filter(action)
+
+    /**
+     * Applies global dependencies for baseline profiles. This has the same effect of defining
+     * a baseline profile dependency in the dependency block. For example:
+     * ```
+     * dependencies {
+     *     baselineProfile(project(":baseline-profile"))
+     * }
+     * ```
+     */
+    override fun from(project: Project, variantName: String?) = main.from(project, variantName)
+
+    fun variants(
+        action: Action<NamedDomainObjectContainer<BaselineProfileVariantConfigurationImpl>>
+    ) {
+        action.execute(variants)
+    }
+
+    fun variants(
+        action: NamedDomainObjectContainer<out BaselineProfileVariantConfigurationImpl>.() -> Unit
+    ) {
+        action.invoke(variants)
+    }
+}
+
+abstract class BaselineProfileVariantConfigurationImpl(val name: String) :
+    BaselineProfileVariantConfiguration {
+
+    internal val filters = FilterRules()
+    internal val dependencies = mutableListOf<Pair<Project, String?>>()
+
+    /**
+     * @inheritDoc
+     */
+    override fun filter(action: FilterRules.() -> (Unit)) = action.invoke(filters)
+
+    /**
+     * @inheritDoc
+     */
+    override fun filter(action: Action<FilterRules>) = action.execute(filters)
+
+    /**
+     * @inheritDoc
+     */
+    override fun from(project: Project, variantName: String?) {
+        dependencies.add(Pair(project, variantName))
+    }
+}
+
+/**
+ * Defines the configuration properties that each variant of a consumer module offers. Note that
+ * also [BaselineProfileConsumerExtension] is an implementation of this interface and it's simply
+ * a proxy to the `main` variant.
+ */
+interface BaselineProfileVariantConfiguration {
+
+    /**
+     * Enables R8 to rewrite the incoming human readable baseline profile rules to account for
+     * synthetics, so they are preserved after optimizations by R8.
+     * TODO: This feature is experimental and currently not working properly.
+     *  https://issuetracker.google.com/issue?id=271172067.
+     */
+    var enableR8BaselineProfileRewrite: Boolean?
+
     /**
      * Specifies whether generated baseline profiles should be stored in the src folder.
      * When this flag is set to true, the generated baseline profiles are stored in
      * `src/<variant>/generated/baselineProfiles`.
      */
-    var saveInSrc = true
+    var saveInSrc: Boolean?
 
     /**
      * Specifies whether baseline profiles should be regenerated when building, for example, during
@@ -51,13 +197,14 @@
      * of building the release build. This including rebuilding the non minified release, running
      * the baseline profile tests and ultimately building the release build.
      */
-    var automaticGenerationDuringBuild = false
+    var automaticGenerationDuringBuild: Boolean?
 
     /**
-     * Specifies the output directory for generated baseline profiles when [saveInSrc] is
-     * `true`. Note that the dir specified here is created in the `src/<variant>/` folder.
+     * Specifies the output directory for generated baseline profiles when
+     * [BaselineProfileVariantConfiguration.saveInSrc] is `true`.
+     * Note that the dir specified here is created in the `src/<variant>/` folder.
      */
-    var baselineProfileOutputDir = "generated/baselineProfiles"
+    var baselineProfileOutputDir: String?
 
     /**
      * Specifies if baseline profile files should be merged into a single one when generating for
@@ -72,16 +219,7 @@
      *  this setting still determines whether the profile included in the built apk or
      *  aar includes all the variant profiles.
      */
-    var mergeIntoMain: Boolean? = null
-
-    /**
-     * Enables R8 to rewrite the incoming human readable baseline profile rules to account for
-     * synthetics, so they are preserved after optimizations by R8.
-     * TODO: This feature is experimental and currently not working properly.
-     *  https://issuetracker.google.com/issue?id=271172067.
-     */
-    @Incubating
-    var enableR8BaselineProfileRewrite = false
+    var mergeIntoMain: Boolean?
 
     /**
      * Specifies a filtering rule to decide which profiles rules should be included in this
@@ -115,24 +253,8 @@
      *          exclude "com.somelibrary.widget.grid.debug.**"
      *     }
      * ```
-     *
-     * Filters also support variants and they can be expressed as follows:
-     * ```
-     *     filter { include "com.somelibrary.*" }
-     *     filter("free") { include "com.somelibrary.*" }
-     *     filter("paid") { include "com.somelibrary.*" }
-     *     filter("release") { include "com.somelibrary.*" }
-     *     filter("freeRelease") { include "com.somelibrary.*" }
-     * ```
-     * Filter block without specifying a variant applies to `main`, i.e. all the variants.
-     * Note that when a variant matches multiple filter blocks, all the filters will be merged.
-     * For example with `filter { ... }`, `filter("free") { ... }` and `filter("release") { ... }`
-     * all the blocks will be evaluated for variant `freeRelease` but only `main` and `release` for
-     * variant `paidRelease`.
      */
-    @JvmOverloads
-    fun filter(variant: String = "main", action: FilterRules.() -> (Unit)) = action
-        .invoke(filterRules.computeIfAbsent(variant) { FilterRules() })
+    fun filter(action: FilterRules.() -> (Unit))
 
     /**
      * Specifies a filtering rule to decide which profiles rules should be included in this
@@ -166,30 +288,46 @@
      *          exclude "com.somelibrary.widget.text.debug.**"
      *     }
      * ```
-     *
-     * Filters also support variants and they can be expressed as follows:
-     * ```
-     *     filter { include "com.somelibrary.*" }
-     *     filter("free") { include "com.somelibrary.*" }
-     *     filter("paid") { include "com.somelibrary.*" }
-     *     filter("release") { include "com.somelibrary.*" }
-     *     filter("freeRelease") { include "com.somelibrary.*" }
-     * ```
-     * Filter block without specifying a variant applies to `main`, i.e. all the variants.
-     * Note that when a variant matches multiple filter blocks, all the filters will be merged.
-     * For example with `filter { ... }`, `filter("free") { ... }` and `filter("release") { ... }`
-     * all the blocks will be evaluated for variant `freeRelease` but only `main` and `release` for
-     * variant `paidRelease`.
      */
-    @JvmOverloads
-    fun filter(variant: String = "main", action: Action<FilterRules>) = action
-        .execute(filterRules.computeIfAbsent(variant) { FilterRules() })
+    fun filter(action: Action<FilterRules>)
 
-    internal val filterRules = mutableMapOf<String, FilterRules>()
+    /**
+     * Allows to specify a target `com.android.test` module that has the `androidx.baselineprofile`
+     * plugin, and that can provide a baseline profile for this module. For example
+     * ```
+     * baselineProfile {
+     *     variants {
+     *         freeRelease {
+     *             from(project(":baseline-profile"))
+     *         }
+     *     }
+     * }
+     * ```
+     */
+    fun from(project: Project) = from(project, null)
+
+    /**
+     * Allows to specify a target `com.android.test` module that has the `androidx.baselineprofile`
+     * plugin, and that can provide a baseline profile for this module. The [variantName] can
+     * directly map to a test variant, to fetch a baseline profile for a different variant.
+     * For example it's possible to use a `paidRelease` baseline profile for `freeRelease` variant.
+     * ```
+     * baselineProfile {
+     *     variants {
+     *         freeRelease {
+     *             from(project(":baseline-profile"), "paidRelease")
+     *         }
+     *     }
+     * }
+     * ```
+     */
+    fun from(project: Project, variantName: String?)
 }
 
 class FilterRules {
+
     internal val rules = mutableListOf<Pair<RuleType, String>>()
+
     fun include(pkg: String) = rules.add(Pair(RuleType.INCLUDE, pkg))
     fun exclude(pkg: String) = rules.add(Pair(RuleType.EXCLUDE, pkg))
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
index 59d35d3..cb98bc2 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPlugin.kt
@@ -16,48 +16,29 @@
 
 package androidx.baselineprofile.gradle.consumer
 
-import androidx.baselineprofile.gradle.attributes.BaselineProfilePluginVersionAttr
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_TARGET_JVM_ENVIRONMENT
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_USAGE_BASELINE_PROFILE
+import androidx.baselineprofile.gradle.configuration.ConfigurationManager
+import androidx.baselineprofile.gradle.consumer.task.MainGenerateBaselineProfileTask
+import androidx.baselineprofile.gradle.consumer.task.MergeBaselineProfileTask
+import androidx.baselineprofile.gradle.consumer.task.PrintConfigurationForVariantTask
+import androidx.baselineprofile.gradle.consumer.task.maybeCreateGenerateTask
+import androidx.baselineprofile.gradle.utils.R8Utils
+import androidx.baselineprofile.gradle.utils.AgpPlugin
+import androidx.baselineprofile.gradle.utils.AgpPluginId
 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX
 import androidx.baselineprofile.gradle.utils.CONFIGURATION_NAME_BASELINE_PROFILES
 import androidx.baselineprofile.gradle.utils.INTERMEDIATES_BASE_FOLDER
-import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
-import androidx.baselineprofile.gradle.utils.afterVariants
-import androidx.baselineprofile.gradle.utils.agpVersion
-import androidx.baselineprofile.gradle.utils.agpVersionString
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.RELEASE
 import androidx.baselineprofile.gradle.utils.camelCase
-import androidx.baselineprofile.gradle.utils.checkAgpVersion
-import androidx.baselineprofile.gradle.utils.isGradleSyncRunning
-import androidx.baselineprofile.gradle.utils.maybeRegister
-import com.android.build.api.AndroidPluginVersion
-import com.android.build.api.attributes.AgpVersionAttr
-import com.android.build.api.attributes.BuildTypeAttr
-import com.android.build.api.attributes.ProductFlavorAttr
-import com.android.build.api.variant.AndroidComponentsExtension
-import com.android.build.api.variant.ApplicationAndroidComponentsExtension
-import com.android.build.api.variant.LibraryAndroidComponentsExtension
+import com.android.build.api.dsl.ApplicationExtension
+import com.android.build.api.dsl.LibraryExtension
 import com.android.build.api.variant.Variant
-import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.Task
 import org.gradle.api.artifacts.Configuration
-import org.gradle.api.attributes.Usage
-import org.gradle.api.attributes.java.TargetJvmEnvironment
-import org.gradle.api.file.DirectoryProperty
-import org.gradle.api.provider.Property
-import org.gradle.api.tasks.Input
-import org.gradle.api.tasks.OutputDirectory
-import org.gradle.api.tasks.TaskAction
-import org.gradle.api.tasks.TaskProvider
-import org.gradle.work.DisableCachingByDefault
-
-private const val GENERATE_TASK_NAME = "generate"
-private const val MERGE_TASK_NAME = "merge"
-private const val COPY_TASK_NAME = "copy"
 
 /**
  * This is the consumer plugin for baseline profile generation. In order to generate baseline
@@ -67,557 +48,388 @@
  * test that generate the baseline profile on the device (producer).
  */
 class BaselineProfileConsumerPlugin : Plugin<Project> {
+    override fun apply(project: Project) = BaselineProfileConsumerAgpPlugin(project).onApply()
+}
 
-    companion object {
-        private const val RELEASE = "release"
-        private const val PROPERTY_R8_REWRITE_BASELINE_PROFILE_RULES =
-            "android.experimental.art-profile-r8-rewriting"
+private class BaselineProfileConsumerAgpPlugin(private val project: Project) : AgpPlugin(
+    project = project,
+    supportedAgpPlugins = setOf(
+        AgpPluginId.ID_ANDROID_APPLICATION_PLUGIN,
+        AgpPluginId.ID_ANDROID_LIBRARY_PLUGIN
+    ),
+    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
+    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+) {
+
+    // List of the non debuggable build types
+    private val nonDebuggableBuildTypes = mutableListOf<String>()
+
+    // Offers quick access to configuration extension, hiding the property override and merge logic
+    private val perVariantBaselineProfileExtensionManager =
+        PerVariantConsumerExtensionManager(BaselineProfileConsumerExtension.register(project))
+
+    // Manages creation of configurations
+    private val configurationManager = ConfigurationManager(project)
+
+    // Manages r8 properties
+    private val r8Utils = R8Utils(project)
+
+    // Global baseline profile configuration. Note that created here it can be directly consumed
+    // in the dependencies block.
+    private val mainBaselineProfileConfiguration = configurationManager.maybeCreate(
+        nameParts = listOf(CONFIGURATION_NAME_BASELINE_PROFILES),
+        canBeConsumed = false,
+        canBeResolved = true,
+        buildType = null,
+        productFlavors = null
+    )
+
+    override fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) {
+        throw IllegalStateException(
+            """
+            The module ${project.name} does not have the `com.android.application` or
+            `com.android.library` plugin applied. The `androidx.baselineprofile.consumer`
+            plugin supports only android application and library modules. Please review
+            your build.gradle to ensure this plugin is applied to the correct module.
+            """.trimIndent()
+        )
     }
 
-    override fun apply(project: Project) {
-        var foundAppOrLibraryPlugin = false
-        project.pluginManager.withPlugin("com.android.application") {
-            foundAppOrLibraryPlugin = true
-            configureWithAndroidPlugin(project = project, isApplication = true)
-        }
-        project.pluginManager.withPlugin("com.android.library") {
-            foundAppOrLibraryPlugin = true
-            configureWithAndroidPlugin(project = project, isApplication = false)
-        }
+    override fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) {
+        project.logger.debug(
+            """
+            [BaselineProfileConsumerPlugin] afterEvaluate check: app or library plugin was applied
+            """.trimIndent()
+        )
+    }
 
-        // Only used to verify that the android application plugin has been applied.
-        // Note that we don't want to throw any exception if gradle sync is in progress.
-        project.afterEvaluate {
-            if (!project.isGradleSyncRunning()) {
-                if (!foundAppOrLibraryPlugin) {
-                    throw IllegalStateException(
-                        """
-                    The module ${project.name} does not have the `com.android.application` or
-                    `com.android.library` plugin applied. The `androidx.baselineprofile.consumer`
-                    plugin supports only android application and library modules. Please review
-                    your build.gradle to ensure this plugin is applied to the correct module.
-                    """.trimIndent()
-                    )
-                }
-                project.logger.debug(
-                    """
-                    [BaselineProfileConsumerPlugin] afterEvaluate check: app or library plugin
-                    was applied""".trimIndent()
-                )
-            }
-        }
+    override fun onApplicationFinalizeDsl(extension: ApplicationExtension) {
+
+        // Here we select the build types we want to process if this is an application,
+        // i.e. non debuggable build types that have not been created by the app target plugin.
+        // Also exclude the build types starting with baseline profile prefix, in case the app
+        // target plugin is also applied.
+
+        nonDebuggableBuildTypes.addAll(extension.buildTypes
+            .filter { !it.isDebuggable && !it.name.startsWith(BUILD_TYPE_BASELINE_PROFILE_PREFIX) }
+            .map { it.name }
+        )
+    }
+
+    override fun onLibraryFinalizeDsl(extension: LibraryExtension) {
+
+        // Here we select the build types we want to process if this is a library.
+        // Libraries don't have a `debuggable` flag. Also we don't need to exclude build types
+        // prefixed with the baseline profile prefix. Ideally on the `debug` type should be
+        // excluded.
+
+        nonDebuggableBuildTypes.addAll(extension.buildTypes
+            .filter { it.name != "debug" }
+            .map { it.name }
+        )
     }
 
     @Suppress("UnstableApiUsage")
-    private fun configureWithAndroidPlugin(project: Project, isApplication: Boolean) {
+    override fun onVariants(variant: Variant) {
 
-        // Checks that the required AGP version is applied to this project.
-        project.checkAgpVersion()
+        // Process only the non debuggable build types we previously selected.
+        if (variant.buildType !in nonDebuggableBuildTypes) return
 
-        val baselineProfileExtension =
-            BaselineProfileConsumerExtension.registerExtension(project)
+        // This allows quick access to this variant configuration according to the override
+        // and merge rules implemented in the PerVariantConsumerExtensionManager.
+        val variantConfiguration = perVariantBaselineProfileExtensionManager.variant(variant)
 
-        // Creates the main baseline profile configuration
-        val mainBaselineProfileConfiguration = createBaselineProfileConfigurationForVariant(
-            project,
-            productFlavors = listOf(),
-            variantName = "",
-            flavorName = "",
-            buildTypeName = "",
-            mainConfiguration = null
+        // For test only: this registers a print task with the configuration of the variant.
+        PrintConfigurationForVariantTask.registerForVariant(
+            project = project,
+            variant = variant,
+            variantConfig = variantConfiguration
         )
 
-        // Here we select the build types we want to process, i.e. non debuggable build types that
-        // have not been created by the app target plugin. Variants are used to create
-        // per-variant configurations, tasks and configured for baseline profiles src sets.
-        val nonDebuggableBuildTypes = mutableListOf<String>()
-
-        // This extension exists only if the module is an application.
-        project
-            .extensions
-            .findByType(ApplicationAndroidComponentsExtension::class.java)
-            ?.finalizeDsl { ext ->
-                nonDebuggableBuildTypes.addAll(ext.buildTypes
-                    .filter {
-
-                        // We want to enable baseline profile generation only for non-debuggable
-                        // build types. Additionally we exclude the ones we may have created in the
-                        // app target plugin if this is also applied to this module.
-                        !it.isDebuggable && !it.name.startsWith(
-                            BUILD_TYPE_BASELINE_PROFILE_PREFIX
-                        )
-                    }
-                    .map { it.name }
-                )
-            }
-
-        // This extension exists only if the module is a library.
-        project
-            .extensions
-            .findByType(LibraryAndroidComponentsExtension::class.java)
-            ?.finalizeDsl { ext ->
-                nonDebuggableBuildTypes.addAll(ext.buildTypes
-                    .filter {
-
-                        // Note that library build types don't have a `debuggable` flag so we'll
-                        // just exclude the one named `debug`. Note that we don't need to filter
-                        // for baseline profile build type if this is a library, since the apk
-                        // provider cannot be applied.
-                        it.name != "debug"
-                    }
-                    .map { it.name })
-            }
-
-        // A list of blocks to execute after agp tasks have been created
-        val afterVariantBlocks = mutableListOf<() -> (Unit)>()
-
-        // Iterate baseline profile variants to create per-variant tasks and configurations
-        project
-            .extensions
-            .getByType(AndroidComponentsExtension::class.java)
-            .apply {
-                onVariants { variant ->
-
-                    if (variant.buildType !in nonDebuggableBuildTypes) return@onVariants
-
-                    // Sets the r8 rewrite baseline profile for the non debuggable variant.
-                    if (baselineProfileExtension.enableR8BaselineProfileRewrite &&
-                        project.agpVersion() >= AndroidPluginVersion(8, 0, 0).beta(2)
-                    ) {
-                        // TODO: Note that currently there needs to be at least a baseline profile,
-                        //  even if empty. For this reason we always add a src set that points to
-                        //  an empty file. This can removed after b/271158087 is fixed.
-                        GenerateDummyBaselineProfileTask.setupForVariant(project, variant)
-                        @Suppress("UnstableApiUsage")
-                        variant.experimentalProperties.put(
-                            PROPERTY_R8_REWRITE_BASELINE_PROFILE_RULES,
-                            baselineProfileExtension.enableR8BaselineProfileRewrite
-                        )
-                    }
-
-                    // Creates the configuration to carry the specific variant artifact
-                    val baselineProfileConfiguration =
-                        createBaselineProfileConfigurationForVariant(
-                            project,
-                            variantName = variant.name,
-                            productFlavors = variant.productFlavors,
-                            flavorName = variant.flavorName ?: "",
-                            buildTypeName = variant.buildType ?: "",
-                            mainConfiguration = mainBaselineProfileConfiguration
-                        )
-
-                    // There are 2 different ways in which the output task can merge the baseline
-                    // profile rules, according to [BaselineProfileConsumerExtension#mergeIntoMain].
-                    // When mergeIntoMain is `true` the first variant will create a task shared across
-                    // all the variants to merge, while the next variants will simply add the additional
-                    // baseline profile artifacts, modifying the existing task.
-                    // When mergeIntoMain is `false` each variants has its own task with a single
-                    // artifact per task, specific for that variant.
-                    // When mergeIntoMain is not specified, it's by default true for libraries and false
-                    // for apps.
-                    val mergeIntoMain = baselineProfileExtension.mergeIntoMain ?: !isApplication
-
-                    // TODO: When `mergeIntoMain` is true it lazily triggers the generation of all
-                    //  the variants for all the build types. Due to b/265438201, that fails when
-                    //  there are multiple build types. As temporary workaround, when `mergeIntoMain`
-                    //  is true, calling a generation task for a specific build type will merge
-                    //  profiles for all the variants of that build type and output it in the `main`
-                    //  folder.
-                    val (mergeAwareVariantName, mergeAwareVariantOutput) = if (mergeIntoMain) {
-                        listOf(variant.buildType ?: "", "main")
-                    } else {
-                        listOf(variant.name, variant.name)
-                    }
-
-                    // Creates the task to merge the baseline profile artifacts coming from
-                    // different configurations.
-                    val mergedTaskOutputDir = project
-                        .layout
-                        .buildDirectory
-                        .dir("$INTERMEDIATES_BASE_FOLDER/$mergeAwareVariantOutput/merged")
-
-                    val mergeTaskProvider = project
-                        .tasks
-                        .maybeRegister<MergeBaselineProfileTask>(
-                            MERGE_TASK_NAME, mergeAwareVariantName, TASK_NAME_SUFFIX,
-                        ) { task ->
-
-                            // Sets whether or not baseline profile dependencies have been set.
-                            // If they haven't, the task will fail at execution time.
-                            task.hasDependencies.set(
-                                baselineProfileConfiguration.allDependencies.isNotEmpty()
-                            )
-
-                            // Sets the name of this variant to print it in error messages.
-                            task.variantName.set(mergeAwareVariantName)
-
-                            // These are all the configurations this task depends on,
-                            // in order to consume their artifacts. Note that if this task already
-                            // exist (for example if `merge` is `all`) the new artifact will be
-                            // added to the existing list.
-                            task.baselineProfileFileCollection
-                                .from
-                                .add(baselineProfileConfiguration)
-
-                            // This is the task output for the generated baseline profile. Output
-                            // is always stored in the intermediates
-                            task.baselineProfileDir.set(mergedTaskOutputDir)
-
-                            // Sets the package filter rules. If this is the first task
-                            task.filterRules.addAll(
-                                baselineProfileExtension.filterRules
-                                    .filter {
-                                        it.key in listOfNotNull(
-                                            "main",
-                                            variant.flavorName,
-                                            variant.buildType,
-                                            variant.name
-                                        )
-                                    }
-                                    .flatMap { it.value.rules }
-                            )
-                        }
-
-                    // If `saveInSrc` is true, we create an additional task to copy the output
-                    // of the merge task in the src folder.
-                    val lastTaskProvider = if (baselineProfileExtension.saveInSrc) {
-
-                        val baselineProfileOutputDir =
-                            baselineProfileExtension.baselineProfileOutputDir
-                        val srcOutputDir = project
-                            .layout
-                            .projectDirectory
-                            .dir("src/$mergeAwareVariantOutput/$baselineProfileOutputDir/")
-
-                        // This task copies the baseline profile generated from the merge task.
-                        // Note that we're reutilizing the [MergeBaselineProfileTask] because
-                        // if the flag `mergeIntoMain` is true tasks will have the same name
-                        // and we just want to add more file to copy to the same output. This is
-                        // already handled in the MergeBaselineProfileTask.
-                        val copyTaskProvider = project
-                            .tasks
-                            .maybeRegister<MergeBaselineProfileTask>(
-                                COPY_TASK_NAME, mergeAwareVariantName, "baselineProfileIntoSrc",
-                            ) { task ->
-                                task.baselineProfileFileCollection
-                                    .from
-                                    .add(mergeTaskProvider.flatMap { it.baselineProfileDir })
-                                task.baselineProfileDir.set(srcOutputDir)
-                            }
-
-                        // Applies the source path for this variant
-                        srcOutputDir.asFile.apply {
-                            mkdirs()
-                            variant
-                                .sources
-                                .baselineProfiles?.addStaticSourceDirectory(absolutePath)
-                        }
-
-                        // If this is an application, we need to ensure that:
-                        // If `automaticGenerationDuringBuild` is true, building a release build
-                        // should trigger the generation of the profile. This is done through a
-                        // dependsOn rule.
-                        // If `automaticGenerationDuringBuild` is false and the user calls both
-                        // tasks to generate and assemble, assembling the release should wait of the
-                        // generation to be completed. This is done through a `mustRunAfter` rule.
-                        // Depending on whether the flag `automaticGenerationDuringBuild` is enabled
-                        // Note that we cannot use the variant src set api
-                        // `addGeneratedSourceDirectory` since that overwrites the outputDir,
-                        // that would be re-set in the build dir.
-                        // Also this is specific for applications: doing this for a library would
-                        // trigger a circular task dependency since the library would require
-                        // the profile in order to build the aar for the sample app and generate
-                        // the profile.
-                        if (isApplication) {
-                            afterVariantBlocks.add {
-                                project
-                                    .tasks
-                                    .named(camelCase("merge", variant.name, "artProfile"))
-                                    .configure {
-                                        // Sets the task dependency according to the configuration
-                                        // flag.
-                                        if (baselineProfileExtension
-                                                .automaticGenerationDuringBuild
-                                        ) {
-                                            it.dependsOn(copyTaskProvider)
-                                        } else {
-                                            it.mustRunAfter(copyTaskProvider)
-                                        }
-                                    }
-                            }
-                        }
-
-                        // In this case the last task is the copy task.
-                        copyTaskProvider
-                    } else {
-
-                        if (baselineProfileExtension.automaticGenerationDuringBuild) {
-
-                            // If the flag `automaticGenerationDuringBuild` is true, we can set the
-                            // merge task to provide generated sources for the variant, using the
-                            // src set variant api. This means that we don't need to manually depend
-                            // on the merge or prepare art profile task.
-                            variant
-                                .sources
-                                .baselineProfiles?.addGeneratedSourceDirectory(
-                                    taskProvider = mergeTaskProvider,
-                                    wiredWith = MergeBaselineProfileTask::baselineProfileDir
-                                )
-                        } else {
-
-                            // This is the case of `saveInSrc` and `automaticGenerationDuringBuild`
-                            // both false, that is unsupported. In this case we simply throw an
-                            // error.
-                            if (!project.isGradleSyncRunning()) {
-                                throw GradleException(
-                                    """
-                                    The current configuration of flags `saveInSrc` and
-                                    `automaticGenerationDuringBuild` is not supported. At least
-                                    one of these should be set to `true`. Please review your
-                                    baseline profile plugin configuration in your build.gradle.
-                                """.trimIndent()
-                                )
-                            }
-                        }
-
-                        // In this case the last task is the merge task.
-                        mergeTaskProvider
-                    }
-
-                    // Here we create the final generate task that triggers the whole generation
-                    // for this variant and all the parent tasks. For this one the child task
-                    // is either copy or merge, depending on the configuration.
-                    val variantGenerateTask = maybeCreateGenerateTask<Task>(
-                        project = project,
-                        variantName = mergeAwareVariantName,
-                        childGenerationTaskProvider = lastTaskProvider
-                    )
-
-                    // Create the build type task. For example `generateReleaseBaselineProfile`
-                    // The variant name is equal to the build type name if there are no flavors.
-                    // Note that if `mergeIntoMain` is `true` the build type task already exists.
-                    if (!mergeIntoMain &&
-                        !variant.buildType.isNullOrBlank() &&
-                        variant.name != variant.buildType
-                    ) {
-                        maybeCreateGenerateTask<Task>(
-                            project = project,
-                            variantName = variant.buildType!!,
-                            childGenerationTaskProvider = variantGenerateTask
-                        )
-                    }
-
-                    // TODO: Due to b/265438201 we cannot have a global task
-                    //  `generateBaselineProfile` that triggers generation for all the
-                    //  variants when there are multiple build types. The temporary workaround
-                    //  is to generate baseline profiles only for variants with the `release`
-                    //  build type until that bug is fixed, when running the global task
-                    //  `generateBaselineProfile`. This can be removed after fix.
-                    if (variant.buildType == RELEASE) {
-                        maybeCreateGenerateTask<MainGenerateBaselineProfileTask>(
-                            project,
-                            "",
-                            variantGenerateTask
-                        )
-                    }
-                }
-            }
-
-        // After variants have been resolved the AGP tasks have been created, so we can set our
-        // task dependency if any.
-        project.afterVariants {
-            afterVariantBlocks.forEach { it() }
+        // Sets the r8 rewrite baseline profile for the non debuggable variant.
+        if (variantConfiguration.enableR8BaselineProfileRewrite) {
+            r8Utils.enableR8RulesRewriteForVariant(variant)
         }
-    }
 
-    private inline fun <reified T : Task> maybeCreateGenerateTask(
-        project: Project,
-        variantName: String,
-        childGenerationTaskProvider: TaskProvider<*>? = null
-    ) = project.tasks.maybeRegister<T>(GENERATE_TASK_NAME, variantName, TASK_NAME_SUFFIX) {
-        it.group = "Baseline Profile"
-        it.description = "Generates a baseline profile for the specified variants or dimensions."
-        if (childGenerationTaskProvider != null) it.dependsOn(childGenerationTaskProvider)
-    }
+        // Check if this variant has any direct dependency
+        val variantDependencies = variantConfiguration.dependencies
 
-    private fun createBaselineProfileConfigurationForVariant(
-        project: Project,
-        variantName: String,
-        productFlavors: List<Pair<String, String>>,
-        flavorName: String,
-        buildTypeName: String,
-        mainConfiguration: Configuration?
-    ): Configuration {
+        // Creates the configuration to carry the specific variant artifact
+        val baselineProfileConfiguration = createConfigurationForVariant(
+            variant = variant,
+            mainConfiguration = mainBaselineProfileConfiguration,
+            hasDirectConfiguration = variantDependencies.any { it.second != null }
+        )
 
-        val buildTypeConfiguration =
-            if (buildTypeName.isNotBlank() && buildTypeName != variantName) {
-                project
-                    .configurations
-                    .maybeCreate(camelCase(buildTypeName, CONFIGURATION_NAME_BASELINE_PROFILES))
-                    .apply {
-                        if (mainConfiguration != null) extendsFrom(mainConfiguration)
-                        isCanBeResolved = true
-                        isCanBeConsumed = false
-                    }
-            } else null
-
-        val flavorConfiguration = if (flavorName.isNotBlank() && flavorName != variantName) {
-            project
-                .configurations
-                .maybeCreate(camelCase(flavorName, CONFIGURATION_NAME_BASELINE_PROFILES))
-                .apply {
-                    if (mainConfiguration != null) extendsFrom(mainConfiguration)
-                    isCanBeResolved = true
-                    isCanBeConsumed = false
-                }
-        } else null
-
-        return project
-            .configurations
-            .maybeCreate(camelCase(variantName, CONFIGURATION_NAME_BASELINE_PROFILES))
-            .apply {
-
-                // The variant specific configuration always extends from build type and flavor
-                // configurations, when existing.
-                setExtendsFrom(
-                    listOfNotNull(
-                        mainConfiguration,
-                        flavorConfiguration,
-                        buildTypeConfiguration
+        // Adds the custom dependencies for baseline profiles. Note that dependencies
+        // for global, build type, flavor and variant specific are all merged.
+        variantDependencies.forEach {
+            val targetProject = it.first
+            val variantName = it.second
+            val targetProjectDependency = if (variantName != null) {
+                val configurationName = camelCase(
+                    variantName,
+                    CONFIGURATION_NAME_BASELINE_PROFILES
+                )
+                project.dependencies.project(
+                    mutableMapOf(
+                        "path" to targetProject.path,
+                        "configuration" to configurationName
                     )
                 )
+            } else {
+                project.dependencyFactory.create(targetProject)
+            }
+            baselineProfileConfiguration.dependencies.add(targetProjectDependency)
+        }
 
-                isCanBeResolved = true
-                isCanBeConsumed = false
+        // There are 2 different ways in which the output task can merge the baseline
+        // profile rules, according to [BaselineProfileConsumerExtension#mergeIntoMain].
+        // When mergeIntoMain is `true` the first variant will create a task shared across
+        // all the variants to merge, while the next variants will simply add the additional
+        // baseline profile artifacts, modifying the existing task.
+        // When mergeIntoMain is `false` each variants has its own task with a single
+        // artifact per task, specific for that variant.
+        // When mergeIntoMain is not specified, it's by default true for libraries and false
+        // for apps.
+        val mergeIntoMain = variantConfiguration.mergeIntoMain ?: isLibraryModule()
 
-                attributes {
+        // TODO: When `mergeIntoMain` is true it lazily triggers the generation of all
+        //  the variants for all the build types. Due to b/265438201, that fails when
+        //  there are multiple build types. As temporary workaround, when `mergeIntoMain`
+        //  is true, calling a generation task for a specific build type will merge
+        //  profiles for all the variants of that build type and output it in the `main`
+        //  folder.
+        val (mergeAwareVariantName, mergeAwareVariantOutput) = if (mergeIntoMain) {
+            listOf(variant.buildType ?: "", "main")
+        } else {
+            listOf(variant.name, variant.name)
+        }
 
-                    // Main specialized attribute
-                    it.attribute(
-                        Usage.USAGE_ATTRIBUTE,
-                        project.objects.named(
-                            Usage::class.java, ATTRIBUTE_USAGE_BASELINE_PROFILE
-                        )
-                    )
+        // Creates the task to merge the baseline profile artifacts coming from
+        // different configurations.
+        val mergedTaskOutputDir = project
+            .layout
+            .buildDirectory
+            .dir("$INTERMEDIATES_BASE_FOLDER/$mergeAwareVariantOutput/merged")
 
-                    // Build type
-                    it.attribute(
-                        BuildTypeAttr.ATTRIBUTE,
-                        project.objects.named(
-                            BuildTypeAttr::class.java, buildTypeName
-                        )
-                    )
+        val mergeTaskProvider = MergeBaselineProfileTask.maybeRegisterForMerge(
+            project = project,
+            variantName = mergeAwareVariantName,
+            hasDependencies = baselineProfileConfiguration.allDependencies.isNotEmpty(),
+            sourceProfilesFileCollection = baselineProfileConfiguration,
+            outputDir = mergedTaskOutputDir,
+            filterRules = variantConfiguration.filterRules
+        )
 
-                    // Jvm Environment
-                    it.attribute(
-                        TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
-                        project.objects.named(
-                            TargetJvmEnvironment::class.java, ATTRIBUTE_TARGET_JVM_ENVIRONMENT
-                        )
-                    )
+        // If `saveInSrc` is true, we create an additional task to copy the output
+        // of the merge task in the src folder.
+        val lastTaskProvider = if (variantConfiguration.saveInSrc) {
 
-                    // Agp version
-                    it.attribute(
-                        AgpVersionAttr.ATTRIBUTE,
-                        project.objects.named(
-                            AgpVersionAttr::class.java, project.agpVersionString()
-                        )
-                    )
+            val baselineProfileOutputDir = perVariantBaselineProfileExtensionManager
+                .variant(variant)
+                .baselineProfileOutputDir
+            val srcOutputDir = project
+                .layout
+                .projectDirectory
+                .dir("src/$mergeAwareVariantOutput/$baselineProfileOutputDir/")
 
-                    // Baseline Profile Plugin Version
-                    it.attribute(
-                        BaselineProfilePluginVersionAttr.ATTRIBUTE,
-                        project.objects.named(
-                            BaselineProfilePluginVersionAttr::class.java,
-                            ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION
-                        )
-                    )
+            // This task copies the baseline profile generated from the merge task.
+            // Note that we're reutilizing the [MergeBaselineProfileTask] because
+            // if the flag `mergeIntoMain` is true tasks will have the same name
+            // and we just want to add more file to copy to the same output. This is
+            // already handled in the MergeBaselineProfileTask.
+            val copyTaskProvider = MergeBaselineProfileTask.maybeRegisterForCopy(
+                project = project,
+                variantName = mergeAwareVariantName,
+                sourceDir = mergeTaskProvider.flatMap { it.baselineProfileDir },
+                outputDir = project.provider { srcOutputDir },
+            )
 
-                    // Product flavors
-                    productFlavors.forEach { (flavorName, flavorValue) ->
-                        it.attribute(
-                            @Suppress("UnstableApiUsage")
-                            ProductFlavorAttr.of(flavorName),
-                            project.objects.named(
-                                ProductFlavorAttr::class.java, flavorValue
-                            )
-                        )
-                    }
+            // Applies the source path for this variant
+            srcOutputDir.asFile.apply {
+                mkdirs()
+                variant
+                    .sources
+                    .baselineProfiles?.addStaticSourceDirectory(absolutePath)
+            }
+
+            // If this is an application, we need to ensure that:
+            // If `automaticGenerationDuringBuild` is true, building a release build
+            // should trigger the generation of the profile. This is done through a
+            // dependsOn rule.
+            // If `automaticGenerationDuringBuild` is false and the user calls both
+            // tasks to generate and assemble, assembling the release should wait of the
+            // generation to be completed. This is done through a `mustRunAfter` rule.
+            // Depending on whether the flag `automaticGenerationDuringBuild` is enabled
+            // Note that we cannot use the variant src set api
+            // `addGeneratedSourceDirectory` since that overwrites the outputDir,
+            // that would be re-set in the build dir.
+            // Also this is specific for applications: doing this for a library would
+            // trigger a circular task dependency since the library would require
+            // the profile in order to build the aar for the sample app and generate
+            // the profile.
+            if (isApplicationModule()) {
+                afterVariants {
+                    project
+                        .tasks
+                        .named(camelCase("merge", variant.name, "artProfile"))
+                        .configure {
+                            // Sets the task dependency according to the configuration
+                            // flag.
+                            val automaticGeneration = perVariantBaselineProfileExtensionManager
+                                .variant(variant)
+                                .automaticGenerationDuringBuild
+
+                            // TODO: this causes a circular task dependency when the producer points
+                            //  to a consumer that does not have the appTarget plugin. (b/272851616)
+                            if (automaticGeneration) {
+                                it.dependsOn(copyTaskProvider)
+                            } else {
+                                it.mustRunAfter(copyTaskProvider)
+                            }
+                        }
                 }
             }
-    }
-}
 
-@DisableCachingByDefault(because = "Not worth caching.")
-abstract class MainGenerateBaselineProfileTask : DefaultTask() {
+            // In this case the last task is the copy task.
+            copyTaskProvider
+        } else {
 
-    init {
-        group = "Baseline Profile"
-        description = "Generates a baseline profile"
-    }
-
-    @TaskAction
-    fun exec() {
-        this.logger.warn(
-            """
-                The task `generateBaselineProfile` cannot currently support
-                generation for all the variants when there are multiple build
-                types without improvements planned for a future version of the
-                Android Gradle Plugin.
-                Until then, `generateBaselineProfile` will only generate
-                baseline profiles for the variants of the release build type,
-                behaving like `generateReleaseBaselineProfile`.
-                If you intend to generate profiles for multiple build types
-                you'll need to run separate gradle commands for each build type.
-                For example: `generateReleaseBaselineProfile` and
-                `generateAnotherReleaseBaselineProfile`.
-
-                Details on https://issuetracker.google.com/issue?id=270433400.
-                """.trimIndent()
-        )
-    }
-}
-
-@DisableCachingByDefault(because = "Not worth caching.")
-abstract class GenerateDummyBaselineProfileTask : DefaultTask() {
-
-    companion object {
-        fun setupForVariant(
-            project: Project,
-            variant: Variant
-        ) {
-            val taskProvider = project
-                .tasks
-                .maybeRegister<GenerateDummyBaselineProfileTask>(
-                    "generate", variant.name, "profileForR8RuleRewrite"
-                ) {
-                    it.outputDir.set(
-                        project
-                            .layout
-                            .buildDirectory
-                            .dir("$INTERMEDIATES_BASE_FOLDER/${variant.name}/empty/")
+            if (variantConfiguration.automaticGenerationDuringBuild) {
+                // If the flag `automaticGenerationDuringBuild` is true, we can set the
+                // merge task to provide generated sources for the variant, using the
+                // src set variant api. This means that we don't need to manually depend
+                // on the merge or prepare art profile task.
+                variant
+                    .sources
+                    .baselineProfiles?.addGeneratedSourceDirectory(
+                        taskProvider = mergeTaskProvider,
+                        wiredWith = MergeBaselineProfileTask::baselineProfileDir
                     )
-                    it.variantName.set(variant.name)
+            } else {
+
+                // This is the case of `saveInSrc` and `automaticGenerationDuringBuild`
+                // both false, that is unsupported. In this case we simply throw an
+                // error.
+                if (!isGradleSyncRunning()) {
+                    throw GradleException(
+                        """
+                The current configuration of flags `saveInSrc` and `automaticGenerationDuringBuild`
+                is not supported. At least one of these should be set to `true`. Please review your
+                baseline profile plugin configuration in your build.gradle.
+                    """.trimIndent()
+                    )
                 }
-            @Suppress("UnstableApiUsage")
-            variant.sources.baselineProfiles?.addGeneratedSourceDirectory(
-                taskProvider, GenerateDummyBaselineProfileTask::outputDir
+            }
+
+            // In this case the last task is the merge task.
+            mergeTaskProvider
+        }
+
+        // Here we create the final generate task that triggers the whole generation
+        // for this variant and all the parent tasks. For this one the child task
+        // is either copy or merge, depending on the configuration.
+        val variantGenerateTask = maybeCreateGenerateTask<Task>(
+            project = project,
+            variantName = mergeAwareVariantName,
+            childGenerationTaskProvider = lastTaskProvider
+        )
+
+        // Create the build type task. For example `generateReleaseBaselineProfile`
+        // The variant name is equal to the build type name if there are no flavors.
+        // Note that if `mergeIntoMain` is `true` the build type task already exists.
+        if (!mergeIntoMain &&
+            !variant.buildType.isNullOrBlank() &&
+            variant.name != variant.buildType
+        ) {
+            maybeCreateGenerateTask<Task>(
+                project = project,
+                variantName = variant.buildType!!,
+                childGenerationTaskProvider = variantGenerateTask
+            )
+        }
+
+        // TODO: Due to b/265438201 we cannot have a global task
+        //  `generateBaselineProfile` that triggers generation for all the
+        //  variants when there are multiple build types. The temporary workaround
+        //  is to generate baseline profiles only for variants with the `release`
+        //  build type until that bug is fixed, when running the global task
+        //  `generateBaselineProfile`. This can be removed after fix.
+        if (variant.buildType == RELEASE) {
+            maybeCreateGenerateTask<MainGenerateBaselineProfileTask>(
+                project,
+                "",
+                variantGenerateTask
             )
         }
     }
 
-    @get:OutputDirectory
-    abstract val outputDir: DirectoryProperty
+    private fun createConfigurationForVariant(
+        variant: Variant,
+        mainConfiguration: Configuration?,
+        hasDirectConfiguration: Boolean
+    ): Configuration {
 
-    @get:Input
-    abstract val variantName: Property<String>
+        val variantName = variant.name
+        val productFlavors = variant.productFlavors
+        val flavorName = variant.flavorName ?: ""
+        val buildTypeName = variant.buildType ?: ""
 
-    @TaskAction
-    fun exec() {
-        outputDir
-            .file("empty-baseline-prof.txt")
-            .get()
-            .asFile
-            .writeText("Lignore/This;")
+        val buildTypeConfiguration =
+            if (buildTypeName.isNotBlank() && buildTypeName != variantName) {
+                configurationManager.maybeCreate(
+                    nameParts = listOf(buildTypeName, CONFIGURATION_NAME_BASELINE_PROFILES),
+                    canBeResolved = true,
+                    canBeConsumed = false,
+                    buildType = null,
+                    productFlavors = null,
+                    extendFromConfigurations = listOfNotNull(mainConfiguration)
+                )
+            } else null
+
+        val flavorConfiguration =
+            if (flavorName.isNotBlank() && flavorName != variantName) {
+                configurationManager.maybeCreate(
+                    nameParts = listOf(flavorName, CONFIGURATION_NAME_BASELINE_PROFILES),
+                    canBeResolved = true,
+                    canBeConsumed = false,
+                    buildType = null,
+                    productFlavors = null,
+                    extendFromConfigurations = listOfNotNull(mainConfiguration)
+                )
+            } else null
+
+        // When there is direct configuration for the dependency the matching through attributes
+        // is bypassed, because most likely the user meant to match a configuration that does not
+        // have the same tags (for example to a different flavor or build type).
+
+        return if (hasDirectConfiguration) {
+            configurationManager.maybeCreate(
+                nameParts = listOf(variantName, CONFIGURATION_NAME_BASELINE_PROFILES),
+                canBeResolved = true,
+                canBeConsumed = false,
+                extendFromConfigurations = listOfNotNull(
+                    mainConfiguration,
+                    flavorConfiguration,
+                    buildTypeConfiguration
+                ),
+                buildType = null,
+                productFlavors = null
+            )
+        } else {
+            configurationManager.maybeCreate(
+                nameParts = listOf(variantName, CONFIGURATION_NAME_BASELINE_PROFILES),
+                canBeResolved = true,
+                canBeConsumed = false,
+                extendFromConfigurations = listOfNotNull(
+                    mainConfiguration,
+                    flavorConfiguration,
+                    buildTypeConfiguration
+                ),
+                buildType = buildTypeName,
+                productFlavors = productFlavors
+            )
+        }
     }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt
new file mode 100644
index 0000000..6168f23
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/PerVariantConsumerExtensionManager.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.baselineprofile.gradle.consumer
+
+import com.android.build.api.variant.Variant
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+
+/**
+ * The [BaselineProfileConsumerPlugin] supports per variant configuration, according to values
+ * expressed in [BaselineProfileVariantConfiguration]. The correct value for a property is
+ * determined considering the concept of override or merge.
+ * When a property is evaluated considering the override, the variants are evaluated in this order:
+ * `variantName`, `buildType` or `productFlavor` and `main`. The first variant configuration to
+ * define the property is used to return that property.
+ * For lists only, a property can be evaluated also merging all the variant configurations. This
+ * is the case for dependencies for example, so that when accessing custom dependencies for variant
+ * `freeRelease` the returned list contains the dependencies for `freeRelease`, `free`, `release`
+ * and `main` (global ones).
+ */
+internal class PerVariantConsumerExtensionManager(
+    private val extension: BaselineProfileConsumerExtension,
+) {
+
+    fun variant(variant: Variant) = VariantConfigurationProxy(
+        variant = variant,
+        ext = extension
+    )
+
+    internal class VariantConfigurationProxy internal constructor(
+        private val variant: Variant,
+        private val ext: BaselineProfileConsumerExtension,
+    ) {
+
+        val filterRules: List<Pair<RuleType, String>>
+            get() = getMergedListForVariant(variant) { filters.rules }
+
+        val dependencies: List<Pair<Project, String?>>
+            get() = getMergedListForVariant(variant) { dependencies }
+
+        val enableR8BaselineProfileRewrite: Boolean
+            get() = getOverriddenValueForVariant(variant) { enableR8BaselineProfileRewrite }
+
+        val saveInSrc: Boolean
+            get() = getOverriddenValueForVariant(variant) { saveInSrc }
+
+        val automaticGenerationDuringBuild: Boolean
+            get() = getOverriddenValueForVariant(variant) { automaticGenerationDuringBuild }
+
+        val baselineProfileOutputDir: String
+            get() = getOverriddenValueForVariant(variant) { baselineProfileOutputDir }
+
+        val mergeIntoMain: Boolean?
+            get() = getOverriddenValueForVariantAllowNull(variant) { mergeIntoMain }
+
+        private fun <T> getMergedListForVariant(
+            variant: Variant,
+            getter: BaselineProfileVariantConfigurationImpl.() -> List<T>
+        ): List<T> {
+            return listOfNotNull("main", variant.flavorName, variant.buildType, variant.name)
+                .mapNotNull { ext.variants.findByName(it) }
+                .map { getter.invoke(it) }
+                .flatten()
+        }
+
+        private fun <T> getOverriddenValueForVariantAllowNull(
+            variant: Variant,
+            getter: BaselineProfileVariantConfigurationImpl.() -> T
+        ): T? {
+            // Here we select a setting for the given variant. [BaselineProfileVariantConfiguration]
+            // are evaluated in the following order: variant, flavor, build type, `main`.
+            // If a property is found it will return it. Note that `main` should have all the
+            // defaults set so this method never returns a nullable value and should always return.
+
+            val definedProperties = listOfNotNull(
+                variant.name,
+                variant.flavorName,
+                variant.buildType,
+                "main"
+            ).mapNotNull {
+                val variantConfig = ext.variants.findByName(it) ?: return@mapNotNull null
+                return@mapNotNull Pair(it, getter.invoke(variantConfig))
+            }.filter { it.second != null }
+
+            // This is a case where the property is defined in both build type and flavor.
+            // In this case it should fail because the result is ambiguous.
+            val propMap = definedProperties.toMap()
+            if (variant.flavorName in propMap &&
+                variant.buildType in propMap &&
+                propMap[variant.flavorName] != propMap[variant.buildType]
+            ) {
+                throw GradleException(
+                    """
+            The per-variant configuration for baseline profiles is ambiguous. This happens when
+            that the same property has been defined in both a build type and a flavor.
+
+            For example:
+
+            baselineProfiles {
+                variants {
+                    free {
+                        saveInSrc = true
+                    }
+                    release {
+                        saveInSrc = false
+                    }
+                }
+            }
+
+            In this case for `freeRelease` it's not possible to determine the exact value of the
+            property. Please specify either the build type or the flavor.
+            """.trimIndent()
+                )
+            }
+
+            return definedProperties.firstOrNull()?.second
+        }
+
+        private fun <T> getOverriddenValueForVariant(
+            variant: Variant,
+            default: T? = null,
+            getter: BaselineProfileVariantConfigurationImpl.() -> T?
+        ): T {
+            val value = getOverriddenValueForVariantAllowNull(variant, getter)
+            if (value != null) return value
+            if (default != null) return default
+
+            // This should never happen. It means the extension is missing a default property and
+            // n default was specified when accessing this value. This cannot happen because of the
+            // user configuration.
+            throw GradleException("The required property does not have a default.")
+        }
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/GenerateBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/GenerateBaselineProfileTask.kt
new file mode 100644
index 0000000..422ba51
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/GenerateBaselineProfileTask.kt
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.consumer.task
+
+import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
+import androidx.baselineprofile.gradle.utils.maybeRegister
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
+import org.gradle.work.DisableCachingByDefault
+
+private const val GENERATE_TASK_NAME = "generate"
+
+/**
+ * Creates the `generate<variant>BaselineProfile` task. Note that this task does nothing on its
+ * own and it's only to start the generation process.
+ */
+internal inline fun <reified T : Task> maybeCreateGenerateTask(
+    project: Project,
+    variantName: String,
+    childGenerationTaskProvider: TaskProvider<*>? = null
+) = project.tasks.maybeRegister<T>(GENERATE_TASK_NAME, variantName, TASK_NAME_SUFFIX) {
+    it.group = "Baseline Profile"
+    it.description = "Generates a baseline profile for the specified variants or dimensions."
+    if (childGenerationTaskProvider != null) it.dependsOn(childGenerationTaskProvider)
+}
+
+@DisableCachingByDefault(because = "Not worth caching.")
+abstract class MainGenerateBaselineProfileTask : DefaultTask() {
+
+    init {
+        group = "Baseline Profile"
+        description = "Generates a baseline profile"
+    }
+
+    @TaskAction
+    fun exec() {
+        this.logger.warn(
+            """
+                The task `generateBaselineProfile` cannot currently support
+                generation for all the variants when there are multiple build
+                types without improvements planned for a future version of the
+                Android Gradle Plugin.
+                Until then, `generateBaselineProfile` will only generate
+                baseline profiles for the variants of the release build type,
+                behaving like `generateReleaseBaselineProfile`.
+                If you intend to generate profiles for multiple build types
+                you'll need to run separate gradle commands for each build type.
+                For example: `generateReleaseBaselineProfile` and
+                `generateAnotherReleaseBaselineProfile`.
+
+                Details on https://issuetracker.google.com/issue?id=270433400.
+                """.trimIndent()
+        )
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/MergeBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
similarity index 74%
rename from benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/MergeBaselineProfileTask.kt
rename to benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
index 020d9ac..2a3c367 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/MergeBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/MergeBaselineProfileTask.kt
@@ -14,14 +14,21 @@
  * limitations under the License.
  */
 
-package androidx.baselineprofile.gradle.consumer
+package androidx.baselineprofile.gradle.consumer.task
 
+import androidx.baselineprofile.gradle.consumer.RuleType
+import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
+import androidx.baselineprofile.gradle.utils.maybeRegister
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
+import org.gradle.api.Project
 import org.gradle.api.file.ConfigurableFileCollection
+import org.gradle.api.file.Directory
 import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.file.FileCollection
 import org.gradle.api.provider.ListProperty
 import org.gradle.api.provider.Property
+import org.gradle.api.provider.Provider
 import org.gradle.api.tasks.CacheableTask
 import org.gradle.api.tasks.Input
 import org.gradle.api.tasks.InputFiles
@@ -30,6 +37,7 @@
 import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
 import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
 
 /**
  * Collects all the baseline profile artifacts generated by all the producer configurations and
@@ -46,8 +54,60 @@
 
     companion object {
 
+        private const val MERGE_TASK_NAME = "merge"
+        private const val COPY_TASK_NAME = "copy"
+
         // The output file for the HRF baseline profile file in `src/main`
         private const val BASELINE_PROFILE_FILENAME = "baseline-prof.txt"
+
+        internal fun maybeRegisterForMerge(
+            project: Project,
+            variantName: String,
+            hasDependencies: Boolean = false,
+            sourceProfilesFileCollection: FileCollection,
+            outputDir: Provider<Directory>,
+            filterRules: List<Pair<RuleType, String>> = listOf(),
+        ): TaskProvider<MergeBaselineProfileTask> {
+            return project
+                .tasks
+                .maybeRegister(MERGE_TASK_NAME, variantName, TASK_NAME_SUFFIX) { task ->
+
+                    // Sets whether or not baseline profile dependencies have been set.
+                    // If they haven't, the task will fail at execution time.
+                    task.hasDependencies.set(hasDependencies)
+
+                    // Sets the name of this variant to print it in error messages.
+                    task.variantName.set(variantName)
+
+                    // These are all the configurations this task depends on,
+                    // in order to consume their artifacts. Note that if this task already
+                    // exist (for example if `merge` is `all`) the new artifact will be
+                    // added to the existing list.
+                    task.baselineProfileFileCollection.from.add(sourceProfilesFileCollection)
+
+                    // This is the task output for the generated baseline profile. Output
+                    // is always stored in the intermediates
+                    task.baselineProfileDir.set(outputDir)
+
+                    // Sets the package filter rules. Note that if this task already exists
+                    // because of a mergeIntoMain rule, rules are added to the existing ones.
+                    task.filterRules.addAll(filterRules)
+                }
+        }
+
+        internal fun maybeRegisterForCopy(
+            project: Project,
+            variantName: String,
+            sourceDir: Provider<Directory>,
+            outputDir: Provider<Directory>,
+        ): TaskProvider<MergeBaselineProfileTask> {
+            return project
+                .tasks
+                .maybeRegister(COPY_TASK_NAME, variantName, "baselineProfileIntoSrc") { task ->
+                    task.baselineProfileFileCollection.from.add(sourceDir)
+                    task.baselineProfileDir.set(outputDir)
+                }
+        }
     }
 
     @get: Input
@@ -175,6 +235,7 @@
     }
 
     private fun Pair<RuleType, String>.isInclude(): Boolean = first == RuleType.INCLUDE
+
     private fun Pair<RuleType, String>.matches(fullClassName: String): Boolean {
         val rule = second
         return when {
@@ -201,32 +262,3 @@
         }
     }
 }
-
-// Implementation from:
-// benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileRule.kt
-private data class ProfileRule(
-    val underlying: String,
-    val flags: String,
-    val classDescriptor: String,
-    val methodDescriptor: String?,
-    val fullClassName: String,
-) {
-    companion object {
-
-        private val PROFILE_RULE_REGEX = "(H?S?P?)L([^;]*);(->)?(.*)".toRegex()
-
-        fun parse(rule: String): ProfileRule? = when (val result = PROFILE_RULE_REGEX.find(rule)) {
-            null -> null
-            else -> {
-                // Ignore `->`
-                val (flags, classDescriptor, _, methodDescriptor) = result.destructured
-                val fullClassName = classDescriptor.split("/").joinToString(".")
-                ProfileRule(rule, flags, classDescriptor, methodDescriptor, fullClassName)
-            }
-        }
-
-        internal val comparator: Comparator<ProfileRule> = compareBy(
-            { it.classDescriptor }, { it.methodDescriptor ?: "" }
-        )
-    }
-}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/PrintConfigurationForVariantTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/PrintConfigurationForVariantTask.kt
new file mode 100644
index 0000000..c9e2d08
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/PrintConfigurationForVariantTask.kt
@@ -0,0 +1,68 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.baselineprofile.gradle.consumer.task
+
+import androidx.baselineprofile.gradle.consumer.PerVariantConsumerExtensionManager
+import androidx.baselineprofile.gradle.utils.maybeRegister
+import com.android.build.api.variant.Variant
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.TaskAction
+import org.gradle.work.DisableCachingByDefault
+
+/**
+ * Only used for testing, this task does not have description or group so that it doesn't show up
+ * in the task list. It prints internal properties to facilitate assertions in integration tests.
+ */
+@DisableCachingByDefault(because = "Not worth caching. Used only for tests.")
+abstract class PrintConfigurationForVariantTask : DefaultTask() {
+
+    companion object {
+
+        private const val TASK_NAME_PREFIX = "printBaselineProfileExtensionForVariant"
+
+        internal fun registerForVariant(
+            project: Project,
+            variant: Variant,
+            variantConfig: PerVariantConsumerExtensionManager.VariantConfigurationProxy
+        ) {
+            project
+                .tasks
+                .maybeRegister<PrintConfigurationForVariantTask>(TASK_NAME_PREFIX, variant.name) {
+                    it.text.set(
+                        """
+                    mergeIntoMain=`${variantConfig.mergeIntoMain}`
+                    baselineProfileOutputDir=`${variantConfig.baselineProfileOutputDir}`
+                    enableR8BaselineProfileRewrite=`${variantConfig.enableR8BaselineProfileRewrite}`
+                    saveInSrc=`${variantConfig.saveInSrc}`
+                    automaticGenerationDuringBuild=`${variantConfig.automaticGenerationDuringBuild}`
+                    """.trimIndent()
+                    )
+                }
+        }
+    }
+
+    @get: Input
+    abstract val text: Property<String>
+
+    @TaskAction
+    fun exec() {
+        logger.warn(text.get())
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/ProfileRule.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/ProfileRule.kt
new file mode 100644
index 0000000..b189232
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/consumer/task/ProfileRule.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.baselineprofile.gradle.consumer.task
+
+// Implementation from:
+// benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/ProfileRule.kt
+internal data class ProfileRule(
+    val underlying: String,
+    val flags: String,
+    val classDescriptor: String,
+    val methodDescriptor: String?,
+    val fullClassName: String,
+) {
+    companion object {
+
+        private val PROFILE_RULE_REGEX = "(H?S?P?)L([^;]*);(->)?(.*)".toRegex()
+
+        fun parse(rule: String): ProfileRule? = when (val result = PROFILE_RULE_REGEX.find(rule)) {
+            null -> null
+            else -> {
+                // Ignore `->`
+                val (flags, classDescriptor, _, methodDescriptor) = result.destructured
+                val fullClassName = classDescriptor.split("/").joinToString(".")
+                ProfileRule(rule, flags, classDescriptor, methodDescriptor, fullClassName)
+            }
+        }
+
+        internal val comparator: Comparator<ProfileRule> = compareBy(
+            { it.classDescriptor }, { it.methodDescriptor ?: "" }
+        )
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerExtension.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerExtension.kt
index 2129e73..df905e3 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerExtension.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerExtension.kt
@@ -28,14 +28,16 @@
 
         private const val EXTENSION_NAME = "baselineProfile"
 
-        internal fun registerExtension(project: Project): BaselineProfileProducerExtension {
+        internal fun register(project: Project): BaselineProfileProducerExtension {
             val ext = project
-                .extensions.findByType(BaselineProfileProducerExtension::class.java)
+                .extensions
+                .findByType(BaselineProfileProducerExtension::class.java)
             if (ext != null) {
                 return ext
             }
             return project
-                .extensions.create(EXTENSION_NAME, BaselineProfileProducerExtension::class.java)
+                .extensions
+                .create(EXTENSION_NAME, BaselineProfileProducerExtension::class.java)
         }
     }
 
@@ -67,10 +69,4 @@
      */
     @Incubating
     var enableEmulatorDisplay = false
-
-    internal val devices by lazy {
-        mutableSetOf<String>()
-            .also { it.addAll(this.managedDevices) }
-            .also { if (this.useConnectedDevices) it.add("connected") }
-    }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
index 6342c02..7a93406 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPlugin.kt
@@ -16,34 +16,29 @@
 
 package androidx.baselineprofile.gradle.producer
 
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_USAGE_BASELINE_PROFILE
-import androidx.baselineprofile.gradle.utils.ATTRIBUTE_TARGET_JVM_ENVIRONMENT
+import androidx.baselineprofile.gradle.configuration.ConfigurationManager
+import androidx.baselineprofile.gradle.producer.tasks.CollectBaselineProfileTask
+import androidx.baselineprofile.gradle.producer.tasks.InstrumentationTestTaskWrapper
+import androidx.baselineprofile.gradle.utils.AgpPlugin
+import androidx.baselineprofile.gradle.utils.AgpPluginId
+import androidx.baselineprofile.gradle.utils.AndroidTestModuleWrapper
 import androidx.baselineprofile.gradle.utils.BUILD_TYPE_BASELINE_PROFILE_PREFIX
-import androidx.baselineprofile.gradle.attributes.BaselineProfilePluginVersionAttr
 import androidx.baselineprofile.gradle.utils.CONFIGURATION_ARTIFACT_TYPE
 import androidx.baselineprofile.gradle.utils.CONFIGURATION_NAME_BASELINE_PROFILES
-import androidx.baselineprofile.gradle.utils.INTERMEDIATES_BASE_FOLDER
-import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
-import androidx.baselineprofile.gradle.utils.agpVersionString
+import androidx.baselineprofile.gradle.utils.MAX_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.MIN_AGP_VERSION_REQUIRED
+import androidx.baselineprofile.gradle.utils.RELEASE
 import androidx.baselineprofile.gradle.utils.camelCase
-import androidx.baselineprofile.gradle.utils.checkAgpVersion
 import androidx.baselineprofile.gradle.utils.createBuildTypeIfNotExists
 import androidx.baselineprofile.gradle.utils.createExtendedBuildTypes
-import androidx.baselineprofile.gradle.utils.isGradleSyncRunning
-import com.android.build.api.attributes.AgpVersionAttr
-import com.android.build.api.attributes.BuildTypeAttr
-import com.android.build.api.attributes.ProductFlavorAttr
 import com.android.build.api.dsl.TestBuildType
-import com.android.build.api.variant.TestAndroidComponentsExtension
-import com.android.build.gradle.TestExtension
+import com.android.build.api.dsl.TestExtension
+import com.android.build.api.variant.TestVariant
+import com.android.build.api.variant.TestVariantBuilder
+import com.android.build.api.variant.Variant
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
-import org.gradle.api.UnknownTaskException
-import org.gradle.api.attributes.Usage
-import org.gradle.api.attributes.java.TargetJvmEnvironment
-import org.gradle.api.file.DirectoryProperty
 
 /**
  * This is the producer plugin for baseline profile generation. In order to generate baseline
@@ -53,310 +48,214 @@
  * test that generate the baseline profile on the device (producer).
  */
 class BaselineProfileProducerPlugin : Plugin<Project> {
+    override fun apply(project: Project) = BaselineProfileProducerAgpPlugin(project).onApply()
+}
 
-    companion object {
-        private const val COLLECT_TASK_NAME = "collect"
-        private const val RELEASE = "release"
+private class BaselineProfileProducerAgpPlugin(private val project: Project) : AgpPlugin(
+    project = project,
+    supportedAgpPlugins = setOf(AgpPluginId.ID_ANDROID_TEST_PLUGIN),
+    minAgpVersion = MIN_AGP_VERSION_REQUIRED,
+    maxAgpVersion = MAX_AGP_VERSION_REQUIRED
+) {
+
+    private val baselineProfileExtension = BaselineProfileProducerExtension.register(project)
+    private val configurationManager = ConfigurationManager(project)
+
+    // This maps all the extended build types to the original ones. Note that release does not
+    // exist by default so we need to create nonMinifiedRelease and map it manually to `release`.
+    private val nonObfuscatedReleaseName =
+        camelCase(BUILD_TYPE_BASELINE_PROFILE_PREFIX, RELEASE)
+    private val extendedTypeToOriginalTypeMapping =
+        mutableMapOf(nonObfuscatedReleaseName to RELEASE)
+
+    override fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) {
+        project
+            .logger
+            .debug("[BaselineProfileProducerPlugin] afterEvaluate check: app plugin was applied")
     }
 
-    override fun apply(project: Project) {
-        var foundTestPlugin = false
-        project.pluginManager.withPlugin("com.android.test") {
-            foundTestPlugin = true
-            configureWithAndroidPlugin(project = project)
-        }
-
-        // Only used to verify that the android test plugin has been applied.
-        project.afterEvaluate {
-            if (!project.isGradleSyncRunning()) {
-                if (!foundTestPlugin) {
-                    throw IllegalStateException(
-                        """
-                    The module ${project.name} does not have the `com.android.test` plugin
-                    applied. The `androidx.baselineprofile.producer` plugin supports only android
-                    test modules. Please review your build.gradle to ensure this plugin is applied
-                    to the correct module.
-                    """.trimIndent()
-                    )
-                }
-                project.logger.debug(
-                    "[BaselineProfileProducerPlugin] afterEvaluate check: app plugin was applied"
-                )
-            }
-        }
-    }
-
-    private fun configureWithAndroidPlugin(project: Project) {
-
-        // Checks that the required AGP version is applied to this project.
-        project.checkAgpVersion()
-
-        // Prepares extensions used by the plugin
-        val baselineProfileExtension = BaselineProfileProducerExtension.registerExtension(project)
-
-        val testAndroidComponent = project.extensions.getByType(
-            TestAndroidComponentsExtension::class.java
+    override fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) {
+        throw IllegalStateException(
+            """
+    The module ${project.name} does not have the `com.android.test` plugin applied. Currently,
+    the `androidx.baselineprofile.producer` plugin supports only android test modules. In future this
+    plugin will also support library modules (https://issuetracker.google.com/issue?id=259737450).
+    Please review your build.gradle to ensure this plugin is applied to the correct module.
+            """.trimIndent()
         )
+    }
+
+    override fun onBeforeFinalizeDsl() {
 
         // We need the instrumentation apk to run as a separate process
-        val testExtension = project.extensions.getByType(TestExtension::class.java)
-        @Suppress("UnstableApiUsage")
-        testExtension.experimentalProperties["android.experimental.self-instrumenting"] = true
+        AndroidTestModuleWrapper(project).setSelfInstrumenting(true)
+    }
 
-        // Creates the new build types to match the app target. Note that release does not
-        // exist by default so we need to create nonMinifiedRelease and map it manually to
-        // `release`. All the existing build types beside `debug`, that is the default one, are
-        // added manually in the configuration so we can assume they've been added for the purpose
-        // of generating baseline profiles. We don't need to create a nonMinified build type from
-        // `debug` since there will be no matching configuration with the app target module.
+    override fun onTestFinalizeDsl(extension: TestExtension) {
 
-        val nonObfuscatedReleaseName = camelCase(BUILD_TYPE_BASELINE_PROFILE_PREFIX, RELEASE)
-        val extendedTypeToOriginalTypeMapping = mutableMapOf(nonObfuscatedReleaseName to RELEASE)
+        // Creates the new build types to match the app target. All the existing build types beside
+        // `debug`, that is the default one, are added manually in the configuration so we can
+        // assume they've been added for the purpose of generating baseline profiles. We don't
+        // need to create a nonMinified build type from `debug` since there will be no matching
+        // configuration with the app target module.
 
-        testAndroidComponent.finalizeDsl { ext ->
+        // The test build types need to be debuggable and have the same singing config key to
+        // be installed. We also disable the test coverage tracking since it's not important
+        // here.
+        val configureBlock: TestBuildType.() -> (Unit) = {
+            isDebuggable = true
+            enableAndroidTestCoverage = false
+            enableUnitTestCoverage = false
+            signingConfig = extension.buildTypes.getByName("debug").signingConfig
 
-            // The test build types need to be debuggable and have the same singing config key to
-            // be installed. We also disable the test coverage tracking since it's not important
-            // here.
-            val configureBlock: TestBuildType.() -> (Unit) = {
-                isDebuggable = true
-                enableAndroidTestCoverage = false
-                enableUnitTestCoverage = false
-                signingConfig = ext.buildTypes.getByName("debug").signingConfig
-                matchingFallbacks += listOf(RELEASE)
-            }
-
-            // The variant names are used by the test module to request a specific apk artifact to
-            // the under test app module (using configuration attributes). This is all handled by
-            // the com.android.test plugin, as long as both modules have the same variants.
-            // Unfortunately the test module cannot determine which variants are present in the
-            // under test app module. As a result we need to replicate the same build types and
-            // flavors, so that the same variant names are created.
-            createExtendedBuildTypes(
-                project = project,
-                extension = ext,
-                newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
-                extendedBuildTypeToOriginalBuildTypeMapping = extendedTypeToOriginalTypeMapping,
-                configureBlock = configureBlock,
-                filterBlock = {
-                    // All the build types that have been added to the test module should be
-                    // extended. This is because we can't know here which ones are actually
-                    // release in the under test module. We can only exclude debug for sure.
-                    it.name != "debug"
-                }
-            )
-            createBuildTypeIfNotExists(
-                project = project,
-                extension = ext,
-                buildTypeName = nonObfuscatedReleaseName,
-                configureBlock = configureBlock
-            )
+            // TODO: The matching fallback is causing a circular dependency when app target plugin
+            //  is not applied. Normally this is not used, but if an app defines only the consumer
+            //  plugin the `nonMinified` build won't exist. If the provider points to it, the
+            //  matching fallback will kick in and will use the `release` build instead of the
+            //  `nonMinifiedRelease`. When this happens, since  we depend on
+            //  `mergeArtReleaseProfile` to ensure that a profile is copied is the baseline profile
+            //  src sets before the build is complete, it will trigger a circular task dependency:
+            //      collectNonMinifiedReleaseBaselineProfile ->
+            //          connectedNonMinifiedReleaseAndroidTest ->
+            //              packageRelease ->
+            //                  compileReleaseArtProfile ->
+            //                      mergeReleaseArtProfile ->
+            //                          copyReleaseBaselineProfileIntoSrc ->
+            //                              mergeReleaseBaselineProfile ->
+            //                                  collectNonMinifiedReleaseBaselineProfile
+            //  Note that the error is expected but we should handle it gracefully with proper
+            //  explanation. (b/272851616)
+            matchingFallbacks += listOf(RELEASE)
         }
 
+        // The variant names are used by the test module to request a specific apk artifact to
+        // the under test app module (using configuration attributes). This is all handled by
+        // the com.android.test plugin, as long as both modules have the same variants.
+        // Unfortunately the test module cannot determine which variants are present in the
+        // under test app module. As a result we need to replicate the same build types and
+        // flavors, so that the same variant names are created.
+        createExtendedBuildTypes(
+            project = project,
+            extension = extension,
+            newBuildTypePrefix = BUILD_TYPE_BASELINE_PROFILE_PREFIX,
+            extendedBuildTypeToOriginalBuildTypeMapping = extendedTypeToOriginalTypeMapping,
+            configureBlock = configureBlock,
+            filterBlock = {
+                // All the build types that have been added to the test module should be
+                // extended. This is because we can't know here which ones are actually
+                // release in the under test module. We can only exclude debug for sure.
+                it.name != "debug"
+            }
+        )
+        createBuildTypeIfNotExists(
+            project = project,
+            extension = extension,
+            buildTypeName = nonObfuscatedReleaseName,
+            configureBlock = configureBlock
+        )
+    }
+
+    override fun onTestBeforeVariants(variantBuilder: TestVariantBuilder) {
+
         // Makes sure that only the non obfuscated build type variant selected is enabled
-        testAndroidComponent.apply {
-            beforeVariants {
-                it.enable = it.buildType in extendedTypeToOriginalTypeMapping.keys
-            }
-        }
+        variantBuilder.enable = variantBuilder.buildType in extendedTypeToOriginalTypeMapping.keys
+    }
+
+    override fun onTestVariants(variant: TestVariant) {
 
         // Creates all the configurations, one per variant for the newly created build type.
         // Note that for this version of the plugin is not possible to rely entirely on the variant
         // api so the actual creation of the tasks is postponed to be executed when all the
         // agp tasks have been created, using the old api.
-        val createTaskBlocks = mutableListOf<() -> (Unit)>()
-        testAndroidComponent.apply {
 
-            onVariants {
-
-                // Creating configurations only for the extended build types.
-                if (it.buildType == null ||
-                    it.buildType !in extendedTypeToOriginalTypeMapping.keys
-                ) {
-                    return@onVariants
-                }
-
-                // Creates the configuration to handle this variant. Note that in the attributes
-                // to match the configuration we use the original build type without `nonObfuscated`.
-                val originalBuildTypeName = extendedTypeToOriginalTypeMapping[it.buildType] ?: ""
-                val configurationName = createBaselineProfileConfigurationForVariant(
-                    project = project,
-                    variantName = it.name,
-                    productFlavors = it.productFlavors,
-                    originalBuildTypeName = originalBuildTypeName
-                )
-
-                // Prepares a block to execute later that creates the tasks for this variant
-                createTaskBlocks.add {
-                    createTasksForVariant(
-                        project = project,
-                        variantName = it.name,
-                        flavorName = it.flavorName,
-                        buildType = it.buildType,
-                        configurationName = configurationName,
-                        baselineProfileExtension = baselineProfileExtension
-                    )
-                }
-            }
+        // Creating configurations only for the extended build types.
+        if (variant.buildType !in extendedTypeToOriginalTypeMapping.keys) {
+            return
         }
 
-        // After variants have been resolved and the AGP tasks have been created, create the plugin
-        // tasks.
-        var applied = false
-        testExtension.applicationVariants.all {
-            if (applied) return@all
-            applied = true
-            createTaskBlocks.forEach { it() }
+        // Creates the configuration to handle this variant. Note that in the attributes
+        // to match the configuration we use the original build type without `nonObfuscated`.
+        val configuration = createConfigurationForVariant(
+            variant = variant,
+            originalBuildTypeName = extendedTypeToOriginalTypeMapping[variant.buildType] ?: "",
+        )
+
+        // Prepares a block to execute later that creates the tasks for this variant
+        afterVariants {
+            createTasksForVariant(
+                project = project,
+                variant = variant,
+                configurationName = configuration.name,
+                baselineProfileExtension = baselineProfileExtension
+            )
         }
     }
 
+    private fun createConfigurationForVariant(variant: Variant, originalBuildTypeName: String) =
+        configurationManager.maybeCreate(
+            nameParts = listOf(
+                variant.flavorName ?: "",
+                originalBuildTypeName,
+                CONFIGURATION_NAME_BASELINE_PROFILES
+            ),
+            canBeConsumed = true,
+            canBeResolved = false,
+            buildType = originalBuildTypeName,
+            productFlavors = variant.productFlavors
+        )
+
     private fun createTasksForVariant(
         project: Project,
-        variantName: String,
-        flavorName: String?,
-        buildType: String?,
+        variant: TestVariant,
         configurationName: String,
         baselineProfileExtension: BaselineProfileProducerExtension
     ) {
 
         // Prepares the devices list to use to generate the baseline profile.
-        val devices = baselineProfileExtension.devices
+        val devices = mutableSetOf<String>()
+        devices.addAll(baselineProfileExtension.managedDevices)
+        if (baselineProfileExtension.useConnectedDevices) devices.add("connected")
 
         // The test task runs the ui tests
-        val testTasks = devices.map {
-            try {
-                project.tasks.named(camelCase(it, variantName, "androidTest")).apply {
-                    configure { t ->
-                        // TODO: this is a bit hack-ish but we can rewrite if we decide to keep the
-                        //  configuration [BaselineProfileProducerExtension.enableEmulatorDisplay]
-                        if (t.hasProperty("enableEmulatorDisplay")) {
-                            t.setProperty(
-                                "enableEmulatorDisplay",
-                                baselineProfileExtension.enableEmulatorDisplay
-                            )
-                        }
-                    }
-                }
-            } catch (e: UnknownTaskException) {
+        val testTasks = devices.map { device ->
+            val task = InstrumentationTestTaskWrapper.getByName(
+                project = project,
+                device = device,
+                variantName = variant.name
+            )
 
-                // If gradle is syncing don't throw any exception and simply stop here. This plugin
-                // will fail at build time instead. This allows not breaking project sync in ide.
-                if (project.isGradleSyncRunning()) {
-                    return
-                }
+            // The task is null if the managed device name does not exist
+            if (task == null) {
+
+                // If gradle is syncing don't throw any exception and simply stop here. This
+                // plugin will fail at build time instead. This allows not breaking project
+                // sync in ide.
+                if (isGradleSyncRunning()) return
 
                 throw GradleException(
                     """
-                    It wasn't possible to determine the test task for managed device `$it`.
+                    It wasn't possible to determine the test task for managed device `$device`.
                     Please check the managed devices specified in the baseline profile
                     configuration.
-                """.trimIndent(), e
+                    """.trimIndent()
                 )
             }
-        }
+
+            task
+        }.onEach { it.setEnableEmulatorDisplay(baselineProfileExtension.enableEmulatorDisplay) }
 
         // The collect task collects the baseline profile files from the ui test results
-        val collectTaskProvider = project.tasks.register(
-            camelCase(COLLECT_TASK_NAME, variantName, TASK_NAME_SUFFIX),
-            CollectBaselineProfileTask::class.java
-        ) {
-
-            var outputDir = project
-                .layout
-                .buildDirectory
-                .dir("$INTERMEDIATES_BASE_FOLDER/$flavorName/")
-            if (!flavorName.isNullOrBlank()) {
-                outputDir = outputDir.map { d -> d.dir(flavorName) }
-            }
-            if (!buildType.isNullOrBlank()) {
-                outputDir = outputDir.map { d -> d.dir(buildType) }
-            }
-
-            // Sets the baseline-prof output path.
-            it.outputFile.set(outputDir.map { d -> d.file("baseline-prof.txt") })
-
-            // Sets the test results inputs
-            it.testResultDirs.setFrom(testTasks.map { task ->
-                task.flatMap { t -> t.property("resultsDir") as DirectoryProperty }
-            })
-        }
+        val collectTaskProvider = CollectBaselineProfileTask.registerForVariant(
+            project = project,
+            variant = variant,
+            testTaskDependencies = testTasks
+        )
 
         // The artifacts are added to the configuration that exposes the generated baseline profile
-        project.artifacts { artifactHandler ->
-            artifactHandler.add(configurationName, collectTaskProvider) { artifact ->
-                artifact.type = CONFIGURATION_ARTIFACT_TYPE
-                artifact.builtBy(collectTaskProvider)
-            }
-        }
-    }
-
-    private fun createBaselineProfileConfigurationForVariant(
-        project: Project,
-        variantName: String,
-        productFlavors: List<Pair<String, String>>,
-        originalBuildTypeName: String,
-    ): String {
-        val configurationName =
-            camelCase(variantName, CONFIGURATION_NAME_BASELINE_PROFILES)
-        project.configurations
-            .maybeCreate(configurationName)
-            .apply {
-                isCanBeResolved = false
-                isCanBeConsumed = true
-                attributes {
-
-                    // Main specialized attribute
-                    it.attribute(
-                        Usage.USAGE_ATTRIBUTE,
-                        project.objects.named(
-                            Usage::class.java, ATTRIBUTE_USAGE_BASELINE_PROFILE
-                        )
-                    )
-
-                    // Build type
-                    it.attribute(
-                        BuildTypeAttr.ATTRIBUTE,
-                        project.objects.named(
-                            BuildTypeAttr::class.java, originalBuildTypeName)
-                    )
-
-                    // Jvm Environment
-                    it.attribute(
-                        TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
-                        project.objects.named(
-                            TargetJvmEnvironment::class.java, ATTRIBUTE_TARGET_JVM_ENVIRONMENT
-                        )
-                    )
-
-                    // Agp version
-                    it.attribute(
-                        AgpVersionAttr.ATTRIBUTE,
-                        project.objects.named(
-                            AgpVersionAttr::class.java, project.agpVersionString()
-                        )
-                    )
-
-                    // Baseline Profile Plugin Version
-                    it.attribute(
-                        BaselineProfilePluginVersionAttr.ATTRIBUTE,
-                        project.objects.named(
-                            BaselineProfilePluginVersionAttr::class.java,
-                            ATTRIBUTE_BASELINE_PROFILE_PLUGIN_VERSION
-                        )
-                    )
-
-                    // Product flavors
-                    productFlavors.forEach { (flavorName, flavorValue) ->
-                        it.attribute(
-                            @Suppress("UnstableApiUsage")
-                            ProductFlavorAttr.of(flavorName),
-                            project.objects.named(
-                                ProductFlavorAttr::class.java, flavorValue
-                            )
-                        )
-                    }
-                }
-            }
-        return configurationName
+        addArtifactToConfiguration(
+            configurationName = configurationName,
+            taskProvider = collectTaskProvider,
+            artifactType = CONFIGURATION_ARTIFACT_TYPE
+        )
     }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/CollectBaselineProfileTask.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/CollectBaselineProfileTask.kt
similarity index 74%
rename from benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/CollectBaselineProfileTask.kt
rename to benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/CollectBaselineProfileTask.kt
index 1b09faf..aba5cd6 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/CollectBaselineProfileTask.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/CollectBaselineProfileTask.kt
@@ -14,12 +14,17 @@
  * limitations under the License.
  */
 
-package androidx.baselineprofile.gradle.producer
+package androidx.baselineprofile.gradle.producer.tasks
 
+import androidx.baselineprofile.gradle.utils.INTERMEDIATES_BASE_FOLDER
+import androidx.baselineprofile.gradle.utils.TASK_NAME_SUFFIX
+import androidx.baselineprofile.gradle.utils.camelCase
+import com.android.build.api.variant.TestVariant
 import com.google.testing.platform.proto.api.core.TestSuiteResultProto
 import java.io.File
 import org.gradle.api.DefaultTask
 import org.gradle.api.GradleException
+import org.gradle.api.Project
 import org.gradle.api.file.ConfigurableFileCollection
 import org.gradle.api.file.RegularFileProperty
 import org.gradle.api.tasks.InputFiles
@@ -27,6 +32,7 @@
 import org.gradle.api.tasks.PathSensitive
 import org.gradle.api.tasks.PathSensitivity
 import org.gradle.api.tasks.TaskAction
+import org.gradle.api.tasks.TaskProvider
 import org.gradle.work.DisableCachingByDefault
 
 /**
@@ -36,6 +42,49 @@
 @DisableCachingByDefault(because = "Not worth caching.")
 abstract class CollectBaselineProfileTask : DefaultTask() {
 
+    companion object {
+        private const val COLLECT_TASK_NAME = "collect"
+
+        internal fun registerForVariant(
+            project: Project,
+            variant: TestVariant,
+            testTaskDependencies: List<InstrumentationTestTaskWrapper>
+        ): TaskProvider<CollectBaselineProfileTask> {
+
+            val flavorName = variant.flavorName
+            val buildType = variant.buildType
+
+            return project.tasks.register(
+                camelCase(COLLECT_TASK_NAME, variant.name, TASK_NAME_SUFFIX),
+                CollectBaselineProfileTask::class.java
+            ) {
+
+                var outputDir = project
+                    .layout
+                    .buildDirectory
+                    .dir("$INTERMEDIATES_BASE_FOLDER/${variant.flavorName}/")
+
+                if (!flavorName.isNullOrBlank()) {
+                    outputDir = outputDir.map { d -> d.dir(flavorName) }
+                }
+                if (!buildType.isNullOrBlank()) {
+                    outputDir = outputDir.map { d -> d.dir(buildType) }
+                }
+
+                // Sets the baseline-prof output path.
+                it.outputFile.set(outputDir.map { d -> d.file("baseline-prof.txt") })
+
+                // Sets the test results inputs
+                it.testResultDirs.setFrom(testTaskDependencies.map { t -> t.resultsDir })
+            }
+        }
+    }
+
+    init {
+        group = "Baseline Profile"
+        description = "Collects a baseline profile previously generated through integration tests."
+    }
+
     @get:InputFiles
     @get:PathSensitive(PathSensitivity.NONE)
     abstract val testResultDirs: ConfigurableFileCollection
@@ -43,11 +92,6 @@
     @get:OutputFile
     abstract val outputFile: RegularFileProperty
 
-    init {
-        group = "Baseline Profile"
-        description = "Collects a baseline profile previously generated through integration tests."
-    }
-
     @TaskAction
     fun exec() {
 
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/InstrumentationTestTaskWrapper.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/InstrumentationTestTaskWrapper.kt
new file mode 100644
index 0000000..e4f9650
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/producer/tasks/InstrumentationTestTaskWrapper.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.producer.tasks
+
+import androidx.baselineprofile.gradle.utils.namedOrNull
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.file.Directory
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Provider
+import org.gradle.api.tasks.TaskProvider
+
+/**
+ * Wrapper around the instrumentation test task. This wrapper tries to retrieve the instrumentation
+ * test task named `<device><variantName>AndroidTest`, if existing. For example:
+ * `connectedFreeReleaseAndroidTest`.It supports both `managed` and `connected` devices.
+ */
+internal class InstrumentationTestTaskWrapper(private val testTask: TaskProvider<Task>) {
+
+    companion object {
+
+        private const val ANDROID_TEST = "androidTest"
+
+        internal fun getByName(
+            project: Project,
+            device: String,
+            variantName: String
+        ): InstrumentationTestTaskWrapper? {
+            val taskProvider = project
+                .tasks
+                .namedOrNull<Task>(device, variantName, ANDROID_TEST) ?: return null
+            return InstrumentationTestTaskWrapper(taskProvider)
+        }
+    }
+
+    val resultsDir: Provider<Directory>
+        get() = testTask.flatMap { t -> t.property("resultsDir") as DirectoryProperty }
+
+    fun setEnableEmulatorDisplay(value: Boolean) {
+        testTask.configure { t ->
+            // TODO: this is a bit hack-ish but we can rewrite if we decide to keep the
+            //  configuration [BaselineProfileProducerExtension.enableEmulatorDisplay]
+            if (t.hasProperty("enableEmulatorDisplay")) {
+                t.setProperty("enableEmulatorDisplay", value)
+            }
+        }
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Agp.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Agp.kt
index 6116132..1eedf43 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Agp.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Agp.kt
@@ -17,24 +17,10 @@
 package androidx.baselineprofile.gradle.utils
 
 import com.android.build.api.AndroidPluginVersion
-import com.android.build.api.dsl.TestedExtension
 import com.android.build.api.variant.AndroidComponentsExtension
-import com.android.build.gradle.AppExtension
-import com.android.build.gradle.LibraryExtension
 import org.gradle.api.GradleException
 import org.gradle.api.Project
 
-private val gradleSyncProps by lazy {
-    listOf(
-        "android.injected.build.model.v2",
-        "android.injected.build.model.only",
-        "android.injected.build.model.only.advanced",
-    )
-}
-
-internal fun Project.isGradleSyncRunning() =
-    gradleSyncProps.any { it in properties && properties[it].toString().toBoolean() }
-
 internal fun Project.agpVersion(): AndroidPluginVersion {
     return project
         .extensions
@@ -43,60 +29,9 @@
         ?: throw GradleException(
             // This can happen only if the plugin is not applied to an android module.
             """
-                The module $name does not have a registered `AndroidComponentsExtension`. This can
-                only happen if this is not an Android module. Please review your build.gradle to
-                ensure this plugin is applied to the correct module.
-                """.trimIndent()
-        )
-}
-
-internal fun Project.agpVersionString(): String {
-    val agpVersion = agpVersion()
-    val preview = if (!agpVersion.previewType.isNullOrBlank()) {
-        "-${agpVersion.previewType}${agpVersion.preview}"
-    } else {
-        ""
-    }
-    return "${agpVersion.major}.${agpVersion.minor}.${agpVersion.micro}$preview"
-}
-
-internal fun Project.checkAgpVersion(
-    min: AndroidPluginVersion = MIN_AGP_VERSION_REQUIRED,
-    max: AndroidPluginVersion = MAX_AGP_VERSION_REQUIRED,
-) {
-    val agpVersion = agpVersion()
-    if (agpVersion < min || agpVersion > max) {
-        throw GradleException(
-            """
-            This version of the Baseline Profile Gradle Plugin only works with Android Gradle plugin
-            between versions $MIN_AGP_VERSION_REQUIRED and $MAX_AGP_VERSION_REQUIRED. Current version
-            is $agpVersion."
+        The module $name does not have a registered `AndroidComponentsExtension`. This can only
+        happen if this is not an Android module. Please review your build.gradle to ensure this
+        plugin is applied to the correct module.
             """.trimIndent()
         )
-    }
-}
-
-internal fun Project.afterVariants(block: () -> (Unit)) {
-    val extensionVariants =
-        when (val tested = extensions.getByType(TestedExtension::class.java)) {
-            is AppExtension -> tested.applicationVariants
-            is LibraryExtension -> tested.libraryVariants
-            else -> {
-                if (isGradleSyncRunning()) {
-                    return
-                }
-                throw GradleException(
-                    """
-                    Unrecognized extension: $tested not of type AppExtension or LibraryExtension.
-                    """.trimIndent()
-                )
-            }
-        }
-
-    var applied = false
-    extensionVariants.all {
-        if (applied) return@all
-        applied = true
-        block()
-    }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt
new file mode 100644
index 0000000..d5a4e25
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AgpPlugin.kt
@@ -0,0 +1,281 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.utils
+
+import com.android.build.api.AndroidPluginVersion
+import com.android.build.api.dsl.ApplicationExtension
+import com.android.build.api.dsl.LibraryExtension
+import com.android.build.api.dsl.TestExtension
+import com.android.build.api.dsl.TestedExtension
+import com.android.build.api.variant.AndroidComponentsExtension
+import com.android.build.api.variant.ApplicationAndroidComponentsExtension
+import com.android.build.api.variant.ApplicationVariant
+import com.android.build.api.variant.ApplicationVariantBuilder
+import com.android.build.api.variant.LibraryAndroidComponentsExtension
+import com.android.build.api.variant.LibraryVariant
+import com.android.build.api.variant.LibraryVariantBuilder
+import com.android.build.api.variant.TestAndroidComponentsExtension
+import com.android.build.api.variant.TestVariant
+import com.android.build.api.variant.TestVariantBuilder
+import com.android.build.api.variant.Variant
+import com.android.build.api.variant.VariantBuilder
+import org.gradle.api.GradleException
+import org.gradle.api.Project
+import org.gradle.api.Task
+import org.gradle.api.tasks.TaskProvider
+
+/**
+ * Defines callbacks and utility methods to create a plugin that utilizes AGP apis.
+ * Callbacks with the configuration lifecycle of the agp plugins are provided.
+ */
+internal abstract class AgpPlugin(
+    private val project: Project,
+    private val supportedAgpPlugins: Set<AgpPluginId>,
+    private val minAgpVersion: AndroidPluginVersion,
+    private val maxAgpVersion: AndroidPluginVersion,
+) {
+
+    companion object {
+        private val gradleSyncProps by lazy {
+            listOf(
+                "android.injected.build.model.v2",
+                "android.injected.build.model.only",
+                "android.injected.build.model.only.advanced",
+            )
+        }
+    }
+
+    private val afterVariantBlocks = mutableListOf<() -> (Unit)>()
+
+    fun onApply() {
+
+        val foundPlugins = mutableSetOf<AgpPluginId>()
+
+        // Try to configure with the supported plugins.
+        for (agpPluginId in supportedAgpPlugins) {
+            project.pluginManager.withPlugin(agpPluginId.value) {
+                foundPlugins.add(agpPluginId)
+                configureWithAndroidPlugin()
+            }
+        }
+
+        // Only used to verify that the android application plugin has been applied.
+        // Note that we don't want to throw any exception if gradle sync is in progress.
+        project.afterEvaluate {
+            if (!isGradleSyncRunning()) {
+                if (foundPlugins.isEmpty()) {
+                    onAgpPluginNotFound(foundPlugins)
+                } else {
+                    onAgpPluginFound(foundPlugins)
+                }
+            }
+        }
+    }
+
+    private fun configureWithAndroidPlugin() {
+
+        checkAgpVersion(min = minAgpVersion, max = maxAgpVersion)
+
+        onBeforeFinalizeDsl()
+
+        testAndroidComponentExtension()?.let { testComponent ->
+            testComponent.finalizeDsl { onTestFinalizeDsl(it) }
+            testComponent.beforeVariants { onTestBeforeVariants(it) }
+            testComponent.onVariants { onTestVariants(it) }
+        }
+
+        applicationAndroidComponentsExtension()?.let { applicationComponent ->
+            applicationComponent.finalizeDsl { onApplicationFinalizeDsl(it) }
+            applicationComponent.beforeVariants { onApplicationBeforeVariants(it) }
+            applicationComponent.onVariants { onApplicationVariants(it) }
+        }
+
+        libraryAndroidComponentsExtension()?.let { libraryComponent ->
+            libraryComponent.finalizeDsl { onLibraryFinalizeDsl(it) }
+            libraryComponent.beforeVariants { onLibraryBeforeVariants(it) }
+            libraryComponent.onVariants { onLibraryVariants(it) }
+        }
+
+        androidComponentsExtension()?.let { commonComponent ->
+            commonComponent.finalizeDsl { onFinalizeDsl(commonComponent) }
+            commonComponent.beforeVariants { onBeforeVariants(it) }
+            commonComponent.onVariants { onVariants(it) }
+        }
+
+        // Runs the after variants callback that is module type dependent
+        val testedExtension = testedExtension()
+        val testExtension = testExtension()
+
+        val variants = when {
+            testedExtension != null &&
+                testedExtension is com.android.build.gradle.AppExtension -> {
+                testedExtension.applicationVariants
+            }
+
+            testedExtension != null &&
+                testedExtension is com.android.build.gradle.LibraryExtension -> {
+                testedExtension.libraryVariants
+            }
+
+            testExtension != null -> {
+                testExtension.applicationVariants
+            }
+
+            else -> {
+                if (isGradleSyncRunning()) return
+                // This cannot happen because of user configuration because the plugin is only
+                // applied if there is an android gradle plugin.
+                throw GradleException("Module `${project.path}` is not a supported android module.")
+            }
+        }
+
+        var applied = false
+        variants.all {
+            if (applied) return@all
+            applied = true
+
+            // Execute all the scheduled variant blocks
+            afterVariantBlocks.forEach { it() }
+
+            // Execute the after variant callback if scheduled.
+            onAfterVariants()
+        }
+    }
+
+    // Utility methods
+
+    protected fun <T : Task> addArtifactToConfiguration(
+        configurationName: String,
+        taskProvider: TaskProvider<T>,
+        artifactType: String
+    ) {
+        project.artifacts { artifactHandler ->
+            artifactHandler.add(configurationName, taskProvider) { artifact ->
+                artifact.type = artifactType
+                artifact.builtBy(taskProvider)
+            }
+        }
+    }
+
+    protected fun afterVariants(block: () -> (Unit)) {
+        afterVariantBlocks.add(block)
+    }
+
+    private fun checkAgpVersion(min: AndroidPluginVersion, max: AndroidPluginVersion) {
+        val agpVersion = project.agpVersion()
+        if (agpVersion < min || agpVersion > max) {
+            throw GradleException(
+                """
+        This version of the Baseline Profile Gradle Plugin only works with Android Gradle plugin
+        between versions $MIN_AGP_VERSION_REQUIRED and $MAX_AGP_VERSION_REQUIRED. Current version
+        is $agpVersion."
+            """.trimIndent()
+            )
+        }
+    }
+
+    internal fun isGradleSyncRunning() = gradleSyncProps.any {
+        it in project.properties && project.properties[it].toString().toBoolean()
+    }
+
+    protected fun isTestModule() = testAndroidComponentExtension() != null
+    protected fun isLibraryModule() = libraryAndroidComponentsExtension() != null
+    protected fun isApplicationModule() = applicationAndroidComponentsExtension() != null
+
+    // Plugin application callbacks
+
+    protected open fun onAgpPluginNotFound(pluginIds: Set<AgpPluginId>) {}
+
+    protected open fun onAgpPluginFound(pluginIds: Set<AgpPluginId>) {}
+
+    // Test callbacks
+
+    protected open fun onTestFinalizeDsl(extension: TestExtension) {}
+
+    protected open fun onTestBeforeVariants(variantBuilder: TestVariantBuilder) {}
+
+    protected open fun onTestVariants(variant: TestVariant) {}
+
+    // Application callbacks
+
+    protected open fun onApplicationFinalizeDsl(extension: ApplicationExtension) {}
+
+    protected open fun onApplicationBeforeVariants(variantBuilder: ApplicationVariantBuilder) {}
+
+    protected open fun onApplicationVariants(variant: ApplicationVariant) {}
+
+    // Library callbacks
+
+    protected open fun onLibraryFinalizeDsl(extension: LibraryExtension) {}
+
+    protected open fun onLibraryBeforeVariants(variantBuilder: LibraryVariantBuilder) {}
+
+    protected open fun onLibraryVariants(variant: LibraryVariant) {}
+
+    // Shared callbacks
+
+    protected open fun onBeforeFinalizeDsl() {}
+
+    protected open fun onFinalizeDsl(extension: AndroidComponentsExtension<*, *, *>) {}
+
+    protected open fun onBeforeVariants(variantBuilder: VariantBuilder) {}
+
+    protected open fun onVariants(variant: Variant) {}
+
+    protected open fun onAfterVariants() {}
+
+    // Quick access to extension methods
+
+    private fun testAndroidComponentExtension(): TestAndroidComponentsExtension? =
+        project
+            .extensions
+            .findByType(TestAndroidComponentsExtension::class.java)
+
+    private fun applicationAndroidComponentsExtension(): ApplicationAndroidComponentsExtension? =
+        project
+            .extensions
+            .findByType(ApplicationAndroidComponentsExtension::class.java)
+
+    private fun libraryAndroidComponentsExtension(): LibraryAndroidComponentsExtension? =
+        project
+            .extensions
+            .findByType(LibraryAndroidComponentsExtension::class.java)
+
+    private fun androidComponentsExtension(): AndroidComponentsExtension<*, *, *>? =
+        project
+            .extensions
+            .findByType(AndroidComponentsExtension::class.java)
+
+    private fun testedExtension(): TestedExtension? =
+        project
+            .extensions
+            .findByType(TestedExtension::class.java)
+
+    private fun testExtension(): com.android.build.gradle.TestExtension? =
+        project
+            .extensions
+            .findByType(com.android.build.gradle.TestExtension::class.java)
+}
+
+/**
+ * Enumerates the supported android plugins.
+ */
+internal enum class AgpPluginId(val value: String) {
+    ID_ANDROID_APPLICATION_PLUGIN("com.android.application"),
+    ID_ANDROID_LIBRARY_PLUGIN("com.android.library"),
+    ID_ANDROID_TEST_PLUGIN("com.android.test")
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AndroidTestModuleWrapper.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AndroidTestModuleWrapper.kt
new file mode 100644
index 0000000..db8657f
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/AndroidTestModuleWrapper.kt
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.baselineprofile.gradle.utils
+
+import com.android.build.gradle.TestExtension
+import org.gradle.api.Project
+
+/**
+ * Wrapper around an AGP test module to provide utility methods, such setting experimental
+ * properties.
+ */
+internal class AndroidTestModuleWrapper(project: Project) {
+
+    private val testExtension by lazy { project.extensions.getByType(TestExtension::class.java) }
+
+    fun setSelfInstrumenting(value: Boolean) {
+        @Suppress("UnstableApiUsage")
+        testExtension.experimentalProperties["android.experimental.self-instrumenting"] = value
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
index e9f82dc..54ec7cd 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/Constants.kt
@@ -43,3 +43,6 @@
 // structure: <action><variant><suffix>. For example, if action is `generate`, variant is `release`
 // and suffix is `baselineProfile` the task name will be `generateReleaseBaselineProfile`.
 internal const val TASK_NAME_SUFFIX = "baselineProfile"
+
+// Other constants
+internal const val RELEASE = "release"
diff --git a/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/R8Utils.kt b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/R8Utils.kt
new file mode 100644
index 0000000..121dc35
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/main/kotlin/androidx/baselineprofile/gradle/utils/R8Utils.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.utils
+
+import com.android.build.api.AndroidPluginVersion
+import com.android.build.api.variant.Variant
+import org.gradle.api.DefaultTask
+import org.gradle.api.Project
+import org.gradle.api.file.DirectoryProperty
+import org.gradle.api.provider.Property
+import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.OutputDirectory
+import org.gradle.api.tasks.TaskAction
+import org.gradle.work.DisableCachingByDefault
+
+internal class R8Utils(private val project: Project) {
+
+    companion object {
+        private const val PROPERTY_R8_REWRITE_BASELINE_PROFILE_RULES =
+            "android.experimental.art-profile-r8-rewriting"
+        private val AGP_MIN_VERSION = AndroidPluginVersion(8, 1, 0).alpha(8)
+    }
+
+    @Suppress("UnstableApiUsage")
+    fun enableR8RulesRewriteForVariant(variant: Variant) {
+
+        // Checks the AGP min version to support this.
+        if (project.agpVersion() < AGP_MIN_VERSION) {
+            return
+        }
+
+        // TODO: Checks the R8 version to support this.
+
+        // TODO: Note that currently there needs to be at least a baseline profile,
+        //  even if empty. For this reason we always add a src set that points to
+        //  an empty file. This can removed after b/271158087 is fixed.
+        GenerateDummyBaselineProfileTask.setupForVariant(project, variant)
+
+        // Sets the experimental property
+        variant.experimentalProperties.put(PROPERTY_R8_REWRITE_BASELINE_PROFILE_RULES, true)
+    }
+}
+
+@DisableCachingByDefault(because = "Not worth caching.")
+abstract class GenerateDummyBaselineProfileTask : DefaultTask() {
+
+    companion object {
+        internal fun setupForVariant(
+            project: Project,
+            variant: Variant
+        ) {
+            val taskProvider = project
+                .tasks
+                .maybeRegister<GenerateDummyBaselineProfileTask>(
+                    "generate", variant.name, "profileForR8RuleRewrite"
+                ) {
+                    it.outputDir.set(
+                        project
+                            .layout
+                            .buildDirectory
+                            .dir("$INTERMEDIATES_BASE_FOLDER/${variant.name}/empty/")
+                    )
+                    it.variantName.set(variant.name)
+                }
+            @Suppress("UnstableApiUsage")
+            variant.sources.baselineProfiles?.addGeneratedSourceDirectory(
+                taskProvider, GenerateDummyBaselineProfileTask::outputDir
+            )
+        }
+    }
+
+    @get:OutputDirectory
+    abstract val outputDir: DirectoryProperty
+
+    @get:Input
+    abstract val variantName: Property<String>
+
+    @TaskAction
+    fun exec() {
+        outputDir
+            .file("empty-baseline-prof.txt")
+            .get()
+            .asFile
+            .writeText("Lignore/This;")
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPluginTest.kt
index ed34636..205a7b5 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/apptarget/BaselineProfileAppTargetPluginTest.kt
@@ -16,11 +16,8 @@
 
 package androidx.baselineprofile.gradle.apptarget
 
-import androidx.baselineprofile.gradle.utils.GRADLE_CODE_PRINT_TASK
-import androidx.testutils.gradle.ProjectSetupRule
-import com.google.common.truth.Truth.assertThat
-import org.gradle.testkit.runner.GradleRunner
-import org.junit.Before
+import androidx.baselineprofile.gradle.utils.BaselineProfileProjectSetupRule
+import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,66 +27,45 @@
 class BaselineProfileAppTargetPluginTest {
 
     @get:Rule
-    val projectSetup = ProjectSetupRule()
-
-    private lateinit var gradleRunner: GradleRunner
-
-    @Before
-    fun setUp() {
-        gradleRunner = GradleRunner.create()
-            .withProjectDir(projectSetup.rootDir)
-            .withPluginClasspath()
-    }
+    val projectSetup = BaselineProfileProjectSetupRule()
 
     @Test
     fun verifyBuildTypes() {
-        projectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id("com.android.application")
-                    id("androidx.baselineprofile.apptarget")
-                }
-                android {
-                    namespace 'com.example.namespace'
-                    buildTypes {
-                        anotherRelease { initWith(release) }
-                    }
-                }
-
-                $GRADLE_CODE_PRINT_TASK
-
-                def registerTask(buildTypeName, taskName) {
-                    tasks.register(taskName, PrintTask) { t ->
-                        def buildType = android.buildTypes[buildTypeName]
-                        def text = "minifyEnabled=" + buildType.minifyEnabled.toString() + "\n"
-                        text += "testCoverageEnabled=" + buildType.testCoverageEnabled.toString() + "\n"
-                        text += "debuggable=" + buildType.debuggable.toString() + "\n"
-                        text += "profileable=" + buildType.profileable.toString() + "\n"
-                        t.text.set(text)
-                    }
-                }
-                registerTask("nonMinifiedRelease", "printNonMinifiedReleaseBuildType")
-                registerTask("nonMinifiedAnotherRelease", "printNonMinifiedAnotherReleaseBuildType")
-            """.trimIndent(),
-            suffix = ""
-        )
-
-        val runGradleAndAssertOutput: (String, (String) -> (Unit)) -> (Unit) =
-            { taskName, assertBlock ->
-                gradleRunner
-                    .withArguments(taskName, "--stacktrace")
-                    .build()
-                    .output
-                    .let(assertBlock)
+        projectSetup.appTarget.setBuildGradle(
+            """
+            plugins {
+                id("com.android.application")
+                id("androidx.baselineprofile.apptarget")
             }
+            android {
+                namespace 'com.example.namespace'
+                buildTypes {
+                    anotherRelease { initWith(release) }
+                }
+            }
+
+            def registerTask(buildTypeName, taskName) {
+                tasks.register(taskName, PrintTask) { t ->
+                    def buildType = android.buildTypes[buildTypeName]
+                    def text = "minifyEnabled=" + buildType.minifyEnabled.toString() + "\n"
+                    text += "testCoverageEnabled=" + buildType.testCoverageEnabled.toString() + "\n"
+                    text += "debuggable=" + buildType.debuggable.toString() + "\n"
+                    text += "profileable=" + buildType.profileable.toString() + "\n"
+                    t.text.set(text)
+                }
+            }
+            registerTask("nonMinifiedRelease", "printNonMinifiedReleaseBuildType")
+            registerTask("nonMinifiedAnotherRelease", "printNonMinifiedAnotherReleaseBuildType")
+            """.trimIndent()
+        )
 
         arrayOf("printNonMinifiedReleaseBuildType", "printNonMinifiedAnotherReleaseBuildType")
             .forEach { taskName ->
-                runGradleAndAssertOutput(taskName) {
-                    assertThat(it).contains("minifyEnabled=false")
-                    assertThat(it).contains("testCoverageEnabled=false")
-                    assertThat(it).contains("debuggable=false")
-                    assertThat(it).contains("profileable=true")
+                projectSetup.appTarget.gradleRunner.buildAndAssertThatOutput(taskName) {
+                    contains("minifyEnabled=false")
+                    contains("testCoverageEnabled=false")
+                    contains("debuggable=false")
+                    contains("profileable=true")
                 }
             }
     }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
index 5b925ae..d5974d9 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/consumer/BaselineProfileConsumerPluginTest.kt
@@ -16,19 +16,19 @@
 
 package androidx.baselineprofile.gradle.consumer
 
-import androidx.baselineprofile.gradle.utils.CONFIGURATION_NAME_BASELINE_PROFILES
-import androidx.baselineprofile.gradle.utils.GRADLE_CODE_PRINT_TASK
+import androidx.baselineprofile.gradle.utils.ANDROID_APPLICATION_PLUGIN
+import androidx.baselineprofile.gradle.utils.ANDROID_LIBRARY_PLUGIN
+import androidx.baselineprofile.gradle.utils.ANDROID_TEST_PLUGIN
+import androidx.baselineprofile.gradle.utils.BaselineProfileProjectSetupRule
+import androidx.baselineprofile.gradle.utils.Fixtures
+import androidx.baselineprofile.gradle.utils.VariantProfile
 import androidx.baselineprofile.gradle.utils.build
 import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
-import androidx.baselineprofile.gradle.utils.camelCase
-import androidx.testutils.gradle.ProjectSetupRule
+import androidx.baselineprofile.gradle.utils.buildAndFailAndAssertThatOutput
 import com.google.common.truth.Truth.assertThat
 import java.io.File
-import org.gradle.testkit.runner.GradleRunner
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
@@ -41,41 +41,15 @@
 
     companion object {
         private const val EXPECTED_PROFILE_FOLDER = "generated/baselineProfiles"
-        private const val ANDROID_APPLICATION_PLUGIN = "com.android.application"
-        private const val ANDROID_LIBRARY_PLUGIN = "com.android.library"
-        private const val ANDROID_TEST_PLUGIN = "com.android.test"
     }
 
-    private val rootFolder = TemporaryFolder().also { it.create() }
-
     @get:Rule
-    val consumerProjectSetup = ProjectSetupRule(rootFolder.root)
+    val projectSetup = BaselineProfileProjectSetupRule()
 
-    @get:Rule
-    val producerProjectSetup = ProjectSetupRule(rootFolder.root)
-
-    private lateinit var consumerModuleName: String
-    private lateinit var producerModuleName: String
-    private lateinit var gradleRunner: GradleRunner
-
-    @Before
-    fun setUp() {
-        consumerModuleName = consumerProjectSetup.rootDir.relativeTo(rootFolder.root).name
-        producerModuleName = producerProjectSetup.rootDir.relativeTo(rootFolder.root).name
-
-        rootFolder.newFile("settings.gradle").writeText(
-            """
-            include '$consumerModuleName'
-            include '$producerModuleName'
-        """.trimIndent()
-        )
-        gradleRunner = GradleRunner.create()
-            .withProjectDir(consumerProjectSetup.rootDir)
-            .withPluginClasspath()
-    }
+    private val gradleRunner by lazy { projectSetup.consumer.gradleRunner }
 
     private fun baselineProfileFile(variantName: String) = File(
-        consumerProjectSetup.rootDir,
+        projectSetup.consumer.rootDir,
         "src/$variantName/$EXPECTED_PROFILE_FOLDER/baseline-prof.txt"
     )
 
@@ -84,14 +58,24 @@
 
     @Test
     fun testGenerateTaskWithNoFlavors() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_LIBRARY_PLUGIN,
             dependencyOnProducerProject = true,
             flavors = false
         )
-        setupProducerProject(
-            listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
-            listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
+        projectSetup.producer.setup(
+            variantProfiles = listOf(
+                VariantProfile(
+                    flavor = null,
+                    buildType = "release",
+                    profileLines = listOf(
+                        Fixtures.CLASS_1_METHOD_1,
+                        Fixtures.CLASS_1,
+                        Fixtures.CLASS_2_METHOD_1,
+                        Fixtures.CLASS_2
+                    )
+                )
+            )
         )
 
         gradleRunner
@@ -109,12 +93,12 @@
 
     @Test
     fun testGenerateTaskWithFlavorsAndDefaultMerge() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             dependencyOnProducerProject = true
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
         )
@@ -145,7 +129,7 @@
 
     @Test
     fun testGenerateTaskWithFlavorsAndMergeAll() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             dependencyOnProducerProject = true,
@@ -153,7 +137,7 @@
                 mergeIntoMain = true
             """.trimIndent()
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
         )
@@ -180,23 +164,9 @@
     }
 
     @Test
-    fun testPluginAppliedToApplicationModule() {
-        setupProducerProject()
-        setupConsumerProject(
-            androidPlugin = ANDROID_APPLICATION_PLUGIN,
-            addAppTargetPlugin = false,
-            dependencyOnProducerProject = true
-        )
-        gradleRunner
-            .withArguments("generateBaselineProfile", "--stacktrace")
-            .build()
-        // This should not fail.
-    }
-
-    @Test
     fun testPluginAppliedToLibraryModule() {
-        setupProducerProject()
-        setupConsumerProject(
+        projectSetup.producer.setup()
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_LIBRARY_PLUGIN,
             addAppTargetPlugin = false,
             dependencyOnProducerProject = true
@@ -209,8 +179,8 @@
 
     @Test
     fun testPluginAppliedToNonApplicationAndNonLibraryModule() {
-        setupProducerProject()
-        setupConsumerProject(
+        projectSetup.producer.setup()
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_TEST_PLUGIN,
             addAppTargetPlugin = false,
             dependencyOnProducerProject = true
@@ -223,20 +193,18 @@
 
     @Test
     fun testSrcSetAreAddedToVariants() {
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
         )
-        setupConsumerProject(
-            androidPlugin = "com.android.application",
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             dependencyOnProducerProject = true,
             baselineProfileBlock = """
                 enableR8BaselineProfileRewrite = false
             """.trimIndent(),
             additionalGradleCodeBlock = """
-                $GRADLE_CODE_PRINT_TASK
-
                 androidComponents {
                     onVariants(selector()) { variant ->
                         tasks.register(variant.name + "Print", PrintTask) { t ->
@@ -254,7 +222,7 @@
                 // not exist so we need to create it.
                 val expected =
                     File(
-                        consumerProjectSetup.rootDir,
+                        projectSetup.consumer.rootDir,
                         "src/$it/$EXPECTED_PROFILE_FOLDER"
                     )
                         .apply {
@@ -270,8 +238,8 @@
 
     @Test
     fun testWhenPluginIsAppliedAndNoDependencyIsSetShouldFailWithErrorMsg() {
-        setupConsumerProject(
-            androidPlugin = "com.android.application",
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = false,
             dependencyOnProducerProject = false
         )
@@ -291,18 +259,16 @@
 
     @Test
     fun testR8RewriteBaselineProfilePropertySet() {
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2)
         )
-        setupConsumerProject(
-            androidPlugin = "com.android.library",
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
             dependencyOnProducerProject = true,
             flavors = true,
             buildTypeAnotherRelease = true,
             additionalGradleCodeBlock = """
-                $GRADLE_CODE_PRINT_TASK
-
                 androidComponents {
                     onVariants(selector()) { variant ->
                         println(variant.name)
@@ -330,8 +296,8 @@
 
     @Test
     fun testFilterAndSortAndMerge() {
-        setupConsumerProject(
-            androidPlugin = "com.android.library",
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
             flavors = true,
             baselineProfileBlock = """
                 filter {
@@ -339,7 +305,7 @@
                 }
             """.trimIndent()
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(
                 Fixtures.CLASS_1_METHOD_1,
                 Fixtures.CLASS_1_METHOD_2,
@@ -374,7 +340,7 @@
 
     @Test
     fun testSaveInSrcTrueAndAutomaticGenerationDuringBuildTrue() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             baselineProfileBlock = """
@@ -382,18 +348,20 @@
                 automaticGenerationDuringBuild = true
             """.trimIndent()
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
         )
 
         // Asserts that assembling release triggers generation of profile
         gradleRunner.buildAndAssertThatOutput("assembleFreeRelease", "--dry-run") {
-            contains(":$consumerModuleName:mergeFreeReleaseBaselineProfile")
-            contains(":$consumerModuleName:copyFreeReleaseBaselineProfileIntoSrc")
-            contains(":$consumerModuleName:mergeFreeReleaseArtProfile")
-            contains(":$consumerModuleName:compileFreeReleaseArtProfile")
-            contains(":$consumerModuleName:assembleFreeRelease")
+            arrayOf(
+                "mergeFreeReleaseBaselineProfile",
+                "copyFreeReleaseBaselineProfileIntoSrc",
+                "mergeFreeReleaseArtProfile",
+                "compileFreeReleaseArtProfile",
+                "assembleFreeRelease"
+            ).forEach { contains(":${projectSetup.consumer.name}:$it") }
         }
 
         // Asserts that the profile is generated in the src folder
@@ -408,7 +376,7 @@
 
     @Test
     fun testSaveInSrcTrueAndAutomaticGenerationDuringBuildFalse() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             baselineProfileBlock = """
@@ -416,18 +384,22 @@
                 automaticGenerationDuringBuild = false
             """.trimIndent()
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
         )
 
         // Asserts that assembling release does not trigger generation of profile
         gradleRunner.buildAndAssertThatOutput("assembleFreeRelease", "--dry-run") {
-            doesNotContain(":$consumerModuleName:mergeFreeReleaseBaselineProfile")
-            doesNotContain(":$consumerModuleName:copyFreeReleaseBaselineProfileIntoSrc")
-            contains(":$consumerModuleName:mergeFreeReleaseArtProfile")
-            contains(":$consumerModuleName:compileFreeReleaseArtProfile")
-            contains(":$consumerModuleName:assembleFreeRelease")
+            arrayOf(
+                "mergeFreeReleaseBaselineProfile",
+                "copyFreeReleaseBaselineProfileIntoSrc"
+            ).forEach { doesNotContain(":${projectSetup.consumer.name}:$it") }
+            arrayOf(
+                "mergeFreeReleaseArtProfile",
+                "compileFreeReleaseArtProfile",
+                "assembleFreeRelease"
+            ).forEach { contains(":${projectSetup.consumer.name}:$it") }
         }
 
         // Asserts that the profile is generated in the src folder
@@ -442,7 +414,7 @@
 
     @Test
     fun testSaveInSrcFalseAndAutomaticGenerationDuringBuildTrue() {
-        setupConsumerProject(
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             flavors = true,
             baselineProfileBlock = """
@@ -450,17 +422,22 @@
                 automaticGenerationDuringBuild = true
             """.trimIndent()
         )
-        setupProducerProjectWithFlavors(
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
             freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
             paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
         )
 
         // Asserts that assembling release triggers generation of profile
         gradleRunner.buildAndAssertThatOutput("assembleFreeRelease", "--dry-run") {
-            contains(":$consumerModuleName:mergeFreeReleaseBaselineProfile")
-            contains(":$consumerModuleName:mergeFreeReleaseArtProfile")
-            contains(":$consumerModuleName:compileFreeReleaseArtProfile")
-            contains(":$consumerModuleName:assembleFreeRelease")
+            arrayOf(
+                "mergeFreeReleaseBaselineProfile",
+                "mergeFreeReleaseArtProfile",
+                "compileFreeReleaseArtProfile",
+                "assembleFreeRelease"
+            ).forEach { contains(":${projectSetup.consumer.name}:$it") }
+            doesNotContain(
+                ":${projectSetup.consumer.name}:copyFreeReleaseBaselineProfileIntoSrc"
+            )
         }
 
         // Asserts that the profile is not generated in the src folder
@@ -472,8 +449,8 @@
 
     @Test
     fun testSaveInSrcFalseAndAutomaticGenerationDuringBuildFalse() {
-        setupProducerProject()
-        setupConsumerProject(
+        projectSetup.producer.setup()
+        projectSetup.consumer.setup(
             androidPlugin = ANDROID_APPLICATION_PLUGIN,
             baselineProfileBlock = """
                 saveInSrc = false
@@ -496,16 +473,22 @@
 
     @Test
     fun testWhenFiltersFilterOutAllTheProfileRules() {
-        setupConsumerProject(
-            androidPlugin = "com.android.library",
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
             baselineProfileBlock = """
                 filter { include("nothing.**") }
             """.trimIndent()
         )
-        setupProducerProject(
-            releaseProfile = listOf(
-                Fixtures.CLASS_1_METHOD_1,
-                Fixtures.CLASS_1,
+        projectSetup.producer.setup(
+            variantProfiles = listOf(
+                VariantProfile(
+                    flavor = null,
+                    buildType = "release",
+                    profileLines = listOf(
+                        Fixtures.CLASS_1_METHOD_1,
+                        Fixtures.CLASS_1
+                    )
+                )
             )
         )
         gradleRunner
@@ -523,219 +506,256 @@
     }
 
     @Test
-    fun testWhenProfileProducerProducesEmptyArtifact() {
-        setupConsumerProject(androidPlugin = "com.android.library")
-        setupProducerProject(releaseProfile = listOf())
-        gradleRunner.buildAndAssertThatOutput("generateReleaseBaselineProfile") {
-            contains("No baseline profile rules were generated for the variant `release`")
-        }
-    }
-
-    private fun setupConsumerProject(
-        androidPlugin: String,
-        flavors: Boolean = false,
-        dependencyOnProducerProject: Boolean = true,
-        buildTypeAnotherRelease: Boolean = false,
-        addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN,
-        baselineProfileBlock: String = "",
-        additionalGradleCodeBlock: String = "",
-    ) {
-        val flavorsBlock = """
-            productFlavors {
-                flavorDimensions = ["version"]
-                free { dimension "version" }
-                paid { dimension "version" }
-            }
-
-        """.trimIndent()
-
-        val buildTypeAnotherReleaseBlock = """
-            buildTypes {
-                anotherRelease { initWith(release) }
-            }
-
-        """.trimIndent()
-
-        val dependencyOnProducerProjectBlock = """
-            dependencies {
-                baselineProfile(project(":$producerModuleName"))
-            }
-
-        """.trimIndent()
-
-        consumerProjectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id("$androidPlugin")
-                    id("androidx.baselineprofile.consumer")
-                    ${if (addAppTargetPlugin) "id(\"androidx.baselineprofile.apptarget\")" else ""}
-                }
-                android {
-                    namespace 'com.example.namespace'
-                    ${if (flavors) flavorsBlock else ""}
-                    ${if (buildTypeAnotherRelease) buildTypeAnotherReleaseBlock else ""}
-                }
-
-               ${if (dependencyOnProducerProject) dependencyOnProducerProjectBlock else ""}
-
-                baselineProfile {
-                    $baselineProfileBlock
-                }
-
-                $additionalGradleCodeBlock
-
-            """.trimIndent(),
-            suffix = ""
+    fun testWhenProfileProducerProducesEmptyProfile() {
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN
         )
-    }
-
-    private fun setupProducerProjectWithFlavors(
-        freeReleaseProfileLines: List<String>,
-        paidReleaseProfileLines: List<String>,
-    ) {
-        producerProjectSetup.writeDefaultBuildGradle(
-            prefix = MockProducerBuildGrade()
-                .withProducedBaselineProfile(
-                    lines = freeReleaseProfileLines,
-                    flavorName = "free",
+        projectSetup.producer.setup(
+            variantProfiles = listOf(
+                VariantProfile(
+                    flavor = null,
                     buildType = "release",
-                    productFlavors = mapOf("version" to "free")
+                    profileLines = listOf()
                 )
-                .withProducedBaselineProfile(
-                    lines = paidReleaseProfileLines,
-                    flavorName = "paid",
-                    buildType = "release",
-                    productFlavors = mapOf("version" to "paid")
-                )
-                .build(),
-            suffix = ""
+            )
         )
+        gradleRunner.buildAndFailAndAssertThatOutput("generateReleaseBaselineProfile") {
+            contains("No baseline profile found in test outputs.")
+        }
     }
 
-    private fun setupProducerProject(
-        releaseProfile: List<String> = listOf(
-            Fixtures.CLASS_1_METHOD_1,
-            Fixtures.CLASS_2_METHOD_2,
-            Fixtures.CLASS_2,
-            Fixtures.CLASS_1
-        ),
-        vararg additionalReleaseProfiles: List<String>
-    ) {
-        val mock = MockProducerBuildGrade()
-            .withProducedBaselineProfile(
-                lines = releaseProfile,
-                flavorName = "",
-                buildType = "release",
-                productFlavors = mapOf()
-            )
-        for (profile in additionalReleaseProfiles) {
-            mock.withProducedBaselineProfile(
-                lines = profile,
-                flavorName = "",
-                buildType = "release",
-                productFlavors = mapOf()
-            )
-        }
-        producerProjectSetup.writeDefaultBuildGradle(
-            prefix = mock.build(),
-            suffix = ""
+    @Test
+    fun testVariantConfigurationOverrideForFlavors() {
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
+            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
+            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
         )
-    }
-}
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
+            flavors = true,
+            baselineProfileBlock = """
 
-private class MockProducerBuildGrade {
+                // Global configuration
+                enableR8BaselineProfileRewrite = false
+                saveInSrc = true
+                automaticGenerationDuringBuild = false
+                baselineProfileOutputDir = "generated/baselineProfiles"
+                mergeIntoMain = true
 
-    private var profileIndex = 0
-    private var content = """
-        plugins { id("com.android.library") }
-        android { namespace 'com.example.namespace' }
-
-        import com.android.build.api.attributes.BuildTypeAttr
-        import com.android.build.api.attributes.ProductFlavorAttr
-        import com.android.build.gradle.internal.attributes.VariantAttr
-        import androidx.baselineprofile.gradle.attributes.BaselineProfilePluginVersionAttr
-
-        // This task produces a file with a fixed output
-        abstract class TestProfileTask extends DefaultTask {
-            @Input abstract Property<String> getFileContent()
-            @OutputFile abstract RegularFileProperty getOutputFile()
-            @TaskAction void exec() { getOutputFile().get().asFile.write(getFileContent().get()) }
-        }
-
-    """.trimIndent()
-
-    fun withProducedBaselineProfile(
-        lines: List<String>,
-        productFlavors: Map<String, String>,
-        flavorName: String = "",
-        buildType: String
-    ): MockProducerBuildGrade {
-        val productFlavorAttributes = productFlavors.map { (name, value) ->
-            """
-            attribute(ProductFlavorAttr.of("$name"), objects.named(ProductFlavorAttr, "$value"))
+                // Per variant configuration overrides global configuration.
+                variants {
+                    free {
+                        enableR8BaselineProfileRewrite = true
+                        saveInSrc = false
+                        automaticGenerationDuringBuild = true
+                        baselineProfileOutputDir = "somefolder"
+                        mergeIntoMain = false
+                    }
+                    paidRelease {
+                        enableR8BaselineProfileRewrite = true
+                        saveInSrc = false
+                        automaticGenerationDuringBuild = true
+                        baselineProfileOutputDir = "someOtherfolder"
+                        mergeIntoMain = false
+                    }
+                }
 
             """.trimIndent()
-        }.joinToString("\n")
+        )
 
-        content += """
-
-        configurations {
-            ${configurationName(flavorName, buildType)} {
-                canBeConsumed = true
-                canBeResolved = false
-                attributes {
-                    attribute(Usage.USAGE_ATTRIBUTE, objects.named(Usage, "baselineProfile"))
-                    attribute(BuildTypeAttr.ATTRIBUTE, objects.named(BuildTypeAttr, "$buildType"))
-                    attribute(
-                        TargetJvmEnvironment.TARGET_JVM_ENVIRONMENT_ATTRIBUTE,
-                        objects.named(TargetJvmEnvironment, "android")
-                    )
-                    attribute(
-                        BaselineProfilePluginVersionAttr.ATTRIBUTE,
-                        objects.named(BaselineProfilePluginVersionAttr, "alpha1")
-                    )
-
-                    $productFlavorAttributes
-                }
-            }
+        gradleRunner.buildAndAssertThatOutput(
+            "printBaselineProfileExtensionForVariantFreeRelease"
+        ) {
+            contains("enableR8BaselineProfileRewrite=`true`")
+            contains("saveInSrc=`false`")
+            contains("automaticGenerationDuringBuild=`true`")
+            contains("baselineProfileOutputDir=`somefolder`")
+            contains("mergeIntoMain=`false`")
         }
 
-        """.trimIndent()
-        profileIndex++
-        content += """
-
-        def task$profileIndex = tasks.register('testProfile$profileIndex', TestProfileTask)
-        task$profileIndex.configure {
-            it.outputFile.set(project.layout.buildDirectory.file("test$profileIndex"))
-            it.fileContent.set(${"\"\"\"${lines.joinToString("\n")}\"\"\""})
+        gradleRunner.buildAndAssertThatOutput(
+            "printBaselineProfileExtensionForVariantPaidRelease"
+        ) {
+            contains("enableR8BaselineProfileRewrite=`true`")
+            contains("saveInSrc=`false`")
+            contains("automaticGenerationDuringBuild=`true`")
+            contains("baselineProfileOutputDir=`someOtherfolder`")
+            contains("mergeIntoMain=`false`")
         }
-        artifacts {
-            add("${
-            configurationName(
-                flavorName,
-                buildType
-            )
-        }", task$profileIndex.map { it.outputFile })
-        }
-
-        """.trimIndent()
-        return this
     }
 
-    fun build() = content
+    @Test
+    fun testVariantConfigurationOverrideForBuildTypes() {
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
+            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
+            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
+        )
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
+            flavors = true,
+            baselineProfileBlock = """
+
+                // Global configuration
+                enableR8BaselineProfileRewrite = false
+                saveInSrc = true
+                automaticGenerationDuringBuild = false
+                baselineProfileOutputDir = "generated/baselineProfiles"
+                mergeIntoMain = true
+
+                // Per variant configuration overrides global configuration.
+                variants {
+                    release {
+                        enableR8BaselineProfileRewrite = true
+                        saveInSrc = false
+                        automaticGenerationDuringBuild = true
+                        baselineProfileOutputDir = "myReleaseFolder"
+                        mergeIntoMain = false
+                    }
+                    paidRelease {
+                        enableR8BaselineProfileRewrite = true
+                        saveInSrc = false
+                        automaticGenerationDuringBuild = true
+                        baselineProfileOutputDir = "someOtherfolder"
+                        mergeIntoMain = false
+                    }
+                }
+
+            """.trimIndent()
+        )
+
+        gradleRunner.buildAndAssertThatOutput(
+            "printBaselineProfileExtensionForVariantFreeRelease"
+        ) {
+            contains("enableR8BaselineProfileRewrite=`true`")
+            contains("saveInSrc=`false`")
+            contains("automaticGenerationDuringBuild=`true`")
+            contains("baselineProfileOutputDir=`myReleaseFolder`")
+            contains("mergeIntoMain=`false`")
+        }
+
+        gradleRunner.buildAndAssertThatOutput(
+            "printBaselineProfileExtensionForVariantPaidRelease"
+        ) {
+            contains("enableR8BaselineProfileRewrite=`true`")
+            contains("saveInSrc=`false`")
+            contains("automaticGenerationDuringBuild=`true`")
+            contains("baselineProfileOutputDir=`someOtherfolder`")
+            contains("mergeIntoMain=`false`")
+        }
+    }
+
+    @Test
+    fun testVariantConfigurationOverrideForFlavorsAndBuildType() {
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
+            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
+            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
+        )
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_LIBRARY_PLUGIN,
+            flavors = true,
+            baselineProfileBlock = """
+                variants {
+                    free {
+                        saveInSrc = true
+                    }
+                    release {
+                        saveInSrc = false
+                    }
+                }
+
+            """.trimIndent()
+        )
+        gradleRunner
+            .withArguments("printBaselineProfileExtensionForVariantFreeRelease", "--stacktrace")
+            .buildAndFail()
+            .output
+            .let {
+                assertThat(it)
+                    .contains("The per-variant configuration for baseline profiles is ambiguous")
+            }
+    }
+
+    @Test
+    fun testVariantDependenciesWithFlavors() {
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
+            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
+            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
+        )
+
+        // In this setup no dependency is being added through the dependency block.
+        // Instead dependencies are being added through per-variant configuration block.
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN,
+            flavors = true,
+            dependencyOnProducerProject = false,
+            baselineProfileBlock = """
+                variants {
+                    free {
+                        from(project(":${projectSetup.producer.name}"))
+                    }
+                    paid {
+                        from(project(":${projectSetup.producer.name}"))
+                    }
+                }
+
+            """.trimIndent()
+        )
+        gradleRunner
+            .withArguments("generateReleaseBaselineProfile", "--stacktrace")
+            .build()
+
+        assertThat(readBaselineProfileFileContent("freeRelease"))
+            .containsExactly(
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_1_METHOD_1,
+            )
+        assertThat(readBaselineProfileFileContent("paidRelease"))
+            .containsExactly(
+                Fixtures.CLASS_2,
+                Fixtures.CLASS_2_METHOD_1,
+            )
+    }
+
+    @Test
+    fun testVariantDependenciesWithVariantsAndDirectConfiguration() {
+        projectSetup.producer.setupWithFreeAndPaidFlavors(
+            freeReleaseProfileLines = listOf(Fixtures.CLASS_1_METHOD_1, Fixtures.CLASS_1),
+            paidReleaseProfileLines = listOf(Fixtures.CLASS_2_METHOD_1, Fixtures.CLASS_2),
+        )
+
+        // In this setup no dependency is being added through the dependency block.
+        // Instead dependencies are being added through per-variant configuration block.
+        projectSetup.consumer.setup(
+            androidPlugin = ANDROID_APPLICATION_PLUGIN,
+            flavors = true,
+            dependencyOnProducerProject = false,
+            baselineProfileBlock = """
+                variants {
+                    freeRelease {
+                        from(project(":${projectSetup.producer.name}"))
+                    }
+                    paidRelease {
+                        from(project(":${projectSetup.producer.name}"), "freeRelease")
+                    }
+                }
+
+            """.trimIndent()
+        )
+        gradleRunner
+            .withArguments("generateReleaseBaselineProfile", "--stacktrace")
+            .build()
+
+        assertThat(readBaselineProfileFileContent("freeRelease"))
+            .containsExactly(
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_1_METHOD_1,
+            )
+
+        // This output should be the same of free release
+        assertThat(readBaselineProfileFileContent("paidRelease"))
+            .containsExactly(
+                Fixtures.CLASS_1,
+                Fixtures.CLASS_1_METHOD_1,
+            )
+    }
 }
-
-private fun configurationName(flavor: String, buildType: String): String =
-    camelCase(flavor, buildType, CONFIGURATION_NAME_BASELINE_PROFILES)
-
-object Fixtures {
-    const val CLASS_1 = "Lcom/sample/Activity;"
-    const val CLASS_1_METHOD_1 = "HSPLcom/sample/Activity;-><init>()V"
-    const val CLASS_1_METHOD_2 = "HSPLcom/sample/Activity;->onCreate(Landroid/os/Bundle;)V"
-    const val CLASS_2 = "Lcom/sample/Utils;"
-    const val CLASS_2_METHOD_1 = "HSLcom/sample/Utils;-><init>()V"
-    const val CLASS_2_METHOD_2 = "HLcom/sample/Utils;->someMethod()V"
-    const val CLASS_2_METHOD_3 = "HLcom/sample/Utils;->someOtherMethod()V"
-    const val CLASS_2_METHOD_4 = "HSLcom/sample/Utils;->someOtherMethod()V"
-    const val CLASS_2_METHOD_5 = "HSPLcom/sample/Utils;->someOtherMethod()V"
-}
\ No newline at end of file
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
index f380b5a..18474e6 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/producer/BaselineProfileProducerPluginTest.kt
@@ -16,86 +16,85 @@
 
 package androidx.baselineprofile.gradle.producer
 
-import androidx.testutils.gradle.ProjectSetupRule
-import com.google.common.truth.Truth.assertThat
-import org.gradle.testkit.runner.GradleRunner
-import org.junit.Before
+import androidx.baselineprofile.gradle.utils.BaselineProfileProjectSetupRule
+import androidx.baselineprofile.gradle.utils.VariantProfile
+import androidx.baselineprofile.gradle.utils.buildAndAssertThatOutput
+import androidx.baselineprofile.gradle.utils.buildAndFailAndAssertThatOutput
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
 class BaselineProfileProducerPluginTest {
 
-    // Unit test will be minimal because the producer plugin is applied to an android test module,
-    // that requires a working target application. Testing will be covered only by integration tests.
-
-    private val rootFolder = TemporaryFolder().also { it.create() }
-
     @get:Rule
-    val producerProjectSetup = ProjectSetupRule(rootFolder.root)
+    val projectSetup = BaselineProfileProjectSetupRule()
 
-    @get:Rule
-    val appTargetProjectSetup = ProjectSetupRule(rootFolder.root)
-
-    private lateinit var producerModuleName: String
-    private lateinit var appTargetModuleName: String
-    private lateinit var gradleRunner: GradleRunner
-
-    @Before
-    fun setUp() {
-        producerModuleName = producerProjectSetup.rootDir.relativeTo(rootFolder.root).name
-        appTargetModuleName = appTargetProjectSetup.rootDir.relativeTo(rootFolder.root).name
-
-        rootFolder.newFile("settings.gradle").writeText(
-            """
-            include '$producerModuleName'
-            include '$appTargetModuleName'
-        """.trimIndent()
-        )
-        gradleRunner = GradleRunner.create()
-            .withProjectDir(producerProjectSetup.rootDir)
-            .withPluginClasspath()
-    }
+    private val emptyReleaseVariantProfile = VariantProfile(
+        flavor = null,
+        buildType = "release",
+        profileLines = listOf()
+    )
 
     @Test
     fun verifyTasksWithAndroidTestPlugin() {
-        appTargetProjectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id("com.android.application")
-                    id("androidx.baselineprofile.apptarget")
-                }
-                android {
-                    namespace 'com.example.namespace'
-                }
-            """.trimIndent(),
-            suffix = ""
-        )
-        producerProjectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id("com.android.test")
-                    id("androidx.baselineprofile.producer")
-                }
-                android {
-                    targetProjectPath = ":$appTargetModuleName"
-                    namespace 'com.example.namespace.test'
-                }
-                tasks.register("mergeNonMinifiedReleaseTestResultProtos") { println("Stub") }
-            """.trimIndent(),
-            suffix = ""
+        projectSetup.appTarget.setup()
+        projectSetup.producer.setup(
+            variantProfiles = listOf(emptyReleaseVariantProfile),
+            targetProject = projectSetup.appTarget
         )
 
-        gradleRunner
-            .withArguments("tasks", "--stacktrace")
-            .build()
-            .output
-            .let {
-                assertThat(it).contains("connectedNonMinifiedReleaseAndroidTest - ")
-                assertThat(it).contains("collectNonMinifiedReleaseBaselineProfile - ")
+        projectSetup.producer.gradleRunner.buildAndAssertThatOutput("tasks") {
+            contains("connectedNonMinifiedReleaseAndroidTest - ")
+            contains("collectNonMinifiedReleaseBaselineProfile - ")
+        }
+    }
+
+    @Test
+    fun nonExistingManagedDeviceShouldThrowError() {
+        projectSetup.appTarget.setup()
+        projectSetup.producer.setup(
+            variantProfiles = listOf(emptyReleaseVariantProfile),
+            targetProject = projectSetup.appTarget,
+            managedDevices = listOf(),
+            baselineProfileBlock = """
+                managedDevices = ["nonExisting"]
+            """.trimIndent()
+        )
+
+        projectSetup.producer.gradleRunner.buildAndFailAndAssertThatOutput("tasks") {
+            contains("It wasn't possible to determine the test task for managed device")
+        }
+    }
+
+    @Test
+    fun existingManagedDeviceShouldCreateCollectTaskDependingOnManagedDeviceTask() {
+        projectSetup.appTarget.setup()
+        projectSetup.producer.setup(
+            variantProfiles = listOf(emptyReleaseVariantProfile),
+            targetProject = projectSetup.appTarget,
+            managedDevices = listOf("somePixelDevice"),
+            baselineProfileBlock = """
+                managedDevices = ["somePixelDevice"]
+            """.trimIndent()
+        )
+
+        projectSetup
+            .producer
+            .gradleRunner
+            .buildAndAssertThatOutput(
+                "collectNonMinifiedReleaseBaselineProfile",
+                "--dry-run"
+            ) {
+                contains(
+                    ":${projectSetup.appTarget.name}:packageNonMinifiedRelease")
+                contains(
+                    ":${projectSetup.producer.name}:somePixelDeviceNonMinifiedReleaseAndroidTest")
+                contains(
+                    ":${projectSetup.producer.name}:connectedNonMinifiedReleaseAndroidTest")
+                contains(
+                    ":${projectSetup.producer.name}:collectNonMinifiedReleaseBaselineProfile")
             }
     }
 }
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt
new file mode 100644
index 0000000..d0df57b
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/BaselineProfileProjectSetupRule.kt
@@ -0,0 +1,421 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.baselineprofile.gradle.utils
+
+import androidx.testutils.gradle.ProjectSetupRule
+import com.google.testing.platform.proto.api.core.LabelProto
+import com.google.testing.platform.proto.api.core.PathProto
+import com.google.testing.platform.proto.api.core.TestArtifactProto
+import com.google.testing.platform.proto.api.core.TestResultProto
+import com.google.testing.platform.proto.api.core.TestStatusProto
+import com.google.testing.platform.proto.api.core.TestSuiteResultProto
+import java.io.File
+import org.gradle.configurationcache.extensions.capitalized
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.rules.ExternalResource
+import org.junit.rules.RuleChain
+import org.junit.rules.TemporaryFolder
+import org.junit.runner.Description
+import org.junit.runners.model.Statement
+
+internal const val ANDROID_APPLICATION_PLUGIN = "com.android.application"
+internal const val ANDROID_LIBRARY_PLUGIN = "com.android.library"
+internal const val ANDROID_TEST_PLUGIN = "com.android.test"
+
+class BaselineProfileProjectSetupRule : ExternalResource() {
+
+    /**
+     * Root folder for the project setup that contains 3 modules.
+     */
+    val rootFolder = TemporaryFolder().also { it.create() }
+
+    /**
+     * Represents a module with the app target plugin applied.
+     */
+    val appTarget by lazy {
+        AppTargetModule(
+            rule = appTargetSetupRule,
+            name = appTargetName,
+        )
+    }
+
+    /**
+     * Represents a module with the consumer plugin applied.
+     */
+    val consumer by lazy {
+        ConsumerModule(
+            rule = consumerSetupRule,
+            name = consumerName,
+            producerName = producerName
+        )
+    }
+
+    /**
+     * Represents a module with the producer plugin applied.
+     */
+    val producer by lazy {
+        ProducerModule(
+            rule = producerSetupRule,
+            name = producerName,
+            tempFolder = tempFolder,
+            consumer = consumer
+        )
+    }
+
+    // Temp folder for temp generated files that need to be referenced by a module.
+    private val tempFolder by lazy { File(rootFolder.root, "temp").apply { mkdirs() } }
+
+    // Project setup rules
+    private val appTargetSetupRule by lazy { ProjectSetupRule(rootFolder.root) }
+    private val consumerSetupRule by lazy { ProjectSetupRule(rootFolder.root) }
+    private val producerSetupRule by lazy { ProjectSetupRule(rootFolder.root) }
+
+    // Module names (generated automatically)
+    private val appTargetName: String by lazy {
+        appTargetSetupRule.rootDir.relativeTo(rootFolder.root).name
+    }
+    private val consumerName: String by lazy {
+        consumerSetupRule.rootDir.relativeTo(rootFolder.root).name
+    }
+    private val producerName: String by lazy {
+        producerSetupRule.rootDir.relativeTo(rootFolder.root).name
+    }
+
+    override fun apply(base: Statement, description: Description): Statement {
+        return RuleChain
+            .outerRule(appTargetSetupRule)
+            .around(producerSetupRule)
+            .around(consumerSetupRule)
+            .around { b, _ -> applyInternal(b) }
+            .apply(base, description)
+    }
+
+    private fun applyInternal(base: Statement) = object : Statement() {
+        override fun evaluate() {
+
+            // Creates the main settings.gradle
+            rootFolder.newFile("settings.gradle").writeText(
+                """
+                include '$appTargetName'
+                include '$producerName'
+                include '$consumerName'
+            """.trimIndent()
+            )
+
+            // Copies test project data
+            mapOf(
+                "app-target" to appTargetSetupRule,
+                "consumer" to consumerSetupRule,
+                "producer" to producerSetupRule
+            ).forEach { (folder, project) ->
+                File("src/test/test-data", folder)
+                    .apply { deleteOnExit() }
+                    .copyRecursively(project.rootDir)
+            }
+
+            base.evaluate()
+        }
+    }
+}
+
+data class VariantProfile(
+    val flavor: String?,
+    val buildType: String = "release",
+    val profileLines: List<String> = listOf()
+) {
+    val nonMinifiedVariant = "${flavor ?: ""}NonMinified${buildType.capitalized()}"
+}
+
+interface Module {
+
+    val name: String
+    val rule: ProjectSetupRule
+    val rootDir: File
+        get() = rule.rootDir
+    val gradleRunner: GradleRunner
+        get() = GradleRunner.create().withProjectDir(rule.rootDir).withPluginClasspath()
+
+    fun setBuildGradle(buildGradleContent: String) =
+        rule.writeDefaultBuildGradle(
+            prefix = buildGradleContent,
+            suffix = """
+                $GRADLE_CODE_PRINT_TASK
+            """.trimIndent()
+        )
+}
+
+class AppTargetModule(
+    override val rule: ProjectSetupRule,
+    override val name: String,
+) : Module {
+
+    fun setup() {
+        setBuildGradle(
+            """
+                plugins {
+                    id("com.android.application")
+                    id("androidx.baselineprofile.apptarget")
+                }
+                android {
+                    namespace 'com.example.namespace'
+                }
+            """.trimIndent()
+        )
+    }
+}
+
+class ProducerModule(
+    override val rule: ProjectSetupRule,
+    override val name: String,
+    private val tempFolder: File,
+    private val consumer: Module
+) : Module {
+
+    fun setupWithFreeAndPaidFlavors(
+        freeReleaseProfileLines: List<String>,
+        paidReleaseProfileLines: List<String>,
+    ) {
+        setup(
+            variantProfiles = listOf(
+                VariantProfile(
+                    flavor = "free",
+                    buildType = "release",
+                    profileLines = freeReleaseProfileLines
+                ),
+                VariantProfile(
+                    flavor = "paid",
+                    buildType = "release",
+                    profileLines = paidReleaseProfileLines
+                ),
+            )
+        )
+    }
+
+    fun setup(
+        variantProfiles: List<VariantProfile> = listOf(
+            VariantProfile(
+                flavor = null,
+                buildType = "release",
+                profileLines = listOf(
+                    Fixtures.CLASS_1_METHOD_1,
+                    Fixtures.CLASS_2_METHOD_2,
+                    Fixtures.CLASS_2,
+                    Fixtures.CLASS_1
+                )
+            )
+        ),
+        baselineProfileBlock: String = "",
+        additionalGradleCodeBlock: String = "",
+        targetProject: Module = consumer,
+        managedDevices: List<String> = listOf()
+    ) {
+        val managedDevicesBlock = """
+            testOptions.managedDevices.devices {
+            ${
+            managedDevices.joinToString("\n") {
+                """
+                $it(ManagedVirtualDevice) {
+                    device = "Pixel 6"
+                    apiLevel = 31
+                    systemImageSource = "aosp"
+                }
+
+            """.trimIndent()
+            }
+        }
+            }
+        """.trimIndent()
+
+        val flavorsBlock = """
+            productFlavors {
+                flavorDimensions = ["version"]
+                ${
+            variantProfiles
+                .filter { !it.flavor.isNullOrBlank() }
+                .joinToString("\n") { " ${it.flavor} { dimension \"version\" } " }
+        }
+            }
+        """.trimIndent()
+
+        val buildTypesBlock = """
+            buildTypes {
+                ${
+            variantProfiles
+                .filter { it.buildType.isNotBlank() && it.buildType != "release" }
+                .joinToString("\n") { " ${it.buildType} { initWith(debug) } " }
+        }
+            }
+        """.trimIndent()
+
+        val disableConnectedAndroidTestsBlock = variantProfiles.joinToString("\n") {
+
+            // Creates a folder to use as results dir
+            val outputDir = File(tempFolder, it.nonMinifiedVariant).apply { mkdirs() }
+
+            // Writes the fake test result proto in it, with the given lines
+            writeFakeTestResultsProto(
+                outputDir = outputDir,
+                profileLines = it.profileLines
+            )
+
+            // Gradle script to injects a fake and disable the actual task execution for
+            // android test
+            """
+            afterEvaluate {
+                project.tasks.named("connected${it.nonMinifiedVariant.capitalized()}AndroidTest") {
+                    it.resultsDir.set(new File("${outputDir.absolutePath}"))
+                    onlyIf { false }
+                }
+            }
+
+                """.trimIndent()
+        }
+
+        setBuildGradle(
+            """
+                import com.android.build.api.dsl.ManagedVirtualDevice
+
+                plugins {
+                    id("com.android.test")
+                    id("androidx.baselineprofile.producer")
+                }
+
+                android {
+                    $flavorsBlock
+
+                    $buildTypesBlock
+
+                    $managedDevicesBlock
+
+                    namespace 'com.example.namespace.test'
+                    targetProjectPath = ":${targetProject.name}"
+                }
+
+                dependencies {
+                }
+
+                baselineProfile {
+                    $baselineProfileBlock
+                }
+
+                $disableConnectedAndroidTestsBlock
+
+                $additionalGradleCodeBlock
+
+            """.trimIndent()
+        )
+    }
+
+    private fun writeFakeTestResultsProto(
+        outputDir: File,
+        profileLines: List<String>
+    ) {
+
+        val generatedProfileFile = File
+            .createTempFile("fake-baseline-prof-", ".txt")
+            .apply { writeText(profileLines.joinToString(System.lineSeparator())) }
+
+        val testResultProto = TestResultProto.TestResult.newBuilder()
+            .addOutputArtifact(
+                TestArtifactProto.Artifact.newBuilder()
+                    .setLabel(
+                        LabelProto.Label.newBuilder()
+                            .setLabel("additionaltestoutput.benchmark.trace")
+                            .build()
+                    )
+                    .setSourcePath(
+                        PathProto.Path.newBuilder()
+                            .setPath(generatedProfileFile.absolutePath)
+                            .build()
+                    )
+                    .build()
+            )
+            .build()
+
+        val testSuiteResultProto = TestSuiteResultProto.TestSuiteResult.newBuilder()
+            .setTestStatus(TestStatusProto.TestStatus.PASSED)
+            .addTestResult(testResultProto)
+            .build()
+
+        File(outputDir, "test-result.pb")
+            .apply { outputStream().use { testSuiteResultProto.writeTo(it) } }
+    }
+}
+
+class ConsumerModule(
+    override val rule: ProjectSetupRule,
+    override val name: String,
+    private val producerName: String
+) : Module {
+
+    fun setup(
+        androidPlugin: String,
+        flavors: Boolean = false,
+        dependencyOnProducerProject: Boolean = true,
+        buildTypeAnotherRelease: Boolean = false,
+        addAppTargetPlugin: Boolean = androidPlugin == ANDROID_APPLICATION_PLUGIN,
+        baselineProfileBlock: String = "",
+        additionalGradleCodeBlock: String = "",
+    ) {
+        val flavorsBlock = """
+            productFlavors {
+                flavorDimensions = ["version"]
+                free { dimension "version" }
+                paid { dimension "version" }
+            }
+
+        """.trimIndent()
+
+        val buildTypeAnotherReleaseBlock = """
+            buildTypes {
+                anotherRelease { initWith(release) }
+            }
+
+        """.trimIndent()
+
+        val dependencyOnProducerProjectBlock = """
+            dependencies {
+                baselineProfile(project(":$producerName"))
+            }
+
+        """.trimIndent()
+
+        setBuildGradle(
+            """
+                plugins {
+                    id("$androidPlugin")
+                    id("androidx.baselineprofile.consumer")
+                    ${if (addAppTargetPlugin) "id(\"androidx.baselineprofile.apptarget\")" else ""}
+                }
+                android {
+                    namespace 'com.example.namespace'
+                    ${if (flavors) flavorsBlock else ""}
+                    ${if (buildTypeAnotherRelease) buildTypeAnotherReleaseBlock else ""}
+                }
+
+               ${if (dependencyOnProducerProject) dependencyOnProducerProjectBlock else ""}
+
+                baselineProfile {
+                    $baselineProfileBlock
+                }
+
+                $additionalGradleCodeBlock
+
+            """.trimIndent()
+        )
+    }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Fixtures.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Fixtures.kt
new file mode 100644
index 0000000..f9ee2d5c
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/Fixtures.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.baselineprofile.gradle.utils
+
+object Fixtures {
+    const val CLASS_1 = "Lcom/sample/Activity;"
+    const val CLASS_1_METHOD_1 = "HSPLcom/sample/Activity;-><init>()V"
+    const val CLASS_1_METHOD_2 = "HSPLcom/sample/Activity;->onCreate(Landroid/os/Bundle;)V"
+    const val CLASS_2 = "Lcom/sample/Utils;"
+    const val CLASS_2_METHOD_1 = "HSLcom/sample/Utils;-><init>()V"
+    const val CLASS_2_METHOD_2 = "HLcom/sample/Utils;->someMethod()V"
+    const val CLASS_2_METHOD_3 = "HLcom/sample/Utils;->someOtherMethod()V"
+    const val CLASS_2_METHOD_4 = "HSLcom/sample/Utils;->someOtherMethod()V"
+    const val CLASS_2_METHOD_5 = "HSPLcom/sample/Utils;->someOtherMethod()V"
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
index 6e63906..051778b 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/utils/TestUtils.kt
@@ -20,13 +20,13 @@
 import com.google.common.truth.Truth.assertThat
 import org.gradle.testkit.runner.GradleRunner
 
-internal const val GRADLE_CODE_PRINT_TASK = """
+internal val GRADLE_CODE_PRINT_TASK = """
     abstract class PrintTask extends DefaultTask {
         @Input abstract Property<String> getText()
         @TaskAction void exec() { println(getText().get()) }
     }
 
-"""
+    """.trimIndent()
 
 internal fun GradleRunner.build(vararg arguments: String, block: (String) -> (Unit)) {
     this
@@ -36,9 +36,24 @@
         .let(block)
 }
 
+internal fun GradleRunner.buildAndFail(vararg arguments: String, block: (String) -> (Unit)) {
+    this
+        .withArguments(*arguments, "--stacktrace")
+        .buildAndFail()
+        .output
+        .let(block)
+}
+
 internal fun GradleRunner.buildAndAssertThatOutput(
     vararg arguments: String,
     assertBlock: StringSubject.() -> (Unit)
 ) {
     this.build(*arguments) { assertBlock(assertThat(it)) }
 }
+
+internal fun GradleRunner.buildAndFailAndAssertThatOutput(
+    vararg arguments: String,
+    assertBlock: StringSubject.() -> (Unit)
+) {
+    this.buildAndFail(*arguments) { assertBlock(assertThat(it)) }
+}
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/wrapper/BaselineProfileWrapperPluginTest.kt b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/wrapper/BaselineProfileWrapperPluginTest.kt
index 62dd058..372dfe36 100644
--- a/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/wrapper/BaselineProfileWrapperPluginTest.kt
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/kotlin/androidx/baselineprofile/gradle/wrapper/BaselineProfileWrapperPluginTest.kt
@@ -16,147 +16,115 @@
 
 package androidx.baselineprofile.gradle.wrapper
 
-import androidx.testutils.gradle.ProjectSetupRule
+import androidx.baselineprofile.gradle.utils.BaselineProfileProjectSetupRule
+import androidx.baselineprofile.gradle.utils.Module
 import com.google.common.truth.IterableSubject
 import com.google.common.truth.Truth.assertThat
-import org.gradle.testkit.runner.GradleRunner
-import org.junit.Before
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.TemporaryFolder
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 
 @RunWith(JUnit4::class)
 class BaselineProfileWrapperPluginTest {
 
-    private val rootFolder = TemporaryFolder().also { it.create() }
-
     @get:Rule
-    val appTargetProjectSetup = ProjectSetupRule(rootFolder.root)
-    @get:Rule
-    val consumerProjectSetup = ProjectSetupRule(rootFolder.root)
-    @get:Rule
-    val producerProjectSetup = ProjectSetupRule(rootFolder.root)
-
-    private lateinit var appTargetModuleName: String
-    private lateinit var producerModuleName: String
-    private lateinit var consumerModuleName: String
-
-    @Before
-    fun setUp() {
-        appTargetModuleName = appTargetProjectSetup.rootDir.relativeTo(rootFolder.root).name
-        producerModuleName = consumerProjectSetup.rootDir.relativeTo(rootFolder.root).name
-        consumerModuleName = producerProjectSetup.rootDir.relativeTo(rootFolder.root).name
-
-        rootFolder.newFile("settings.gradle").writeText(
-            """
-            include '$appTargetModuleName'
-            include '$producerModuleName'
-            include '$consumerModuleName'
-        """.trimIndent()
-        )
-    }
+    val projectSetup = BaselineProfileProjectSetupRule()
 
     @Test
     fun testWrapperGeneratingForApplication() {
-        consumerProjectSetup.setupProject(
+        projectSetup.consumer.setBuildGradle(
             """
                 plugins {
                     id("com.android.application")
                     id("androidx.baselineprofile")
                 }
                 android { namespace 'com.example.namespace.test' }
-                dependencies { baselineProfile(project(":$producerModuleName")) }
+                dependencies { baselineProfile(project(":${projectSetup.producer.name}")) }
+
+                $taskPrintPlugins
             """.trimIndent()
         )
-        producerProjectSetup.setupProject(
+        projectSetup.producer.setBuildGradle(
             """
                 plugins {
                     id("com.android.test")
                     id("androidx.baselineprofile")
                 }
                 android {
-                    targetProjectPath = ":$consumerModuleName"
+                    targetProjectPath = ":${projectSetup.consumer.name}"
                     namespace 'com.example.namespace.test'
                 }
+
+                $taskPrintPlugins
             """.trimIndent()
         )
 
-        consumerProjectSetup.printPluginsAndAssertOutput {
+        projectSetup.consumer.printPluginsAndAssertOutput {
             contains("class $CLASS_APP_TARGET_PLUGIN")
             contains("class $CLASS_CONSUMER_PLUGIN")
         }
-        producerProjectSetup.printPluginsAndAssertOutput {
+        projectSetup.producer.printPluginsAndAssertOutput {
             contains("class $CLASS_PRODUCER_PLUGIN")
         }
     }
 
     @Test
     fun testWrapperGeneratingForLibraries() {
-        appTargetProjectSetup.setupProject(
+        projectSetup.appTarget.setBuildGradle(
             """
                 plugins {
                     id("com.android.application")
                     id("androidx.baselineprofile")
                 }
                 android { namespace 'com.example.namespace.test' }
+
+                $taskPrintPlugins
             """.trimIndent()
         )
-        consumerProjectSetup.setupProject(
+        projectSetup.consumer.setBuildGradle(
             """
                 plugins {
-                    id("com.android.application")
+                    id("com.android.library")
                     id("androidx.baselineprofile")
                 }
                 android { namespace 'com.example.namespace.test' }
-                dependencies { baselineProfile(project(":$producerModuleName")) }
+                dependencies { baselineProfile(project(":${projectSetup.producer.name}")) }
+
+                $taskPrintPlugins
             """.trimIndent()
         )
-        producerProjectSetup.setupProject(
+        projectSetup.producer.setBuildGradle(
             """
                 plugins {
                     id("com.android.test")
                     id("androidx.baselineprofile")
                 }
                 android {
-                    targetProjectPath = ":$consumerModuleName"
+                    targetProjectPath = ":${projectSetup.consumer.name}"
                     namespace 'com.example.namespace.test'
                 }
+
+                $taskPrintPlugins
             """.trimIndent()
         )
 
-        appTargetProjectSetup.printPluginsAndAssertOutput {
+        projectSetup.appTarget.printPluginsAndAssertOutput {
             contains("class $CLASS_APP_TARGET_PLUGIN")
             contains("class $CLASS_CONSUMER_PLUGIN")
         }
-        producerProjectSetup.printPluginsAndAssertOutput {
+        projectSetup.producer.printPluginsAndAssertOutput {
             contains("class $CLASS_PRODUCER_PLUGIN")
         }
-        consumerProjectSetup.printPluginsAndAssertOutput {
-            contains("class $CLASS_APP_TARGET_PLUGIN")
+        projectSetup.consumer.printPluginsAndAssertOutput {
             contains("class $CLASS_CONSUMER_PLUGIN")
         }
     }
 
-    private fun ProjectSetupRule.setupProject(buildGradleContent: String) {
-        writeDefaultBuildGradle(
-            prefix = """
-                $buildGradleContent
-
-                $taskPrintPlugins
-            """.trimIndent(),
-            suffix = ""
-        )
-    }
-
-    private fun ProjectSetupRule.printPluginsAndAssertOutput(
+    private fun Module.printPluginsAndAssertOutput(
         assertBlock: IterableSubject.() -> (Unit)
     ) {
-        val output = GradleRunner
-            .create()
-            .withProjectDir(rootDir)
-            .withPluginClasspath()
+        val output = gradleRunner
             .withArguments("printPlugins", "--stacktrace")
             .build()
             .output
@@ -173,7 +141,8 @@
     "androidx.baselineprofile.gradle.producer.BaselineProfileProducerPlugin"
 
 private val taskPrintPlugins = """
-tasks.register("printPlugins") {
-    project.plugins.each { println(it.class) }
+tasks.register("printPlugins", PrintTask) { t ->
+    def pluginsList = project.plugins.collect { it.class.toString() }.join("\n")
+    t.text.set(pluginsList)
 }
-""".trimIndent()
\ No newline at end of file
+""".trimIndent()
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/test-data/IGNORE_CHECKSTYLE b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/IGNORE_CHECKSTYLE
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/IGNORE_CHECKSTYLE
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/test-data/app-target/src/main/AndroidManifest.xml b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/app-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3908578
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/app-target/src/main/AndroidManifest.xml
@@ -0,0 +1,22 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application android:label="BaselineProfileApp">
+        <profileable android:shell="true" />
+    </application>
+</manifest>
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/test-data/consumer/src/main/AndroidManifest.xml b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/consumer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..3c9246e
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/consumer/src/main/AndroidManifest.xml
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  Copyright 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android">
+
+    <application android:label="BaselineProfileApp">
+        <profileable android:shell="true" />
+    </application>
+</manifest>
diff --git a/benchmark/baseline-profile-gradle-plugin/src/test/test-data/producer/src/main/AndroidManifest.xml b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/producer/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..df45767
--- /dev/null
+++ b/benchmark/baseline-profile-gradle-plugin/src/test/test-data/producer/src/main/AndroidManifest.xml
@@ -0,0 +1,17 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  Copyright 2019 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+<manifest xmlns:android="http://schemas.android.com/apk/res/android" />
\ No newline at end of file
diff --git a/benchmark/benchmark-common/api/public_plus_experimental_current.txt b/benchmark/benchmark-common/api/public_plus_experimental_current.txt
index 1d9e4cd..a5a3dc8 100644
--- a/benchmark/benchmark-common/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-common/api/public_plus_experimental_current.txt
@@ -31,7 +31,7 @@
   @kotlin.RequiresOptIn @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION}) public @interface ExperimentalPerfettoCaptureApi {
   }
 
-  @RequiresApi(21) @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public final class PerfettoTrace {
+  @RequiresApi(23) @androidx.benchmark.perfetto.ExperimentalPerfettoCaptureApi public final class PerfettoTrace {
     ctor public PerfettoTrace(String path);
     method public String getPath();
     method public static void record(String fileLabel, optional java.util.List<java.lang.String> appTagPackages, optional String? userspaceTracingPackage, optional kotlin.jvm.functions.Function1<? super androidx.benchmark.perfetto.PerfettoTrace,kotlin.Unit>? traceCallback, kotlin.jvm.functions.Function0<kotlin.Unit> block);
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
index 17134b9..95b10d3 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoHelperTest.kt
@@ -33,7 +33,7 @@
 import kotlin.test.assertTrue
 
 @LargeTest
-@SdkSuppress(minSdkVersion = 21)
+@SdkSuppress(minSdkVersion = 23)
 @RunWith(AndroidJUnit4::class)
 class PerfettoHelperTest {
     @Before
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
index 73d01c3..8eba619 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/PerfettoTraceTest.kt
@@ -32,7 +32,7 @@
 @OptIn(ExperimentalPerfettoCaptureApi::class)
 @LargeTest
 @RunWith(AndroidJUnit4::class)
-@SdkSuppress(minSdkVersion = 21)
+@SdkSuppress(minSdkVersion = 23)
 class PerfettoTraceTest {
     @Test
     fun record_basic() {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
index 8cc6dde..26a4da0 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCapture.kt
@@ -42,13 +42,13 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@RequiresApi(21)
+@RequiresApi(23)
 public class PerfettoCapture(
     /**
      * Bundled is available above API 28, but we default to using unbundled as well on API 29, as
      * ProcessStatsConfig.scan_all_processes_on_start isn't supported on the bundled version.
      */
-    unbundled: Boolean = Build.VERSION.SDK_INT in 21..29
+    unbundled: Boolean = Build.VERSION.SDK_INT <= 29
 ) {
 
     private val helper: PerfettoHelper = PerfettoHelper(unbundled)
@@ -98,7 +98,7 @@
      * Enables Perfetto SDK tracing in an app if present. Provides required binary dependencies to
      * the app if they're missing and the [provideBinariesIfMissing] parameter is set to `true`.
      */
-    @RequiresApi(Build.VERSION_CODES.R) // TODO(234351579): Support API < 30
+    @RequiresApi(30) // TODO(234351579): Support API < 30
     fun enableAndroidxTracingPerfetto(
         targetPackage: String,
         provideBinariesIfMissing: Boolean
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
index 9725c61..010cde4 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
@@ -28,7 +28,7 @@
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 
 /**
- * Wrapper for [PerfettoCapture] which does nothing below L.
+ * Wrapper for [PerfettoCapture] which does nothing below API 23.
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 class PerfettoCaptureWrapper {
@@ -36,7 +36,7 @@
     private val TRACE_ENABLE_PROP = "persist.traced.enable"
 
     init {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+        if (Build.VERSION.SDK_INT >= 23) {
             capture = PerfettoCapture()
         }
     }
@@ -52,16 +52,16 @@
         var inUse = false
     }
 
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    @RequiresApi(23)
     private fun start(
         appTagPackages: List<String>,
         userspaceTracingPackage: String?
     ): Boolean {
         capture?.apply {
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            if (Build.VERSION.SDK_INT >= 23) {
                 Log.d(LOG_TAG, "Recording perfetto trace")
                 if (userspaceTracingPackage != null &&
-                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+                    Build.VERSION.SDK_INT >= 30
                 ) {
                     val result = enableAndroidxTracingPerfetto(
                         targetPackage = userspaceTracingPackage,
@@ -76,7 +76,7 @@
         return true
     }
 
-    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    @RequiresApi(23)
     private fun stop(traceLabel: String): String {
         return Outputs.writeFile(
             fileName = "${traceLabel}_${dateToFileName()}.perfetto-trace",
@@ -100,7 +100,7 @@
         block: () -> Unit
     ): String? {
         // skip if Perfetto not supported, or on Cuttlefish (where tracing doesn't work)
-        if (Build.VERSION.SDK_INT < 21 || !isAbiSupported()) {
+        if (Build.VERSION.SDK_INT < 23 || !isAbiSupported()) {
             block()
             return null
         }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
index e3a3d72..a2e8f73 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoHelper.kt
@@ -37,7 +37,7 @@
  * @suppress
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-@RequiresApi(21)
+@RequiresApi(23)
 public class PerfettoHelper(
     private val unbundled: Boolean = Build.VERSION.SDK_INT < LOWEST_BUNDLED_VERSION_SUPPORTED
 ) {
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoTrace.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoTrace.kt
index 98ed9d8..5e2c29a 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoTrace.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoTrace.kt
@@ -20,7 +20,7 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import java.io.File
 
-@RequiresApi(21)
+@RequiresApi(23)
 @ExperimentalPerfettoCaptureApi
 class PerfettoTrace(
     /**
diff --git a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
index aa35558..d8dcd99 100644
--- a/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
+++ b/benchmark/benchmark-junit4/src/main/java/androidx/benchmark/junit4/PerfettoTraceRule.kt
@@ -87,7 +87,7 @@
     ): Statement = object : Statement() {
         override fun evaluate() {
             val thisPackage = InstrumentationRegistry.getInstrumentation().context.packageName
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            if (Build.VERSION.SDK_INT >= 23) {
                 val label = "${description.className}_${description.methodName}"
                 PerfettoTrace.record(
                     fileLabel = label,
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
index fa830d2..c9c8608 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureTest.kt
@@ -34,7 +34,7 @@
 @RunWith(AndroidJUnit4::class)
 class PerfettoCaptureTest {
     @SdkSuppress(
-        minSdkVersion = 21,
+        minSdkVersion = 23,
         maxSdkVersion = LOWEST_BUNDLED_VERSION_SUPPORTED - 1
     )
     @SmallTest
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
index e7ea76a..b6bc2ca 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
@@ -47,7 +47,7 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
 
-private const val tracingPerfettoVersion = "1.0.0-alpha12" // TODO(224510255): get by 'reflection'
+private const val tracingPerfettoVersion = "1.0.0-alpha13" // TODO(224510255): get by 'reflection'
 private const val minSupportedSdk = Build.VERSION_CODES.R // TODO(234351579): Support API < 30
 
 @RunWith(Parameterized::class)
diff --git a/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle b/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
index 97cffb1..d8f0efa 100644
--- a/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
+++ b/benchmark/integration-tests/baselineprofile-flavors-consumer/build.gradle
@@ -48,19 +48,25 @@
 dependencies {
     implementation(libs.kotlinStdlib)
     implementation(libs.constraintLayout)
-
-    baselineProfile(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
 }
 
 baselineProfile {
     filter {
         include "androidx.benchmark.integration.baselineprofile.flavors.consumer.*"
     }
-    filter("free") {
-        include "androidx.benchmark.integration.baselineprofile.flavors.consumer.free.*"
-    }
-    filter("paid") {
-        include "androidx.benchmark.integration.baselineprofile.flavors.consumer.paid.*"
+    variants {
+        freeRelease {
+            filter {
+                include "androidx.benchmark.integration.baselineprofile.flavors.consumer.free.*"
+                from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
+            }
+        }
+        paidRelease {
+            filter {
+                include "androidx.benchmark.integration.baselineprofile.flavors.consumer.paid.*"
+                from(project(":benchmark:integration-tests:baselineprofile-flavors-producer"))
+            }
+        }
     }
 
     // Note that these are the default settings, just reported here to make it explicit.
diff --git a/biometric/biometric/src/main/res/values-pt-rPT/strings.xml b/biometric/biometric/src/main/res/values-pt-rPT/strings.xml
index 6e1bedd..4d9b0cc 100644
--- a/biometric/biometric/src/main/res/values-pt-rPT/strings.xml
+++ b/biometric/biometric/src/main/res/values-pt-rPT/strings.xml
@@ -26,17 +26,17 @@
     <string name="fingerprint_error_lockout" msgid="7291787166416782245">"Demasiadas tentativas. Tente mais tarde."</string>
     <string name="default_error_msg" msgid="4776854077120974966">"Erro desconhecido."</string>
     <string name="generic_error_user_canceled" msgid="7309881387583143581">"Autenticação cancelada pelo utilizador."</string>
-    <string name="confirm_device_credential_password" msgid="5912733858573823945">"Utilizar palavra-passe"</string>
+    <string name="confirm_device_credential_password" msgid="5912733858573823945">"Usar palavra-passe"</string>
     <string name="generic_error_no_device_credential" msgid="3791785319221634505">"Nenhum PIN, padrão ou palavra-passe definidos."</string>
     <string name="generic_error_no_keyguard" msgid="1807436368654974044">"Este dispositivo não suporta o PIN, o padrão ou a palavra-passe."</string>
     <string name="fingerprint_dialog_icon_description" msgid="5462024216548165325">"Ícone de impressão digital"</string>
-    <string name="use_fingerprint_label" msgid="6961788485681412417">"Utilizar a impressão digital"</string>
-    <string name="use_face_label" msgid="6533512708069459542">"Utilizar o rosto"</string>
-    <string name="use_biometric_label" msgid="6524145989441579428">"Utilizar a biometria"</string>
-    <string name="use_screen_lock_label" msgid="5459869335976243512">"Utilizar o bloqueio de ecrã"</string>
-    <string name="use_fingerprint_or_screen_lock_label" msgid="7577690399303139443">"Utilizar o bloqueio de ecrã ou a impressão digital"</string>
-    <string name="use_face_or_screen_lock_label" msgid="2116180187159450292">"Utilizar o rosto ou o bloqueio de ecrã"</string>
-    <string name="use_biometric_or_screen_lock_label" msgid="5385448280139639016">"Utilizar a biometria ou o bloqueio de ecrã"</string>
+    <string name="use_fingerprint_label" msgid="6961788485681412417">"Usar a impressão digital"</string>
+    <string name="use_face_label" msgid="6533512708069459542">"Usar o rosto"</string>
+    <string name="use_biometric_label" msgid="6524145989441579428">"Usar a biometria"</string>
+    <string name="use_screen_lock_label" msgid="5459869335976243512">"Usar o bloqueio de ecrã"</string>
+    <string name="use_fingerprint_or_screen_lock_label" msgid="7577690399303139443">"Usar o bloqueio de ecrã ou a impressão digital"</string>
+    <string name="use_face_or_screen_lock_label" msgid="2116180187159450292">"Usar o rosto ou o bloqueio de ecrã"</string>
+    <string name="use_biometric_or_screen_lock_label" msgid="5385448280139639016">"Usar a biometria ou o bloqueio de ecrã"</string>
     <string name="fingerprint_prompt_message" msgid="7449360011861769080">"Utilize a impressão digital para continuar"</string>
     <string name="face_prompt_message" msgid="2282389249605674226">"Utilize o rosto para continuar"</string>
     <string name="biometric_prompt_message" msgid="1160635338192065472">"Utilize a biometria para continuar"</string>
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index 05930af..0737dd3 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -53,7 +53,6 @@
 import com.android.build.gradle.TestExtension
 import com.android.build.gradle.TestPlugin
 import com.android.build.gradle.TestedExtension
-import com.android.build.gradle.internal.tasks.AnalyticsRecordingTask
 import com.android.build.gradle.internal.tasks.CheckAarMetadataTask
 import com.android.build.gradle.internal.tasks.ListingFileRedirectTask
 import java.io.File
@@ -711,12 +710,6 @@
         // lives alongside the project's buildDir.
         externalNativeBuild.cmake.buildStagingDirectory =
             File(project.buildDir, "../nativeBuildStaging")
-
-        // disable analytics recording
-        // It's always out-of-date, and we don't release any apps in this repo
-        project.tasks.withType(AnalyticsRecordingTask::class.java).configureEach { task ->
-            task.enabled = false
-        }
     }
 
     /**
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
index b805725..5c45e4e 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
@@ -83,6 +83,14 @@
     @get:Option(option = "pullScreenshots", description = "true if screenshots should be pulled")
     abstract val pullScreenshots: Property<String>
 
+    @get:Optional
+    @get:Input
+    @get:Option(
+        option = "instrumentationArgs",
+        description = "instrumentation arguments to pass to FTL test runner"
+    )
+    abstract val instrumentationArgs: Property<String>
+
     @get:Input
     abstract val device: Property<String>
 
@@ -142,6 +150,8 @@
                     if (shouldPull) {
                         "/sdcard/Android/data/${apkPackageName.get()}/cache/androidx_screenshots"
                     } else null,
+                    if (instrumentationArgs.isPresent) "--environment-variables" else null,
+                    if (instrumentationArgs.isPresent) instrumentationArgs.get() else null,
                 )
             )
         }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index 4a8a0d2..68a974b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -25,6 +25,7 @@
 import androidx.build.enforceKtlintVersion
 import androidx.build.getAndroidJar
 import androidx.build.getBuildId
+import androidx.build.getCheckoutRoot
 import androidx.build.getDistributionDirectory
 import androidx.build.getKeystore
 import androidx.build.getLibraryByName
@@ -465,7 +466,9 @@
                 jvmSourcesDir = unzippedJvmSourcesDirectory
                 multiplatformSourcesDir = unzippedMultiplatformSourcesDirectory
                 docsProjectDir = File(project.rootDir, "docs-public")
-                dependenciesClasspath = project.getAndroidJar() + dependencyClasspath
+                dependenciesClasspath = dependencyClasspath +
+                    project.getAndroidJar() +
+                    project.getExtraCommonDependencies()
                 excludedPackages = hiddenPackages.toSet()
                 excludedPackagesForJava = hiddenPackagesJava
                 excludedPackagesForKotlin = emptySet()
@@ -745,3 +748,23 @@
         }
     }
 }
+
+private fun Project.getPrebuiltsExternalPath() =
+    File(project.getCheckoutRoot(), "prebuilts/androidx/external/")
+
+private fun Project.getExtraCommonDependencies(): FileCollection = files(
+    arrayOf(
+        File(
+            getPrebuiltsExternalPath(),
+            "org/jetbrains/kotlinx/kotlinx-coroutines-core/1.6.4/kotlinx-coroutines-core-1.6.4.jar"
+        ),
+        File(
+            getPrebuiltsExternalPath(),
+            "org/jetbrains/kotlinx/atomicfu/0.17.0/atomicfu-0.17.0.jar"
+        ),
+        File(
+           getPrebuiltsExternalPath(),
+            "com/squareup/okio/okio-jvm/3.1.0/okio-jvm-3.1.0.jar"
+        )
+    )
+)
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
index 51626cc..f3b7ee8 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/SupportConfig.kt
@@ -33,7 +33,7 @@
      * Either an integer value or a pre-release platform code, prefixed with "android-" (ex.
      * "android-28" or "android-Q") as you would see within the SDK's platforms directory.
      */
-    const val COMPILE_SDK_VERSION = "android-33-ext4"
+    const val COMPILE_SDK_VERSION = "android-33-ext5"
 
     /**
      * The Android SDK version to use for targetSdkVersion meta-data.
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
index 243d9e2..95d9e98 100644
--- a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/CameraControlAdapterDeviceTest.kt
@@ -389,6 +389,7 @@
         )
     }
 
+    @SdkSuppress(minSdkVersion = 22) // b/266740827
     @Test
     fun setZoomRatio_operationCanceledExceptionIfNoUseCase() {
         assertFutureFailedWithOperationCancellation(cameraControl.setZoomRatio(1.5f))
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
index f8bb7fb..fab238c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraFactoryAdapter.kt
@@ -30,6 +30,7 @@
 import androidx.camera.camera2.pipe.integration.config.DaggerCameraAppComponent
 import androidx.camera.camera2.pipe.integration.internal.CameraCompatibilityFilter
 import androidx.camera.camera2.pipe.integration.internal.CameraSelectionOptimizer
+import androidx.camera.core.CameraInfo
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.concurrent.CameraCoordinator.ConcurrentCameraModeListener
@@ -97,13 +98,11 @@
                 return mutableListOf()
             }
 
-            override fun getActiveConcurrentCameraSelectors(): MutableList<CameraSelector> {
+            override fun getActiveConcurrentCameraInfos(): MutableList<CameraInfo> {
                 return mutableListOf()
             }
 
-            override fun setActiveConcurrentCameraSelectors(
-                cameraSelectors: MutableList<CameraSelector>
-            ) {
+            override fun setActiveConcurrentCameraInfos(cameraInfos: MutableList<CameraInfo>) {
             }
 
             override fun getPairedConcurrentCameraId(cameraId: String): String? {
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 5c5be0e..7492aa0 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
@@ -19,16 +19,17 @@
 package androidx.camera.camera2.pipe.integration.adapter
 
 import android.annotation.SuppressLint
-import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraMetadata
-import android.os.Build
 import android.util.Range
 import android.util.Size
 import android.view.Surface
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
@@ -36,7 +37,6 @@
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
 import androidx.camera.camera2.pipe.integration.interop.Camera2CameraInfo
 import androidx.camera.camera2.pipe.integration.interop.ExperimentalCamera2Interop
-import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.core.CameraSelector
 import androidx.camera.core.CameraState
 import androidx.camera.core.ExposureState
@@ -45,7 +45,6 @@
 import androidx.camera.core.impl.CameraCaptureCallback
 import androidx.camera.core.impl.CameraInfoInternal
 import androidx.camera.core.impl.EncoderProfilesProvider
-import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.Quirks
 import androidx.camera.core.impl.Timebase
 import androidx.camera.core.impl.utils.CameraOrientationUtil
@@ -68,7 +67,8 @@
     private val cameraCallbackMap: CameraCallbackMap,
     private val focusMeteringControl: FocusMeteringControl,
     private val cameraQuirks: CameraQuirks,
-    private val encoderProfilesProviderAdapter: EncoderProfilesProviderAdapter
+    private val encoderProfilesProviderAdapter: EncoderProfilesProviderAdapter,
+    private val streamConfigurationMapCompat: StreamConfigurationMapCompat,
 ) : CameraInfoInternal {
     @OptIn(ExperimentalCamera2Interop::class)
     internal val camera2CameraInfo: Camera2CameraInfo by lazy {
@@ -92,8 +92,7 @@
     }
 
     override fun getSensorRotationDegrees(): Int = getSensorRotationDegrees(Surface.ROTATION_0)
-    override fun hasFlashUnit(): Boolean =
-        cameraProperties.metadata[CameraCharacteristics.FLASH_INFO_AVAILABLE]!!
+    override fun hasFlashUnit(): Boolean = cameraProperties.isFlashAvailable()
 
     override fun getSensorRotationDegrees(relativeRotation: Int): Int {
         val sensorOrientation: Int =
@@ -145,27 +144,13 @@
 
     @SuppressLint("ClassVerificationFailure")
     override fun getSupportedResolutions(format: Int): List<Size> {
-        val streamConfigurationMap =
-            cameraProperties.metadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]!!
-        return if (Build.VERSION.SDK_INT < 23 &&
-            format == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
-        ) {
-            streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)
-        } else {
-            streamConfigurationMap.getOutputSizes(format)
-        }?.toList() ?: emptyList()
+        return streamConfigurationMapCompat.getOutputSizes(format)?.toList() ?: emptyList()
     }
 
     @SuppressLint("ClassVerificationFailure")
     override fun getSupportedHighResolutions(format: Int): List<Size> {
-        val streamConfigurationMap =
-            cameraProperties.metadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]!!
-        if (Build.VERSION.SDK_INT >= 23) {
-            streamConfigurationMap.getHighResolutionOutputSizes(format)?.let {
-                return it.toList()
-            }
-        }
-        return emptyList()
+        return streamConfigurationMapCompat.getHighResolutionOutputSizes(format)?.toList()
+            ?: emptyList()
     }
 
     override fun toString(): String = "CameraInfoAdapter<$cameraConfig.cameraId>"
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
index 6f518f8..91b1c4f 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapter.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.hardware.camera2.CameraCaptureSession.CaptureCallback
 import android.hardware.camera2.CameraDevice
+import android.util.Size
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.core.Log.debug
 import androidx.camera.camera2.pipe.core.Log.info
@@ -36,6 +37,7 @@
 import androidx.camera.core.impl.ImageOutputConfig
 import androidx.camera.core.impl.MutableOptionsBundle
 import androidx.camera.core.impl.OptionsBundle
+import androidx.camera.core.impl.PreviewConfig
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.UseCaseConfig
 import androidx.camera.core.impl.UseCaseConfigFactory
@@ -90,10 +92,6 @@
                 CameraDevice.TEMPLATE_RECORD
             )
         }
-        if (captureType == CaptureType.PREVIEW) {
-            // Set the WYSIWYG preview for CAPTURE_TYPE_PREVIEW
-            sessionBuilder.setupHDRnet()
-        }
         mutableConfig.insertOption(
             UseCaseConfig.OPTION_DEFAULT_SESSION_CONFIG,
             sessionBuilder.build()
@@ -202,7 +200,11 @@
 
     object DefaultSessionOptionsUnpacker : SessionConfig.OptionUnpacker {
         @OptIn(ExperimentalCamera2Interop::class)
-        override fun unpack(config: UseCaseConfig<*>, builder: SessionConfig.Builder) {
+        override fun unpack(
+            resolution: Size,
+            config: UseCaseConfig<*>,
+            builder: SessionConfig.Builder
+        ) {
             val defaultSessionConfig = config.getDefaultSessionConfig( /*valueIfMissing=*/null)
 
             var implOptions: Config = OptionsBundle.emptyBundle()
@@ -222,6 +224,11 @@
             // Set any additional implementation options
             builder.setImplementationOptions(implOptions)
 
+            if (config is PreviewConfig) {
+                // Set the WYSIWYG preview for CAPTURE_TYPE_PREVIEW
+                builder.setupHDRnet(resolution)
+            }
+
             // Get Camera2 extended options
             val camera2Config = Camera2ImplConfig(config)
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
index 4739e54..25230dc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapter.kt
@@ -33,6 +33,7 @@
 import androidx.camera.core.UseCase
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
+import androidx.camera.core.streamsharing.StreamSharing
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
@@ -156,6 +157,7 @@
             Preview::class.java -> OutputStream.StreamUseCase.PREVIEW.value
             ImageCapture::class.java -> OutputStream.StreamUseCase.STILL_CAPTURE.value
             MediaCodec::class.java -> OutputStream.StreamUseCase.VIDEO_RECORD.value
+            StreamSharing::class.java -> OutputStream.StreamUseCase.VIDEO_RECORD.value
             else -> OutputStream.StreamUseCase.DEFAULT.value
         }
     }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
index 6733cc7..c8a98dc0 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/SupportedSurfaceCombination.kt
@@ -25,11 +25,12 @@
 import android.hardware.display.DisplayManager
 import android.media.CamcorderProfile
 import android.media.MediaRecorder
-import android.os.Build
 import android.util.Size
 import android.view.Display
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.core.impl.AttachedSurfaceInfo
 import androidx.camera.core.impl.EncoderProfilesProxy
 import androidx.camera.core.impl.ImageFormatConstants
@@ -74,6 +75,7 @@
     internal lateinit var surfaceSizeDefinition: SurfaceSizeDefinition
     private val displayManager: DisplayManager =
         (context.getSystemService(Context.DISPLAY_SERVICE) as DisplayManager)
+    private val streamConfigurationMapCompat = getStreamConfigurationMapCompat()
 
     init {
         checkCapabilities()
@@ -318,7 +320,7 @@
         } catch (e: NumberFormatException) {
             // The camera Id is not an integer because the camera may be a removable device. Use
             // StreamConfigurationMap to determine the RECORD size.
-            return getRecordSizeFromStreamConfigurationMap()
+            return getRecordSizeFromStreamConfigurationMapCompat()
         }
         var profiles: EncoderProfilesProxy? = null
         if (encoderProfilesProviderAdapter.hasProfile(cameraId)) {
@@ -332,9 +334,10 @@
     /**
      * Obtains the stream configuration map from camera meta data.
      */
-    private fun getStreamConfigurationMap(): StreamConfigurationMap {
-        return cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+    private fun getStreamConfigurationMapCompat(): StreamConfigurationMapCompat {
+        val map = cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
             ?: throw IllegalArgumentException("Cannot retrieve SCALER_STREAM_CONFIGURATION_MAP")
+        return StreamConfigurationMapCompat(map, OutputSizesCorrector(cameraMetadata))
     }
 
     /**
@@ -343,9 +346,8 @@
      *
      * @return Maximum supported video size.
      */
-    private fun getRecordSizeFromStreamConfigurationMap(): Size {
-        val map: StreamConfigurationMap = getStreamConfigurationMap()
-        val videoSizeArr = map.getOutputSizes(
+    private fun getRecordSizeFromStreamConfigurationMapCompat(): Size {
+        val videoSizeArr = streamConfigurationMapCompat.getOutputSizes(
             MediaRecorder::class.java
         ) ?: return RESOLUTION_480P
         Arrays.sort(videoSizeArr, CompareSizesByArea(true))
@@ -480,45 +482,22 @@
      * @return the max supported output size for the image format
      */
     internal fun getMaxOutputSizeByFormat(imageFormat: Int): Size {
-        val outputSizes = getAllOutputSizesByFormat(imageFormat)
-        return Collections.max(listOf(*outputSizes), CompareSizesByArea())
-    }
-
-    /**
-     * Get all output sizes for a given image format.
-     */
-    private fun doGetAllOutputSizesByFormat(imageFormat: Int): Array<Size> {
-        val map: StreamConfigurationMap = getStreamConfigurationMap()
-        val outputSizes = if (Build.VERSION.SDK_INT < 23 &&
-            imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
-        ) {
-            // This is a little tricky that 0x22 that is internal defined in
-            // StreamConfigurationMap.java to be equal to ImageFormat.PRIVATE that is public
-            // after Android level 23 but not public in Android L. Use {@link SurfaceTexture}
-            // or {@link MediaCodec} will finally mapped to 0x22 in StreamConfigurationMap to
-            // retrieve the output sizes information.
-            map.getOutputSizes(SurfaceTexture::class.java)
-        } else {
-            map.getOutputSizes(imageFormat)
-        }
-        // TODO(b/244477758): Exclude problematic sizes
-
-        // Sort the output sizes. The Comparator result must be reversed to have a descending order
-        // result.
-        Arrays.sort(outputSizes, CompareSizesByArea(true))
-        return outputSizes
-    }
-
-    /**
-     * Retrieves the output size associated with the given format.
-     */
-    private fun getAllOutputSizesByFormat(imageFormat: Int): Array<Size> {
-        var outputs: Array<Size>? = outputSizesCache[imageFormat]
-        if (outputs == null) {
-            outputs = doGetAllOutputSizesByFormat(imageFormat)
-            outputSizesCache[imageFormat] = outputs
-        }
-        return outputs
+        // Needs to retrieve the output size from the original stream configuration map without
+        // quirks applied.
+        val map: StreamConfigurationMap =
+            streamConfigurationMapCompat.toStreamConfigurationMap()
+        val outputSizes: Array<Size> =
+            if (imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) {
+                // This is a little tricky that 0x22 that is internal defined in
+                // StreamConfigurationMap.java to be equal to ImageFormat.PRIVATE that is public
+                // after Android level 23 but not public in Android L. Use {@link SurfaceTexture}
+                // or {@link MediaCodec} will finally mapped to 0x22 in StreamConfigurationMap to
+                // retrieve the output sizes information.
+                map.getOutputSizes(SurfaceTexture::class.java)
+            } else {
+                map.getOutputSizes(imageFormat)
+            }
+        return Collections.max(outputSizes.asList(), CompareSizesByArea())
     }
 
     /**
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java
index d9947f5..2a9dd47 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/adapter/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.camera2.pipe.integration.adapter;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
index 2f05905..612133c 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/CameraCompatModule.kt
@@ -19,12 +19,14 @@
 package androidx.camera.camera2.pipe.integration.compat
 
 import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
 import dagger.Module
 
 /** Dependency bindings for adding camera compat related classes (e.g. workarounds, quirks etc.) */
 @Module(
     includes = [
+        AutoFlashAEModeDisabler.Bindings::class,
         MeteringRegionCorrection.Bindings::class,
     ],
 )
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
new file mode 100644
index 0000000..128b3f7
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompat.kt
@@ -0,0 +1,110 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import android.util.Size
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import javax.inject.Inject
+
+/**
+ * Helper for accessing features in [StreamConfigurationMap] in a backwards compatible
+ * fashion.
+ *
+ * @param map [StreamConfigurationMap] class to wrap workarounds when output sizes are retrieved.
+ * @param outputSizesCorrector [OutputSizesCorrector] class to perform correction on sizes.
+ */
+@CameraScope
+@RequiresApi(21)
+class StreamConfigurationMapCompat @Inject constructor(
+    map: StreamConfigurationMap,
+    private val outputSizesCorrector: OutputSizesCorrector
+) {
+    private var impl: StreamConfigurationMapCompatImpl
+
+    init {
+        impl = if (Build.VERSION.SDK_INT >= 23) {
+            StreamConfigurationMapCompatApi23Impl(map)
+        } else {
+            StreamConfigurationMapCompatBaseImpl(map)
+        }
+    }
+
+    /**
+     * Get a list of sizes compatible with the requested image `format`.
+     *
+     *
+     * Output sizes related quirks will be applied onto the returned sizes list.
+     *
+     * @param format an image format from [ImageFormat] or [PixelFormat]
+     * @return an array of supported sizes, or `null` if the `format` is not a
+     * supported output
+     */
+    fun getOutputSizes(format: Int): Array<Size>? {
+        return outputSizesCorrector.applyQuirks(impl.getOutputSizes(format), format)
+    }
+
+    /**
+     * Get a list of sizes compatible with `klass` to use as an output.
+     *
+     *
+     * Output sizes related quirks will be applied onto the returned sizes list.
+     *
+     * @param klass a non-`null` [Class] object reference
+     * @return an array of supported sizes for [ImageFormat.PRIVATE] format,
+     * or `null` if the `klass` is not a supported output.
+     * @throws NullPointerException if `klass` was `null`
+     */
+    fun <T> getOutputSizes(klass: Class<T>): Array<Size>? {
+        return outputSizesCorrector.applyQuirks<T>(impl.getOutputSizes<T>(klass), klass)
+    }
+
+    /**
+     * Get a list of supported high resolution sizes, which cannot operate at full
+     * [CameraMetadata.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE] rate.
+     *
+     *
+     * Output sizes related quirks will be applied onto the returned sizes list.
+     *
+     * @param format an image format from [ImageFormat] or [PixelFormat]
+     * @return an array of supported sizes, or `null` if the `format` is not a
+     * supported output
+     */
+    fun getHighResolutionOutputSizes(format: Int): Array<Size>? {
+        return outputSizesCorrector.applyQuirks(impl.getHighResolutionOutputSizes(format), format)
+    }
+
+    /**
+     * Returns the [StreamConfigurationMap] represented by this object.
+     */
+    fun toStreamConfigurationMap(): StreamConfigurationMap {
+        return impl.unwrap()
+    }
+
+    internal interface StreamConfigurationMapCompatImpl {
+        fun getOutputSizes(format: Int): Array<Size>?
+        fun <T> getOutputSizes(klass: Class<T>): Array<Size>?
+        fun getHighResolutionOutputSizes(format: Int): Array<Size>?
+        /**
+         * Returns the underlying [StreamConfigurationMap] instance.
+         */
+        fun unwrap(): StreamConfigurationMap
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatApi23Impl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatApi23Impl.kt
new file mode 100644
index 0000000..3bcbfd2
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatApi23Impl.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.util.Size
+import androidx.annotation.RequiresApi
+
+@RequiresApi(23)
+internal class StreamConfigurationMapCompatApi23Impl(map: StreamConfigurationMap) :
+    StreamConfigurationMapCompatBaseImpl(map) {
+    override fun getOutputSizes(format: Int): Array<Size>? {
+        return streamConfigurationMap.getOutputSizes(format)
+    }
+
+    override fun getHighResolutionOutputSizes(format: Int): Array<Size>? {
+        return streamConfigurationMap.getHighResolutionOutputSizes(format)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
new file mode 100644
index 0000000..feeb433
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatBaseImpl.kt
@@ -0,0 +1,56 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.util.Size
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.ImageFormatConstants
+
+@RequiresApi(21)
+internal open class StreamConfigurationMapCompatBaseImpl(
+    val streamConfigurationMap: StreamConfigurationMap
+) :
+    StreamConfigurationMapCompat.StreamConfigurationMapCompatImpl {
+    override fun getOutputSizes(format: Int): Array<Size>? {
+        val sizes: Array<Size> =
+            if (format == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) {
+                // This is a little tricky that 0x22 that is internal defined in
+                // StreamConfigurationMap.java to be equal to ImageFormat.PRIVATE that is public
+                // after Android level 23 but not public in Android L. Use {@link SurfaceTexture}
+                // or {@link MediaCodec} will finally mapped to 0x22 in StreamConfigurationMap to
+                // retrieve the output sizes information.
+                streamConfigurationMap.getOutputSizes(SurfaceTexture::class.java)
+            } else {
+                streamConfigurationMap.getOutputSizes(format)
+            }
+        return sizes
+    }
+
+    override fun <T> getOutputSizes(klass: Class<T>): Array<Size>? {
+        return streamConfigurationMap.getOutputSizes(klass)
+    }
+
+    override fun getHighResolutionOutputSizes(format: Int): Array<Size>? {
+        return null
+    }
+
+    override fun unwrap(): StreamConfigurationMap {
+        return streamConfigurationMap
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/package-info.java
index 878a4d8..8b1057d 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.camera2.pipe.integration.compat;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirk.kt
index a804ab08..4d75e89 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirk.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirk.kt
@@ -24,6 +24,7 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraMetadata
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.core.impl.ImageFormatConstants
 import androidx.camera.core.impl.quirk.ProfileResolutionQuirk
 
@@ -48,14 +49,14 @@
  */
 @SuppressLint("CameraXQuirksClassDetector")
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
-class CamcorderProfileResolutionQuirk(private val cameraMetadata: CameraMetadata) :
+class CamcorderProfileResolutionQuirk(
+    private val streamConfigurationMapCompat: StreamConfigurationMapCompat
+) :
     ProfileResolutionQuirk {
 
     private val supportedResolution: List<Size> by lazy {
-        val map: StreamConfigurationMap? =
-            cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
-        checkNotNull(map) { "StreamConfiguration is null" }
-        val sizes = map.getOutputSizes(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE)
+        val sizes = streamConfigurationMapCompat
+            .getOutputSizes(ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE)
 
         val result: List<Size> = sizes?.asList() ?: emptyList()
         Log.debug { "supportedResolutions = $result" }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
index b07324f..c0c7158 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CameraQuirks.kt
@@ -19,6 +19,7 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.impl.Quirk
 import androidx.camera.core.impl.Quirks
@@ -27,7 +28,10 @@
 /** Provider of camera specific quirks. */
 @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
 @CameraScope
-class CameraQuirks @Inject constructor(private val cameraMetadata: CameraMetadata) {
+class CameraQuirks @Inject constructor(
+    private val cameraMetadata: CameraMetadata,
+    private val streamConfigurationMapCompat: StreamConfigurationMapCompat
+) {
 
     /**
      * Goes through all defined camera specific quirks, then filters them to retrieve quirks
@@ -42,7 +46,10 @@
             quirks.add(AfRegionFlipHorizontallyQuirk())
         }
         if (CamcorderProfileResolutionQuirk.isEnabled(cameraMetadata)) {
-            quirks.add(CamcorderProfileResolutionQuirk(cameraMetadata))
+            quirks.add(CamcorderProfileResolutionQuirk(streamConfigurationMapCompat))
+        }
+        if (ImageCaptureFailWithAutoFlashQuirk.isEnabled(cameraMetadata)) {
+            quirks.add(ImageCaptureFailWithAutoFlashQuirk())
         }
         if (JpegHalCorruptImageQuirk.isEnabled()) {
             quirks.add(JpegHalCorruptImageQuirk())
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.kt
new file mode 100644
index 0000000..09bbda7
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.kt
@@ -0,0 +1,66 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.Quirk
+
+/**
+ * Quirk caused by a device bug that occurs on certain devices, like the Samsung A3 devices. It
+ * causes the a crash after taking a picture with a
+ * [android.hardware.camera2.CameraCharacteristics.CONTROL_AE_MODE_ON_AUTO_FLASH] auto-exposure
+ * mode.
+ *
+ * QuirkSummary
+ * - Bug Id: 157535165, 161730578, 194046401
+ * - Description: It will cause a crash when taking pictures with flash AUTO mode.
+ *
+ * TODO(b/270421716): enable CameraXQuirksClassDetector lint check when kotlin is supported.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+class CrashWhenTakingPhotoWithAutoFlashAEModeQuirk : Quirk {
+
+    companion object {
+
+        private val AFFECTED_MODELS = listOf(
+            // Enables on all Galaxy A3 devices.
+            "SM-A3000",
+            "SM-A3009",
+            "SM-A300F",
+            "SM-A300FU",
+            "SM-A300G",
+            "SM-A300H",
+            "SM-A300M",
+            "SM-A300X",
+            "SM-A300XU",
+            "SM-A300XZ",
+            "SM-A300Y",
+            "SM-A300YZ",
+            "SM-J510FN", // Galaxy J5
+            "5059X" // TCT Alcatel 1X
+        )
+
+        @JvmStatic
+        fun isEnabled(): Boolean {
+            return AFFECTED_MODELS.contains(Build.MODEL.uppercase())
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
index 130ac5f..55b7820 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/DeviceQuirksLoader.kt
@@ -34,22 +34,36 @@
         val quirks: MutableList<Quirk> = mutableListOf()
 
         // Load all device specific quirks, preferably in lexicographical order
+        if (CrashWhenTakingPhotoWithAutoFlashAEModeQuirk.isEnabled()) {
+            quirks.add(CrashWhenTakingPhotoWithAutoFlashAEModeQuirk())
+        }
+
+        if (FlashAvailabilityBufferUnderflowQuirk.isEnabled()) {
+            quirks.add(FlashAvailabilityBufferUnderflowQuirk())
+        }
+
         if (ImageCapturePixelHDRPlusQuirk.isEnabled()) {
             quirks.add(ImageCapturePixelHDRPlusQuirk())
         }
-
         if (InvalidVideoProfilesQuirk.isEnabled()) {
             quirks.add(InvalidVideoProfilesQuirk())
         }
-
+        if (ExcludedSupportedSizesQuirk.load()) {
+            quirks.add(ExcludedSupportedSizesQuirk())
+        }
+        if (ExtraSupportedOutputSizeQuirk.load()) {
+            quirks.add(ExtraSupportedOutputSizeQuirk())
+        }
         if (PreviewPixelHDRnetQuirk.isEnabled()) {
             quirks.add(PreviewPixelHDRnetQuirk())
         }
-
         if (RepeatingStreamConstraintForVideoRecordingQuirk.isEnabled()) {
             quirks.add(RepeatingStreamConstraintForVideoRecordingQuirk())
         }
+        if (StillCaptureFlashStopRepeatingQuirk.isEnabled()) {
+            quirks.add(StillCaptureFlashStopRepeatingQuirk())
+        }
 
         return quirks
     }
-}
\ No newline at end of file
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExcludedSupportedSizesQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExcludedSupportedSizesQuirk.kt
new file mode 100644
index 0000000..c942233
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExcludedSupportedSizesQuirk.kt
@@ -0,0 +1,243 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.graphics.ImageFormat
+import android.os.Build
+import android.util.Size
+import androidx.annotation.RequiresApi
+import androidx.camera.core.Logger
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.Quirk
+
+/**
+ * Quirk required to exclude certain supported surface sizes that are problematic.
+ *
+ * QuirkSummary
+ * Bug Id: b/157448499, b/192129158, b/245495234
+ * Description:  These sizes are dependent on the device, camera and image format.
+ * An example is the resolution size 4000x3000 which is supported on OnePlus 6,
+ * but causes a WYSIWYG issue between preview and image capture. Another example
+ * is on Huawei P20 Lite, the Preview screen will become too bright when 400x400
+ * or 720x720 Preview resolutions are used together with a large zoom in value.
+ * The same symptom happens on ImageAnalysis. On Samsung J7 Prime (SM-G610M) or
+ * J7 (SM-J710MN) API 27 devices, the Preview images will be stretched if
+ * 1920x1080 resolution is used.
+ * Device(s): OnePlus 6, OnePlus 6T, Huawei P20, Samsung J7 Prime (SM-G610M) API 27, Samsung
+ * J7 (SM-J710MN) API 27
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class ExcludedSupportedSizesQuirk() : Quirk {
+    /**
+     * Retrieves problematic supported surface sizes that have to be excluded on the current
+     * device, for the given camera id and image format.
+     */
+    fun getExcludedSizes(cameraId: String, imageFormat: Int): List<Size> {
+        if (isOnePlus6) {
+            return getOnePlus6ExcludedSizes(cameraId, imageFormat)
+        }
+        if (isOnePlus6T) {
+            return getOnePlus6TExcludedSizes(cameraId, imageFormat)
+        }
+        if (isHuaweiP20Lite) {
+            return getHuaweiP20LiteExcludedSizes(cameraId, imageFormat, null)
+        }
+        if (isSamsungJ7PrimeApi27Above) {
+            return getSamsungJ7PrimeApi27AboveExcludedSizes(cameraId, imageFormat, null)
+        }
+        if (isSamsungJ7Api27Above) {
+            return getSamsungJ7Api27AboveExcludedSizes(cameraId, imageFormat, null)
+        }
+        Logger.w(TAG, "Cannot retrieve list of supported sizes to exclude on this device.")
+        return emptyList()
+    }
+
+    /**
+     * Retrieves problematic supported surface sizes that have to be excluded on the current
+     * device, for the given camera id and class type.
+     */
+    fun getExcludedSizes(cameraId: String, klass: Class<*>): List<Size> {
+        if (isHuaweiP20Lite) {
+            return getHuaweiP20LiteExcludedSizes(cameraId, UNKNOWN_IMAGE_FORMAT, klass)
+        }
+        if (isSamsungJ7PrimeApi27Above) {
+            return getSamsungJ7PrimeApi27AboveExcludedSizes(cameraId, UNKNOWN_IMAGE_FORMAT, klass)
+        }
+        if (isSamsungJ7Api27Above) {
+            return getSamsungJ7Api27AboveExcludedSizes(cameraId, UNKNOWN_IMAGE_FORMAT, klass)
+        }
+        Logger.w(TAG, "Cannot retrieve list of supported sizes to exclude on this device.")
+        return emptyList()
+    }
+
+    private fun getOnePlus6ExcludedSizes(cameraId: String, imageFormat: Int): List<Size> {
+        val sizes: MutableList<Size> = ArrayList()
+        if ((cameraId == "0") && imageFormat == ImageFormat.JPEG) {
+            sizes.add(Size(4160, 3120))
+            sizes.add(Size(4000, 3000))
+        }
+        return sizes
+    }
+
+    private fun getOnePlus6TExcludedSizes(cameraId: String, imageFormat: Int): List<Size> {
+        val sizes: MutableList<Size> = ArrayList()
+        if ((cameraId == "0") && imageFormat == ImageFormat.JPEG) {
+            sizes.add(Size(4160, 3120))
+            sizes.add(Size(4000, 3000))
+        }
+        return sizes
+    }
+
+    private fun getHuaweiP20LiteExcludedSizes(
+        cameraId: String,
+        imageFormat: Int,
+        klass: Class<*>?
+    ): List<Size> {
+        val sizes: MutableList<Size> = ArrayList()
+        // When klass is not null, the list for PRIVATE format should be returned.
+        if ((cameraId == "0") &&
+            ((imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) ||
+                (imageFormat == ImageFormat.YUV_420_888) || (klass != null))
+        ) {
+            sizes.add(Size(720, 720))
+            sizes.add(Size(400, 400))
+        }
+        return sizes
+    }
+
+    private fun getSamsungJ7PrimeApi27AboveExcludedSizes(
+        cameraId: String,
+        imageFormat: Int,
+        klass: Class<*>?
+    ): List<Size> {
+        val sizes: MutableList<Size> = ArrayList()
+
+        // When klass is not null, the list for PRIVATE format should be returned.
+        if ((cameraId == "0")) {
+            if ((imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE ||
+                    klass != null)
+            ) {
+                sizes.add(Size(4128, 3096))
+                sizes.add(Size(4128, 2322))
+                sizes.add(Size(3088, 3088))
+                sizes.add(Size(3264, 2448))
+                sizes.add(Size(3264, 1836))
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            } else if (imageFormat == ImageFormat.YUV_420_888) {
+                sizes.add(Size(4128, 2322))
+                sizes.add(Size(3088, 3088))
+                sizes.add(Size(3264, 2448))
+                sizes.add(Size(3264, 1836))
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            }
+        } else if ((cameraId == "1")) {
+            if ((imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) ||
+                (imageFormat == ImageFormat.YUV_420_888) || (klass != null)
+            ) {
+                sizes.add(Size(3264, 2448))
+                sizes.add(Size(3264, 1836))
+                sizes.add(Size(2448, 2448))
+                sizes.add(Size(1920, 1920))
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            }
+        }
+        return sizes
+    }
+
+    private fun getSamsungJ7Api27AboveExcludedSizes(
+        cameraId: String,
+        imageFormat: Int,
+        klass: Class<*>?
+    ): List<Size> {
+        val sizes: MutableList<Size> = ArrayList()
+
+        // When klass is not null, the list for PRIVATE format should be returned.
+        if ((cameraId == "0")) {
+            if ((imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE ||
+                    klass != null)
+            ) {
+                sizes.add(Size(4128, 3096))
+                sizes.add(Size(4128, 2322))
+                sizes.add(Size(3088, 3088))
+                sizes.add(Size(3264, 2448))
+                sizes.add(Size(3264, 1836))
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            } else if (imageFormat == ImageFormat.YUV_420_888) {
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            }
+        } else if ((cameraId == "1")) {
+            if ((imageFormat == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE) ||
+                (imageFormat == ImageFormat.YUV_420_888) || (klass != null)
+            ) {
+                sizes.add(Size(2576, 1932))
+                sizes.add(Size(2560, 1440))
+                sizes.add(Size(1920, 1920))
+                sizes.add(Size(2048, 1536))
+                sizes.add(Size(2048, 1152))
+                sizes.add(Size(1920, 1080))
+            }
+        }
+        return sizes
+    }
+
+    companion object {
+        private const val TAG: String = "ExcludedSupportedSizesQuirk"
+        private const val UNKNOWN_IMAGE_FORMAT: Int = -1
+        fun load(): Boolean {
+            return (isOnePlus6 || isOnePlus6T || isHuaweiP20Lite || isSamsungJ7PrimeApi27Above ||
+                isSamsungJ7Api27Above)
+        }
+
+        internal val isOnePlus6: Boolean
+            get() = "OnePlus".equals(Build.BRAND, ignoreCase = true) && "OnePlus6".equals(
+                Build.DEVICE, ignoreCase = true
+            )
+        internal val isOnePlus6T: Boolean
+            get() = "OnePlus".equals(Build.BRAND, ignoreCase = true) && "OnePlus6T".equals(
+                Build.DEVICE, ignoreCase = true
+            )
+        internal val isHuaweiP20Lite: Boolean
+            get() {
+                return "HUAWEI".equals(
+                    Build.BRAND,
+                    ignoreCase = true
+                ) && "HWANE".equals(Build.DEVICE, ignoreCase = true)
+            }
+        internal val isSamsungJ7PrimeApi27Above: Boolean
+            get() {
+                return ("SAMSUNG".equals(Build.BRAND.uppercase(), ignoreCase = true) &&
+                    "ON7XELTE".equals(Build.DEVICE.uppercase(), ignoreCase = true) &&
+                    (Build.VERSION.SDK_INT >= 27))
+            }
+        internal val isSamsungJ7Api27Above: Boolean
+            get() {
+                return ("SAMSUNG".equals(Build.BRAND.uppercase(), ignoreCase = true) &&
+                    "J7XELTE".equals(Build.DEVICE.uppercase(), ignoreCase = true) &&
+                    (Build.VERSION.SDK_INT >= 27))
+            }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedOutputSizeQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedOutputSizeQuirk.kt
new file mode 100644
index 0000000..971320f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ExtraSupportedOutputSizeQuirk.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.params.StreamConfigurationMap
+import android.os.Build
+import android.util.Size
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.ImageFormatConstants
+import androidx.camera.core.impl.Quirk
+
+/**
+ * QuirkSummary
+ * Bug Id: b/241876294
+ * Description: CamcorderProfile resolutions can not find a match in the output size list of
+ * [CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]. Since these resolutions
+ * can be supported in native camera app, add these resolutions back.
+ * Device(s): Motorola Moto E5 Play.
+ *
+ * TODO: enable CameraXQuirksClassDetector lint check when kotlin is supported.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class ExtraSupportedOutputSizeQuirk() : Quirk {
+    /**
+     * Returns the extra supported resolutions on the device.
+     */
+    fun getExtraSupportedResolutions(format: Int): Array<Size> {
+        return if ((format == ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE &&
+                isMotoE5Play)
+        ) {
+            motoE5PlayExtraSupportedResolutions
+        } else {
+            arrayOf()
+        }
+    }
+
+    /**
+     * Returns the extra supported resolutions on the device.
+     */
+    fun <T> getExtraSupportedResolutions(klass: Class<T>): Array<Size> {
+        return if (StreamConfigurationMap.isOutputSupportedFor(klass) && isMotoE5Play) {
+            motoE5PlayExtraSupportedResolutions
+        } else {
+            arrayOf()
+        }
+    }
+
+    // Both the front and the main cameras support the following resolutions.
+    private val motoE5PlayExtraSupportedResolutions: Array<Size>
+        get() = arrayOf(
+            // FHD
+            Size(1920, 1080),
+            Size(1440, 1080),
+            // HD
+            Size(1280, 720),
+            Size(960, 720),
+            // SD (640:480 is already included in the original list)
+            Size(864, 480),
+            Size(720, 480)
+        )
+
+    companion object {
+        fun load(): Boolean {
+            return isMotoE5Play
+        }
+
+        internal val isMotoE5Play: Boolean
+            get() = "motorola".equals(
+                Build.BRAND,
+                ignoreCase = true
+            ) && "moto e5 play".equals(
+                Build.MODEL, ignoreCase = true
+            )
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashAvailabilityBufferUnderflowQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashAvailabilityBufferUnderflowQuirk.kt
new file mode 100644
index 0000000..bc50a37
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/FlashAvailabilityBufferUnderflowQuirk.kt
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.Quirk
+import java.nio.BufferUnderflowException
+import java.util.Locale
+
+/**
+ * A quirk for devices that throw a [BufferUnderflowException] when querying the flash availability.
+ *
+ * QuirkSummary
+ * - Bug Id: 216667482
+ * - Description: When attempting to retrieve the
+ *   [CameraCharacteristics.FLASH_INFO_AVAILABLE] characteristic, a
+ *   [BufferUnderflowException] is thrown. This is an undocumented exception
+ *   on the [CameraCharacteristics.get] method, so this violates the API contract.
+ * - Device(s): Spreadtrum devices including LEMFO LEMP and DM20C
+ *
+ * TODO: enable CameraXQuirksClassDetector lint check when kotlin is supported.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class FlashAvailabilityBufferUnderflowQuirk : Quirk {
+
+    companion object {
+        private val KNOWN_AFFECTED_MODELS = setOf(
+            // Devices enumerated as DeviceInfo(Build.MANUFACTURER, Build.MODEL).
+            DeviceInfo("sprd", "lemp"),
+            DeviceInfo("sprd", "DM20C"),
+        )
+
+        fun isEnabled(): Boolean {
+            return KNOWN_AFFECTED_MODELS.contains(
+                DeviceInfo(Build.MANUFACTURER, Build.MODEL)
+            )
+        }
+    }
+
+    data class DeviceInfo private constructor(val manufacturer: String, val model: String) {
+        companion object {
+            operator fun invoke(manufacturer: String, model: String) =
+                DeviceInfo(manufacturer.lowercase(Locale.US), model.lowercase(Locale.US))
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ImageCaptureFailWithAutoFlashQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ImageCaptureFailWithAutoFlashQuirk.kt
new file mode 100644
index 0000000..1b6a41f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/ImageCaptureFailWithAutoFlashQuirk.kt
@@ -0,0 +1,55 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics.LENS_FACING
+import android.hardware.camera2.CameraCharacteristics.LENS_FACING_FRONT
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.core.impl.Quirk
+
+/**
+ * QuirkSummary
+ * - Bug Id: 228800360
+ * - Description: The image capturing may fail when the camera turns on the auto flash
+ *   mode, and the devices also fail to fire the flash on the flash on mode.
+ * - Device(s): Samsung Galaxy J7 (sm-j700f, sm-j710f) front camera
+ *
+ * TODO(b/270421716): enable CameraXQuirksClassDetector lint check when kotlin is supported.
+ */
+@SuppressLint("CameraXQuirksClassDetector")
+class ImageCaptureFailWithAutoFlashQuirk : Quirk {
+
+    companion object {
+        /**
+         * List of devices with the issue. See b/228800360.
+         */
+        private val BUILD_MODELS_FRONT_CAMERA = listOf(
+            "sm-j700f", // Samsung Galaxy J7
+            "sm-j710f", // Samsung Galaxy J7
+        )
+
+        fun isEnabled(cameraMetadata: CameraMetadata): Boolean {
+            return BUILD_MODELS_FRONT_CAMERA.contains(Build.MODEL.lowercase()) &&
+                cameraMetadata[LENS_FACING] == LENS_FACING_FRONT
+        }
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/StillCaptureFlashStopRepeatingQuirk.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/StillCaptureFlashStopRepeatingQuirk.kt
new file mode 100644
index 0000000..d9c8b7f
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/StillCaptureFlashStopRepeatingQuirk.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.camera.camera2.pipe.integration.compat.quirk
+
+import android.annotation.SuppressLint
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.core.impl.Quirk
+
+/**
+ * Quirk that still capture with flash on/auto requires stopRepeating() being called ahead of
+ * capture.
+ *
+ * QuirkSummary
+ * - Bug Id: 172036589
+ * - Description: On some devices like Samsung SM-A716B, it could lead to CaptureRequest not
+ *                being completed when taking photos in dark environment with flash on/auto.
+ *                Calling stopRepeating ahead of still capture and setRepeating again after
+ *                capture is done can fix the issue.
+ * - Device(s): Samsung SM-A716
+ */
+@SuppressLint("CameraXQuirksClassDetector") // TODO(b/270421716): enable when kotlin is supported.
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class StillCaptureFlashStopRepeatingQuirk : Quirk {
+    companion object {
+        fun isEnabled(): Boolean {
+            return Build.MANUFACTURER.equals("SAMSUNG", ignoreCase = true) &&
+                Build.MODEL.uppercase().startsWith("SM-A716")
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/package-info.java
index eca73c3..ce9fe25 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/quirk/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.camera2.pipe.integration.compat.quirk;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisabler.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisabler.kt
new file mode 100644
index 0000000..6a48515
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisabler.kt
@@ -0,0 +1,71 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.CrashWhenTakingPhotoWithAutoFlashAEModeQuirk
+import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.ImageCaptureFailWithAutoFlashQuirk
+import dagger.Module
+import dagger.Provides
+
+/**
+ * A workaround to turn off the auto flash AE mode if device has the
+ * [CrashWhenTakingPhotoWithAutoFlashAEModeQuirk] or [ImageCaptureFailWithAutoFlashQuirk].
+ */
+interface AutoFlashAEModeDisabler {
+    fun getCorrectedAeMode(aeMode: Int): Int
+
+    @Module
+    abstract class Bindings {
+        companion object {
+            @Provides
+            fun provideAEModeDisabler(cameraQuirks: CameraQuirks): AutoFlashAEModeDisabler {
+                val isFailWithAutoFlashQuirkEnabled =
+                    cameraQuirks.quirks.contains(ImageCaptureFailWithAutoFlashQuirk::class.java)
+
+                val isCrashWithAutoFlashQuirkEnabled =
+                    DeviceQuirks[CrashWhenTakingPhotoWithAutoFlashAEModeQuirk::class.java] != null
+
+                return if (isCrashWithAutoFlashQuirkEnabled || isFailWithAutoFlashQuirkEnabled)
+                    AutoFlashAEModeDisablerImpl
+                else NoOpAutoFlashAEModeDisabler
+            }
+        }
+    }
+}
+
+object AutoFlashAEModeDisablerImpl : AutoFlashAEModeDisabler {
+
+    /**
+     * Get AE mode corrected by the [CrashWhenTakingPhotoWithAutoFlashAEModeQuirk] and
+     * [ImageCaptureFailWithAutoFlashQuirk].
+     */
+    override fun getCorrectedAeMode(aeMode: Int): Int {
+        return if (aeMode == CONTROL_AE_MODE_ON_AUTO_FLASH) CONTROL_AE_MODE_ON
+        else aeMode
+    }
+}
+
+object NoOpAutoFlashAEModeDisabler : AutoFlashAEModeDisabler {
+    override fun getCorrectedAeMode(aeMode: Int): Int = aeMode
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityChecker.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityChecker.kt
new file mode 100644
index 0000000..febe978
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityChecker.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.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.FlashAvailabilityBufferUnderflowQuirk
+import androidx.camera.camera2.pipe.integration.impl.CameraProperties
+import java.nio.BufferUnderflowException
+
+/**
+ * A workaround for devices which may throw a [BufferUnderflowException] when
+ * checking flash availability.
+ *
+ * @param allowRethrowOnError whether exceptions can be rethrown on devices that are not
+ * known to be problematic. If `false`, these devices will be
+ * logged as an error instead.
+ * @return the value of [CameraCharacteristics.FLASH_INFO_AVAILABLE] if it is contained
+ * in the characteristics, or `false` if it is not or a
+ * [BufferUnderflowException] is thrown while checking.
+ *
+ * @see FlashAvailabilityBufferUnderflowQuirk
+ */
+fun CameraProperties.isFlashAvailable(allowRethrowOnError: Boolean = false): Boolean {
+    val flashAvailable = try {
+        metadata[CameraCharacteristics.FLASH_INFO_AVAILABLE]
+    } catch (e: BufferUnderflowException) {
+        if (DeviceQuirks[FlashAvailabilityBufferUnderflowQuirk::class.java] != null) {
+            Log.debug {
+                "Device is known to throw an exception while checking flash availability. Flash" +
+                    " is not available. [Manufacturer: ${Build.MANUFACTURER}, Model:" +
+                    " ${Build.MODEL}, API Level: ${Build.VERSION.SDK_INT}]."
+            }
+        } else {
+            Log.error(e) {
+                "Exception thrown while checking for flash availability on device not known to " +
+                    "throw exceptions during this check. Please file an issue at " +
+                    "https://issuetracker.google.com/issues/new?component=618491&template=1257717" +
+                    " with this error message [Manufacturer: ${Build.MANUFACTURER}, Model:" +
+                    " ${Build.MODEL}, API Level: ${Build.VERSION.SDK_INT}]. Flash is not available."
+            }
+        }
+
+        if (allowRethrowOnError) {
+            throw e
+        } else {
+            false
+        }
+    }
+    if (flashAvailable == null) {
+        Log.warn {
+            "Characteristics did not contain key FLASH_INFO_AVAILABLE. Flash is not available."
+        }
+    }
+    return flashAvailable ?: false
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/OutputSizesCorrector.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/OutputSizesCorrector.kt
new file mode 100644
index 0000000..a873e6b
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/OutputSizesCorrector.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.util.Size
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import javax.inject.Inject
+import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.ExcludedSupportedSizesQuirk
+import androidx.camera.camera2.pipe.integration.compat.quirk.ExtraSupportedOutputSizeQuirk
+
+/**
+ * Helper class to provide the StreamConfigurationMap output sizes related correction functions.
+ *
+ * 1. ExtraSupportedOutputSizeQuirk
+ * 2. ExcludedSupportedSizesContainer
+ * 3. TargetAspectRatio
+ */
+@CameraScope
+@RequiresApi(21)
+class OutputSizesCorrector @Inject constructor(
+    private val cameraMetadata: CameraMetadata
+) {
+    private val excludedSupportedSizesQuirk: ExcludedSupportedSizesQuirk? =
+        DeviceQuirks[ExcludedSupportedSizesQuirk::class.java]
+    private val extraSupportedOutputSizeQuirk: ExtraSupportedOutputSizeQuirk? =
+        DeviceQuirks[ExtraSupportedOutputSizeQuirk::class.java]
+
+    /**
+     * Applies the output sizes related quirks onto the input sizes array.
+     */
+    fun applyQuirks(sizes: Array<Size>?, format: Int): Array<Size>? {
+        var result = addExtraSupportedOutputSizesByFormat(sizes, format)
+        result = excludeProblematicOutputSizesByFormat(result, format)
+        return excludeOutputSizesByTargetAspectRatioWorkaround(result)
+    }
+
+    /**
+     * Applies the output sizes related quirks onto the input sizes array.
+     */
+    fun <T> applyQuirks(sizes: Array<Size>?, klass: Class<T>): Array<Size>? {
+        var result = addExtraSupportedOutputSizesByClass(sizes, klass)
+        result = excludeProblematicOutputSizesByClass(result, klass)
+        return excludeOutputSizesByTargetAspectRatioWorkaround(result)
+    }
+
+    /**
+     * Adds extra supported output sizes for the specified format by ExtraSupportedOutputSizeQuirk.
+     */
+    private fun addExtraSupportedOutputSizesByFormat(
+        sizes: Array<Size>?,
+        format: Int
+    ): Array<Size>? {
+        if (sizes == null || extraSupportedOutputSizeQuirk == null) {
+            return sizes
+        }
+        val extraSizes: Array<Size> =
+            extraSupportedOutputSizeQuirk.getExtraSupportedResolutions(format)
+        return concatNullableSizeLists(sizes.toList(), extraSizes.toList()).toTypedArray()
+    }
+
+    /**
+     * Adds extra supported output sizes for the specified class by ExtraSupportedOutputSizeQuirk.
+     */
+    private fun <T> addExtraSupportedOutputSizesByClass(
+        sizes: Array<Size>?,
+        klass: Class<T>
+    ): Array<Size>? {
+        if (sizes == null || extraSupportedOutputSizeQuirk == null) {
+            return sizes
+        }
+        val extraSizes: Array<Size> =
+            extraSupportedOutputSizeQuirk.getExtraSupportedResolutions(klass)
+        return concatNullableSizeLists(sizes.toList(), extraSizes.toList()).toTypedArray()
+    }
+
+    /**
+     * Excludes problematic output sizes for the specified format by
+     * ExcludedSupportedSizesContainer.
+     */
+    private fun excludeProblematicOutputSizesByFormat(
+        sizes: Array<Size>?,
+        format: Int
+    ): Array<Size>? {
+        if (sizes == null || excludedSupportedSizesQuirk == null) {
+            return sizes
+        }
+        val excludedSizes: List<Size> =
+            excludedSupportedSizesQuirk.getExcludedSizes(cameraMetadata.camera.value, format)
+
+        val resultList: MutableList<Size> = sizes.toMutableList()
+        resultList.removeAll(excludedSizes)
+        return resultList.toTypedArray()
+    }
+
+    /**
+     * Excludes problematic output sizes for the specified class type by
+     * ExcludedSupportedSizesContainer.
+     */
+    private fun <T> excludeProblematicOutputSizesByClass(
+        sizes: Array<Size>?,
+        klass: Class<T>
+    ): Array<Size>? {
+        if (sizes == null || excludedSupportedSizesQuirk == null) {
+            return sizes
+        }
+        val excludedSizes: List<Size> =
+            excludedSupportedSizesQuirk.getExcludedSizes(cameraMetadata.camera.value, klass)
+
+        val resultList: MutableList<Size> = sizes.toMutableList()
+        resultList.removeAll(excludedSizes)
+        return resultList.toTypedArray()
+    }
+
+    /**
+     * Excludes output sizes by TargetAspectRatio.
+     */
+    private fun excludeOutputSizesByTargetAspectRatioWorkaround(sizes: Array<Size>?): Array<Size>? {
+        // TODO(b/245622117): Nexus4AndroidLTargetAspectRatioQuirk and AspectRatioLegacyApi21Quirk
+        return sizes
+    }
+
+    private fun concatNullableSizeLists(
+        sizeList1: List<Size>,
+        sizeList2: List<Size>
+    ): List<Size> {
+        val resultList: MutableList<Size> = ArrayList(sizeList1)
+        resultList.addAll(sizeList2)
+        return resultList
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnet.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnet.kt
index bd566c2..496e17e 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnet.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnet.kt
@@ -19,20 +19,26 @@
 package androidx.camera.camera2.pipe.integration.compat.workaround
 
 import android.hardware.camera2.CaptureRequest
+import android.util.Rational
+import android.util.Size
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
 import androidx.camera.camera2.pipe.integration.compat.quirk.PreviewPixelHDRnetQuirk
 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
 import androidx.camera.core.impl.SessionConfig
 
+private val ASPECT_RATIO_16_9 = Rational(16, 9)
+
 /**
  * Turns on WYSIWYG viewfinder on Pixel devices
  *
  * @see PreviewPixelHDRnetQuirk
  */
-fun SessionConfig.Builder.setupHDRnet() {
+fun SessionConfig.Builder.setupHDRnet(resolution: Size) {
     DeviceQuirks[PreviewPixelHDRnetQuirk::class.java] ?: return
 
+    if (isAspectRatioMatch(resolution, ASPECT_RATIO_16_9)) return
+
     val camera2ConfigBuilder = Camera2ImplConfig.Builder().apply {
         setCaptureRequestOption<Int>(
             CaptureRequest.TONEMAP_MODE,
@@ -41,4 +47,11 @@
     }
 
     addImplementationOptions(camera2ConfigBuilder.build())
+}
+
+private fun isAspectRatioMatch(
+    resolution: Size,
+    aspectRatio: Rational
+): Boolean {
+    return aspectRatio == Rational(resolution.width, resolution.height)
 }
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlow.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlow.kt
new file mode 100644
index 0000000..9a63566
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlow.kt
@@ -0,0 +1,50 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.integration.compat.quirk.DeviceQuirks
+import androidx.camera.camera2.pipe.integration.compat.quirk.StillCaptureFlashStopRepeatingQuirk
+
+/**
+ * Returns whether or not repeating should be stopped before submitting capture request.
+ */
+fun List<Request>.shouldStopRepeatingBeforeCapture(): Boolean {
+    DeviceQuirks[StillCaptureFlashStopRepeatingQuirk::class.java] ?: return false
+
+    var isStillCapture = false
+    var isFlashEnabled = false
+
+    forEach { request ->
+        if (request.template?.value == CameraDevice.TEMPLATE_STILL_CAPTURE) {
+            isStillCapture = true
+        }
+
+        isFlashEnabled = when (request[CaptureRequest.CONTROL_AE_MODE]) {
+            CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH,
+            CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH -> true
+            else -> isFlashEnabled
+        }
+    }
+
+    return isStillCapture && isFlashEnabled
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/package-info.java
index b470822..556205d 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/compat/workaround/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.camera2.pipe.integration.compat.workaround;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
index c00d08a..af4d4c0 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/CameraConfig.kt
@@ -18,6 +18,8 @@
 
 package androidx.camera.camera2.pipe.integration.config
 
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.StreamConfigurationMap
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.camera.camera2.pipe.CameraId
@@ -64,15 +66,15 @@
 @OptIn(ExperimentalCamera2Interop::class)
 @Module(
     includes = [
-        ZoomCompat.Bindings::class,
-        ZoomControl.Bindings::class,
+        Camera2CameraControlCompat.Bindings::class,
         EvCompCompat.Bindings::class,
         EvCompControl.Bindings::class,
         FlashControl.Bindings::class,
         FocusMeteringControl.Bindings::class,
         State3AControl.Bindings::class,
         TorchControl.Bindings::class,
-        Camera2CameraControlCompat.Bindings::class,
+        ZoomCompat.Bindings::class,
+        ZoomControl.Bindings::class,
     ],
     subcomponents = [UseCaseCameraComponent::class]
 )
@@ -124,6 +126,13 @@
         @Provides
         @Named("CameraId")
         fun provideCameraIdString(config: CameraConfig): String = config.cameraId.value
+
+        @CameraScope
+        @Provides
+        fun provideStreamConfigurationMap(cameraMetadata: CameraMetadata): StreamConfigurationMap {
+            return cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]
+                ?: throw IllegalArgumentException("Cannot retrieve SCALER_STREAM_CONFIGURATION_MAP")
+        }
     }
 
     @Binds
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java
index b7d2f6a..13d60f9 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/config/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 package androidx.camera.camera2.pipe.integration.config;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Camera2ImplConfig.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Camera2ImplConfig.kt
index be5d49f..1ed99be 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Camera2ImplConfig.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/Camera2ImplConfig.kt
@@ -76,7 +76,6 @@
     /**
      * Returns all capture request options contained in this configuration.
      *
-     * @hide
      */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY)
     val captureRequestOptions: CaptureRequestOptions
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
index 0d8933c..3b944d4 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/CapturePipeline.kt
@@ -47,6 +47,8 @@
 import androidx.camera.camera2.pipe.RequestMetadata
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.core.Log
+import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
+import androidx.camera.camera2.pipe.integration.compat.workaround.shouldStopRepeatingBeforeCapture
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
@@ -62,12 +64,12 @@
 import androidx.camera.core.TorchState
 import dagger.Binds
 import dagger.Module
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.joinAll
 import kotlinx.coroutines.launch
-import java.util.concurrent.TimeUnit
-import javax.inject.Inject
 
 private val CHECK_3A_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(1)
 private val CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS = TimeUnit.SECONDS.toNanos(5)
@@ -92,10 +94,15 @@
     private val torchControl: TorchControl,
     private val threads: UseCaseThreads,
     private val requestListener: ComboRequestListener,
+    cameraProperties: CameraProperties,
+    private val useCaseCameraState: UseCaseCameraState,
     useCaseGraphConfig: UseCaseGraphConfig,
 ) : CapturePipeline {
     private val graph = useCaseGraphConfig.graph
 
+    // If there is no flash unit, skip the flash related task instead of failing the pipeline.
+    private val hasFlashUnit = cameraProperties.isFlashAvailable()
+
     override var template = CameraDevice.TEMPLATE_PREVIEW
 
     override suspend fun submitStillCaptures(
@@ -114,21 +121,10 @@
         captureMode: Int,
         flashMode: Int,
     ): List<Deferred<Void?>> =
-        if (isFlashRequired(flashMode)) {
+        if (hasFlashUnit && isFlashRequired(flashMode)) {
             torchApplyCapture(requests, captureMode, CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS)
         } else {
-            val lock3ARequired = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY
-            if (lock3ARequired) {
-                lock3A(CHECK_3A_TIMEOUT_IN_NS)
-            }
-            submitRequestInternal(requests).also { captureSignal ->
-                if (lock3ARequired) {
-                    threads.sequentialScope.launch {
-                        captureSignal.joinAll()
-                        unlock3A()
-                    }
-                }
-            }
+            defaultNoFlashCapture(requests, captureMode)
         }
 
     private suspend fun defaultCapture(
@@ -136,14 +132,36 @@
         captureMode: Int,
         flashMode: Int,
     ): List<Deferred<Void?>> {
-        val isFlashRequired = isFlashRequired(flashMode)
-        val timeout =
-            if (isFlashRequired) CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS else CHECK_3A_TIMEOUT_IN_NS
+        return if (hasFlashUnit) {
+            val isFlashRequired = isFlashRequired(flashMode)
+            val timeout =
+                if (isFlashRequired) CHECK_3A_WITH_FLASH_TIMEOUT_IN_NS else CHECK_3A_TIMEOUT_IN_NS
 
-        return if (isFlashRequired || captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY) {
-            aePreCaptureApplyCapture(requests, timeout)
+            if (isFlashRequired || captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY) {
+                aePreCaptureApplyCapture(requests, timeout)
+            } else {
+                defaultNoFlashCapture(requests, captureMode)
+            }
         } else {
-            submitRequestInternal(requests)
+            defaultNoFlashCapture(requests, captureMode)
+        }
+    }
+
+    private suspend fun defaultNoFlashCapture(
+        requests: List<Request>,
+        captureMode: Int
+    ): List<Deferred<Void?>> {
+        val lock3ARequired = captureMode == CAPTURE_MODE_MAXIMIZE_QUALITY
+        if (lock3ARequired) {
+            lock3A(CHECK_3A_TIMEOUT_IN_NS)
+        }
+        return submitRequestInternal(requests).also { captureSignal ->
+            if (lock3ARequired) {
+                threads.sequentialScope.launch {
+                    captureSignal.joinAll()
+                    unlock3A()
+                }
+            }
         }
     }
 
@@ -256,7 +274,17 @@
 
         threads.sequentialScope.launch {
             graph.acquireSession().use {
+                val requiresStopRepeating = requestsToSubmit.shouldStopRepeatingBeforeCapture()
+                if (requiresStopRepeating) {
+                    it.stopRepeating()
+                }
+
                 it.submit(requestsToSubmit)
+
+                if (requiresStopRepeating) {
+                    deferredList.joinAll()
+                    useCaseCameraState.tryStartRepeating()
+                }
             }
         }
 
@@ -359,4 +387,4 @@
 
         completeSignal.complete(totalCaptureResult)
     }
-}
\ No newline at end of file
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
index 84052db..5541a40 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FocusMeteringControl.kt
@@ -26,6 +26,7 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.AeMode
 import androidx.camera.camera2.pipe.AfMode
+import androidx.camera.camera2.pipe.CameraGraph.Constants3A.METERING_REGIONS_DEFAULT
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
@@ -158,10 +159,21 @@
                     (false to autoFocusTimeoutMs)
                 }
                 withTimeoutOrNull(timeout) {
+                    /**
+                     * If device does not support a 3A region, we should not update it at all.
+                     * If device does support but a region list is empty, it means any previously
+                     * set region should be removed, so the no-op METERING_REGIONS_DEFAULT is used.
+                     */
                     useCaseCamera.requestControl.startFocusAndMeteringAsync(
-                        aeRegions = aeRectangles,
-                        afRegions = afRectangles,
-                        awbRegions = awbRectangles,
+                        aeRegions = if (maxAeRegionCount > 0)
+                            aeRectangles.ifEmpty { METERING_REGIONS_DEFAULT.toList() }
+                        else null,
+                        afRegions = if (maxAfRegionCount > 0)
+                            afRectangles.ifEmpty { METERING_REGIONS_DEFAULT.toList() }
+                        else null,
+                        awbRegions = if (maxAwbRegionCount > 0)
+                            awbRectangles.ifEmpty { METERING_REGIONS_DEFAULT.toList() }
+                        else null,
                         afTriggerStartAeMode = cameraProperties.getSupportedAeMode(AeMode.ON)
                     ).await()
                 }.let { result3A ->
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
index 6294da8..9f2844a 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/MeteringRepeating.kt
@@ -76,7 +76,7 @@
         Builder(cameraProperties, displayInfoManager)
 
     override fun onSuggestedStreamSpecUpdated(suggestedStreamSpec: StreamSpec): StreamSpec {
-        updateSessionConfig(createPipeline().build())
+        updateSessionConfig(createPipeline(meteringSurfaceSize).build())
         notifyActive()
         return StreamSpec.builder(meteringSurfaceSize).build()
     }
@@ -95,15 +95,15 @@
         updateSuggestedStreamSpec(StreamSpec.builder(DEFAULT_PREVIEW_SIZE).build())
     }
 
-    private fun createPipeline(): SessionConfig.Builder {
+    private fun createPipeline(resolution: Size): SessionConfig.Builder {
         synchronized(deferrableSurfaceLock) {
             val surfaceTexture = SurfaceTexture(0).apply {
-                setDefaultBufferSize(meteringSurfaceSize.width, meteringSurfaceSize.height)
+                setDefaultBufferSize(resolution.width, resolution.height)
             }
             val surface = Surface(surfaceTexture)
 
             deferrableSurface?.close()
-            deferrableSurface = ImmediateSurface(surface, meteringSurfaceSize, imageFormat)
+            deferrableSurface = ImmediateSurface(surface, resolution, imageFormat)
             deferrableSurface!!.terminationFuture
                 .addListener(
                     {
@@ -115,12 +115,12 @@
         }
 
         return SessionConfig.Builder
-            .createFrom(MeteringRepeatingConfig())
+            .createFrom(MeteringRepeatingConfig(), resolution)
             .apply {
                 setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
                 addSurface(deferrableSurface!!)
                 addErrorListener { _, _ ->
-                    updateSessionConfig(createPipeline().build())
+                    updateSessionConfig(createPipeline(resolution).build())
                     notifyReset()
                 }
             }
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
index 4c5f71f..b402f17 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.kt
@@ -23,6 +23,7 @@
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
+import androidx.camera.camera2.pipe.integration.compat.workaround.AutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.CameraControl
 import androidx.camera.core.ImageCapture
@@ -41,6 +42,7 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class State3AControl @Inject constructor(
     val cameraProperties: CameraProperties,
+    private val aeModeDisabler: AutoFlashAEModeDisabler,
 ) : UseCaseCameraControl, UseCaseCamera.RunningUseCasesChangeListener {
     private var _useCaseCamera: UseCaseCamera? = null
     override var useCaseCamera: UseCaseCamera?
@@ -115,11 +117,9 @@
         val preferAeMode = preferredAeMode ?: when (flashMode) {
             ImageCapture.FLASH_MODE_OFF -> CaptureRequest.CONTROL_AE_MODE_ON
             ImageCapture.FLASH_MODE_ON -> CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH
-            ImageCapture.FLASH_MODE_AUTO -> CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
-            // TODO(b/209383160): porting the Quirk for AEModeDisabler
-            //      mAutoFlashAEModeDisabler.getCorrectedAeMode(
-            //      CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
-            //    )
+            ImageCapture.FLASH_MODE_AUTO -> aeModeDisabler.getCorrectedAeMode(
+                CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH
+            )
             else -> CaptureRequest.CONTROL_AE_MODE_ON
         }
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
index 21e752a..5822ecd 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/TorchControl.kt
@@ -16,10 +16,10 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
-import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CaptureRequest
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.adapter.propagateTo
+import androidx.camera.camera2.pipe.integration.compat.workaround.isFlashAvailable
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.CameraControl
 import androidx.camera.core.TorchState
@@ -68,10 +68,7 @@
         setTorchAsync(false)
     }
 
-    private val hasFlashUnit: Boolean =
-        cameraProperties.metadata[CameraCharacteristics.FLASH_INFO_AVAILABLE].let {
-            it != null && it
-        }
+    private val hasFlashUnit: Boolean = cameraProperties.isFlashAvailable()
 
     private val _torchState = MutableLiveData(TorchState.OFF)
     val torchStateLiveData: LiveData<Int>
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
index b7ca0cb..3e96a048 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraRequestControl.kt
@@ -102,8 +102,7 @@
      *
      *  This method will:
      *  (1) Stores [config], [tags] and [listeners] by [type] respectively. The new inputs above
-     *  will take place of the existing value of the [type]. If the [type] isn't set, it will
-     *  override the config which is stored as the [Type.DEFAULT].
+     *  will take place of the existing value of the [type].
      *  (2) Update the repeating request by merging all the [config], [tags] and [listeners] from
      *  all the defined types.
      *
@@ -130,9 +129,9 @@
     // 3A
     suspend fun setTorchAsync(enabled: Boolean): Deferred<Result3A>
     suspend fun startFocusAndMeteringAsync(
-        aeRegions: List<MeteringRectangle>,
-        afRegions: List<MeteringRectangle>,
-        awbRegions: List<MeteringRectangle>,
+        aeRegions: List<MeteringRectangle>? = null,
+        afRegions: List<MeteringRectangle>? = null,
+        awbRegions: List<MeteringRectangle>? = null,
         afTriggerStartAeMode: AeMode? = null
     ): Deferred<Result3A>
 
@@ -217,9 +216,9 @@
         }
 
     override suspend fun startFocusAndMeteringAsync(
-        aeRegions: List<MeteringRectangle>,
-        afRegions: List<MeteringRectangle>,
-        awbRegions: List<MeteringRectangle>,
+        aeRegions: List<MeteringRectangle>?,
+        afRegions: List<MeteringRectangle>?,
+        awbRegions: List<MeteringRectangle>?,
         afTriggerStartAeMode: AeMode?
     ): Deferred<Result3A> = graph.acquireSession().use {
         it.lock3A(
@@ -357,4 +356,4 @@
 
 fun TagBundle.toMap(): Map<String, Any> = mutableMapOf<String, Any>().also {
     listKeys().forEach { tagKey -> it[tagKey] = getTag(tagKey) as Any }
-}
\ No newline at end of file
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
index bb9c6c0..d2b76ff 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraState.kt
@@ -27,12 +27,12 @@
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.config.UseCaseCameraScope
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineStart
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.ExperimentalCoroutinesApi
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * This object keeps track of the state of the current [UseCaseCamera].
@@ -191,6 +191,12 @@
         }
     }
 
+    /**
+     * Tries to invoke [androidx.camera.camera2.pipe.CameraGraph.Session.startRepeating]
+     * with current (the most recent) set of values.
+     */
+    fun tryStartRepeating() = submitLatest()
+
     @OptIn(ExperimentalCoroutinesApi::class)
     private fun submitLatest() {
         // Update the cameraGraph with the most recent set of values.
@@ -234,4 +240,4 @@
             result?.complete(Unit)
         }
     }
-}
\ No newline at end of file
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/package-info.java
index 2092b25..c49f378 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 package androidx.camera.camera2.pipe.integration.impl;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/package-info.java b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/package-info.java
index 29b62d3..8391631 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/package-info.java
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/internal/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.camera2.pipe.integration.internal;
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2Interop.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2Interop.kt
index a373bc0..e12fbdc 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2Interop.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/interop/Camera2Interop.kt
@@ -87,7 +87,6 @@
          *
          * @param templateType The template type to set.
          * @return The current Extender.
-         * @hide
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         fun setCaptureRequestTemplate(templateType: Int): Extender<T> {
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapterTest.kt
index 2626737..05e8e70 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/CameraUseCaseAdapterTest.kt
@@ -21,6 +21,7 @@
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
+import android.util.Size
 import android.view.Surface
 import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
 import androidx.camera.camera2.pipe.integration.impl.createCaptureRequestOption
@@ -41,6 +42,8 @@
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class CameraUseCaseAdapterTest {
 
+    private val resolution: Size = Size(640, 480)
+
     @Test
     fun shouldApplyOptionsFromConfigToBuilder_whenDefaultConfigSet() {
         // Arrange
@@ -115,7 +118,8 @@
         val builder = SessionConfig.Builder()
 
         // Act
-        CameraUseCaseAdapter.DefaultSessionOptionsUnpacker.unpack(useCaseConfig, builder)
+        CameraUseCaseAdapter.DefaultSessionOptionsUnpacker.unpack(resolution,
+            useCaseConfig, builder)
 
         // Assert
         val config = builder.build()
@@ -159,6 +163,7 @@
         // Act
         val sessionBuilder = SessionConfig.Builder()
         CameraUseCaseAdapter.DefaultSessionOptionsUnpacker.unpack(
+            resolution,
             imageCaptureBuilder.useCaseConfig,
             sessionBuilder
         )
@@ -200,7 +205,8 @@
         val sessionBuilder = SessionConfig.Builder()
 
         // Act
-        CameraUseCaseAdapter.DefaultSessionOptionsUnpacker.unpack(useCaseConfig, sessionBuilder)
+        CameraUseCaseAdapter.DefaultSessionOptionsUnpacker.unpack(resolution,
+            useCaseConfig, sessionBuilder)
         val sessionConfig = sessionBuilder.build()
 
         // Assert
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
index edc02d80..77f4fe0 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/FocusMeteringControlTest.kt
@@ -27,12 +27,16 @@
 import android.os.Build
 import android.util.Rational
 import android.util.Size
+import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.CameraId
 import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.compat.ZoomCompat
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpMeteringRegionCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
 import androidx.camera.camera2.pipe.integration.impl.State3AControl
@@ -56,6 +60,7 @@
 import androidx.camera.testing.SurfaceTextureProvider
 import androidx.camera.testing.fakes.FakeCamera
 import androidx.camera.testing.fakes.FakeUseCase
+import androidx.concurrent.futures.await
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
@@ -84,6 +89,7 @@
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.StreamConfigurationMapBuilder
 import org.robolectric.util.ReflectionHelpers
 
 private const val CAMERA_ID_0 = "0" // 640x480 sensor size
@@ -91,6 +97,7 @@
 private const val CAMERA_ID_2 = "2" // 640x480 sensor size, not support AF_AUTO.
 private const val CAMERA_ID_3 = "3" // camera that does not support 3A regions.
 private const val CAMERA_ID_4 = "4" // camera 0 with LENS_FACING_FRONT
+private const val CAMERA_ID_5 = "5" // camera 0 supporting AF regions only
 
 private const val SENSOR_WIDTH = 640
 private const val SENSOR_HEIGHT = 480
@@ -110,6 +117,7 @@
     SENSOR_WIDTH,
     SENSOR_HEIGHT
 )
+
 // the following rectangles are for metering point (0, 0)
 private val M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480 = Rect(
     0, 60 - AREA_HEIGHT / 2,
@@ -329,18 +337,11 @@
     fun startFocusAndMetering_invalidPoint() = runBlocking {
         val invalidPoint = pointFactory.createPoint(1f, 1.1f)
 
-        startFocusMeteringAndAwait(FocusMeteringAction.Builder(invalidPoint).build())
+        val future = focusMeteringControl.startFocusAndMetering(
+            FocusMeteringAction.Builder(invalidPoint).build()
+        )
 
-        // TODO: This will probably throw an invalid argument exception in future instead of
-        //  passing the parameters to request control, better to assert the exception then.
-
-        val meteringRequests = fakeRequestControl.focusMeteringCalls.lastOrNull()
-            ?: FakeUseCaseCameraRequestControl.FocusMeteringParams()
-        with(meteringRequests) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(0)
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(0)
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(0)
-        }
+        assertFutureFailedWithIllegalArgumentException(future)
     }
 
     @Test
@@ -348,14 +349,14 @@
         startFocusMeteringAndAwait(FocusMeteringAction.Builder(point1).build())
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
 
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
 
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
         }
     }
 
@@ -370,18 +371,18 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(3)
-            assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_1)
-            assertWithMessage("Wrong AE region").that(aeRegions[1].rect).isEqualTo(M_RECT_2)
-            assertWithMessage("Wrong AE region").that(aeRegions[2].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(3)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(2)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(3)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
-            assertWithMessage("Wrong AF region").that(afRegions[1].rect).isEqualTo(M_RECT_2)
-            assertWithMessage("Wrong AF region").that(afRegions[2].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(3)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(1)?.rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(2)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
         }
     }
 
@@ -414,18 +415,22 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(3)
-            assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_1)
-            assertWithMessage("Wrong AE region").that(aeRegions[1].rect).isEqualTo(M_RECT_2)
-            assertWithMessage("Wrong AE region").that(aeRegions[2].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(3)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(2)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(3)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(flippedRect1)
-            assertWithMessage("Wrong AF region").that(afRegions[1].rect).isEqualTo(flippedRect2)
-            assertWithMessage("Wrong AF region").that(afRegions[2].rect).isEqualTo(flippedRect3)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(3)
+            assertWithMessage("Wrong AF region")
+                .that(afRegions?.get(0)?.rect).isEqualTo(flippedRect1)
+            assertWithMessage("Wrong AF region")
+                .that(afRegions?.get(1)?.rect).isEqualTo(flippedRect2)
+            assertWithMessage("Wrong AF region")
+                .that(afRegions?.get(2)?.rect).isEqualTo(flippedRect3)
 
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AWB regions")
+                .that(awbRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
         }
     }
 
@@ -444,16 +449,16 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(2)
-            assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_2)
-            assertWithMessage("Wrong AE region").that(aeRegions[1].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(2)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(1)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(2)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_2)
-            assertWithMessage("Wrong AF region").that(afRegions[1].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(2)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(1)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
         }
     }
 
@@ -468,14 +473,14 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AE regions").that(aeRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AE region").that(aeRegions[0].rect).isEqualTo(M_RECT_3)
+            assertWithMessage("Wrong number of AE regions").that(aeRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AE region").that(aeRegions?.get(0)?.rect).isEqualTo(M_RECT_3)
 
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
 
-            assertWithMessage("Wrong number of AWB regions").that(awbRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AWB region").that(awbRegions[0].rect).isEqualTo(M_RECT_2)
+            assertWithMessage("Wrong number of AWB regions").that(awbRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AWB region").that(awbRegions?.get(0)?.rect).isEqualTo(M_RECT_2)
         }
     }
 
@@ -508,9 +513,9 @@
             cropRect.centerY() + areaHeight / 2
         )
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
             assertWithMessage("Wrong AF region")
-                .that(afRegions[0].rect).isEqualTo(adjustedRect)
+                .that(afRegions?.get(0)?.rect).isEqualTo(adjustedRect)
         }
     }
 
@@ -549,9 +554,9 @@
             cropRect.centerY() + areaHeight / 2
         )
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
             assertWithMessage("Wrong AF region")
-                .that(afRegions[0].rect).isEqualTo(adjustedRect)
+                .that(afRegions?.get(0)?.rect).isEqualTo(adjustedRect)
         }
     }
 
@@ -568,9 +573,9 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
             assertWithMessage("Wrong AF region")
-                .that(afRegions[0].rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480)
+                .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480)
         }
     }
 
@@ -588,9 +593,9 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
             assertWithMessage("Wrong AF region")
-                .that(afRegions[0].rect).isEqualTo(M_RECT_PVIEW_RATIO_4x3_SENSOR_1920x1080)
+                .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_4x3_SENSOR_1920x1080)
         }
     }
 
@@ -613,9 +618,9 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
             assertWithMessage("Wrong AF region")
-                .that(afRegions[0].rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480)
+                .that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_PVIEW_RATIO_16x9_SENSOR_640x480)
         }
     }
 
@@ -638,8 +643,8 @@
         // which is the size of SENSOR_1 in this test. So the point is not adjusted,
         // and simply M_RECT_1 (metering rectangle of point1 with SENSOR_1) should be used.
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(1)
-            assertWithMessage("Wrong AF region").that(afRegions[0].rect).isEqualTo(M_RECT_1)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong AF region").that(afRegions?.get(0)?.rect).isEqualTo(M_RECT_1)
         }
     }
 
@@ -656,22 +661,22 @@
         )
 
         with(fakeRequestControl.focusMeteringCalls.last()) {
-            assertWithMessage("Wrong number of AF regions").that(afRegions.size).isEqualTo(3)
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(3)
 
             assertWithMessage("Wrong AF region width")
-                .that(afRegions[0].rect.width()).isEqualTo((SENSOR_WIDTH * 1.0f).toInt())
+                .that(afRegions?.get(0)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 1.0f).toInt())
             assertWithMessage("Wrong AF region height")
-                .that(afRegions[0].rect.height()).isEqualTo((SENSOR_HEIGHT * 1.0f).toInt())
+                .that(afRegions?.get(0)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 1.0f).toInt())
 
             assertWithMessage("Wrong AF region width")
-                .that(afRegions[1].rect.width()).isEqualTo((SENSOR_WIDTH * 0.5f).toInt())
+                .that(afRegions?.get(1)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 0.5f).toInt())
             assertWithMessage("Wrong AF region height")
-                .that(afRegions[1].rect.height()).isEqualTo((SENSOR_HEIGHT * 0.5f).toInt())
+                .that(afRegions?.get(1)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 0.5f).toInt())
 
             assertWithMessage("Wrong AF region width")
-                .that(afRegions[2].rect.width()).isEqualTo((SENSOR_WIDTH * 0.1f).toInt())
+                .that(afRegions?.get(2)?.rect?.width()).isEqualTo((SENSOR_WIDTH * 0.1f).toInt())
             assertWithMessage("Wrong AF region height")
-                .that(afRegions[2].rect.height()).isEqualTo((SENSOR_HEIGHT * 0.1f).toInt())
+                .that(afRegions?.get(2)?.rect?.height()).isEqualTo((SENSOR_HEIGHT * 0.1f).toInt())
         }
     }
 
@@ -860,11 +865,7 @@
         ).build()
         val future = focusMeteringControl.startFocusAndMetering(action)
 
-        assertThrows(ExecutionException::class.java) {
-            future[500, TimeUnit.MILLISECONDS]
-        }.also {
-            assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
-        }
+        assertFutureFailedWithIllegalArgumentException(future)
     }
 
     @Test
@@ -876,11 +877,7 @@
         ).build()
         val future = focusMeteringControl.startFocusAndMetering(action)
 
-        assertThrows(ExecutionException::class.java) {
-            future[500, TimeUnit.MILLISECONDS]
-        }.also {
-            assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
-        }
+        assertFutureFailedWithIllegalArgumentException(future)
     }
 
     @Test
@@ -892,11 +889,7 @@
         ).build()
         val future = focusMeteringControl.startFocusAndMetering(action)
 
-        assertThrows(ExecutionException::class.java) {
-            future[500, TimeUnit.MILLISECONDS]
-        }.also {
-            assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
-        }
+        assertFutureFailedWithIllegalArgumentException(future)
     }
 
     @Test
@@ -939,11 +932,7 @@
             .addPoint(invalidPt3, FocusMeteringAction.FLAG_AWB).build()
         val future = focusMeteringControl.startFocusAndMetering(action)
 
-        assertThrows(ExecutionException::class.java) {
-            future[500, TimeUnit.MILLISECONDS]
-        }.also {
-            assertThat(it.cause).isInstanceOf(IllegalArgumentException::class.java)
-        }
+        assertFutureFailedWithIllegalArgumentException(future)
     }
 
     @Test
@@ -1215,9 +1204,9 @@
     @Test
     fun startFocusMetering_frameMetadataNullWithOkStatus_futureCompletesWithFocusSuccessful() {
         /**
-         * According to [Controller3A.lock3A] method documentation,
-         * if the operation is not supported by the camera device, then this method returns early
-         * with Result3A made of 'OK' status and 'null' metadata.
+         * According to [androidx.camera.camera2.pipe.graph.Controller3A.lock3A] method
+         * documentation, if the operation is not supported by the camera device, then this method
+         * returns early with Result3A made of 'OK' status and 'null' metadata.
          */
         fakeRequestControl.focusMeteringResult = CompletableDeferred(
             Result3A(
@@ -1236,6 +1225,66 @@
         assertFutureFocusCompleted(future, false)
     }
 
+    @Test
+    fun startFocusMetering_noAePoint_aeRegionsSetToDefault() {
+        startFocusMeteringAndAwait(
+            FocusMeteringAction.Builder(
+                point1, FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AWB
+            ).build()
+        )
+
+        with(fakeRequestControl.focusMeteringCalls.last()) {
+            assertWithMessage("Wrong AE regions").that(aeRegions)
+                .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList())
+        }
+    }
+
+    @Test
+    fun startFocusMetering_noAfPoint_afRegionsSetToDefault() {
+        startFocusMeteringAndAwait(
+            FocusMeteringAction.Builder(
+                point1, FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+            ).build()
+        )
+
+        with(fakeRequestControl.focusMeteringCalls.last()) {
+            assertWithMessage("Wrong AF regions").that(afRegions)
+                .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList())
+        }
+    }
+
+    @Test
+    fun startFocusMetering_noAwbPoint_awbRegionsSetToDefault() {
+        startFocusMeteringAndAwait(
+            FocusMeteringAction.Builder(
+                point1, FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AF
+            ).build()
+        )
+
+        with(fakeRequestControl.focusMeteringCalls.last()) {
+            assertWithMessage("Wrong AWB regions").that(awbRegions)
+                .isEqualTo(CameraGraph.Constants3A.METERING_REGIONS_DEFAULT.toList())
+        }
+    }
+
+    @Test
+    fun startFocusMetering_onlyAfSupported_unsupportedRegionsNotSet() {
+        // camera 5 supports 1 AF and 0 AE/AWB regions
+        focusMeteringControl = initFocusMeteringControl(cameraId = CAMERA_ID_5)
+
+        startFocusMeteringAndAwait(FocusMeteringAction.Builder(
+            point1,
+            FocusMeteringAction.FLAG_AF or FocusMeteringAction.FLAG_AE or
+                FocusMeteringAction.FLAG_AWB
+        ).build())
+
+        with(fakeRequestControl.focusMeteringCalls.last()) {
+            assertWithMessage("Wrong number of AE regions").that(aeRegions).isNull()
+            assertWithMessage("Wrong number of AF regions").that(afRegions?.size).isEqualTo(1)
+            assertWithMessage("Wrong number of AWB regions").that(awbRegions).isNull()
+        }
+    }
+
     // TODO: Port the following tests once their corresponding logics have been implemented.
     //  - [b/255679866] triggerAfWithTemplate, triggerAePrecaptureWithTemplate,
     //          cancelAfAeTriggerWithTemplate
@@ -1248,6 +1297,14 @@
         assertThat(focusMeteringResult.isFocusSuccessful).isEqualTo(isFocused)
     }
 
+    private fun <T> assertFutureFailedWithIllegalArgumentException(future: ListenableFuture<T>) {
+        assertThrows(ExecutionException::class.java) {
+            future[3, TimeUnit.SECONDS]
+        }.apply {
+            assertThat(cause).isInstanceOf(IllegalArgumentException::class.java)
+        }
+    }
+
     private fun <T> assertFutureFailedWithOperationCancellation(future: ListenableFuture<T>) {
         assertThrows(ExecutionException::class.java) {
             future[3, TimeUnit.SECONDS]
@@ -1335,18 +1392,24 @@
         state3AControl: State3AControl = createState3AControl(cameraId),
         zoomCompat: ZoomCompat = FakeZoomCompat()
     ) = FocusMeteringControl(
-            cameraPropertiesMap[cameraId]!!,
-            MeteringRegionCorrection.Bindings.provideMeteringRegionCorrection(
-                CameraQuirks(cameraPropertiesMap[cameraId]!!.metadata)
-            ),
-            state3AControl,
-            useCaseThreads,
-            zoomCompat
-        ).apply {
-            fakeUseCaseCamera.runningUseCases = useCases
-            useCaseCamera = fakeUseCaseCamera
-            onRunningUseCasesChanged()
-        }
+        cameraPropertiesMap[cameraId]!!,
+        MeteringRegionCorrection.Bindings.provideMeteringRegionCorrection(
+            CameraQuirks(
+                cameraPropertiesMap[cameraId]!!.metadata,
+                StreamConfigurationMapCompat(
+                    StreamConfigurationMapBuilder.newBuilder().build(),
+                    OutputSizesCorrector(cameraPropertiesMap[cameraId]!!.metadata)
+                )
+            )
+        ),
+        state3AControl,
+        useCaseThreads,
+        zoomCompat
+    ).apply {
+        fakeUseCaseCamera.runningUseCases = useCases
+        useCaseCamera = fakeUseCaseCamera
+        onRunningUseCasesChanged()
+    }
 
     private fun initCameraProperties(
         cameraIdStr: String,
@@ -1459,12 +1522,24 @@
             CAMERA_ID_4,
             characteristics4
         )
+
+        // **** Camera 5 characteristics (same as Camera 0, but supports AF regions only) **** //
+        val characteristics5 = characteristics0 + mapOf(
+            CameraCharacteristics.CONTROL_MAX_REGIONS_AF to 3,
+            CameraCharacteristics.CONTROL_MAX_REGIONS_AE to 0,
+            CameraCharacteristics.CONTROL_MAX_REGIONS_AWB to 0
+        )
+
+        cameraPropertiesMap[CAMERA_ID_5] = initCameraProperties(
+            CAMERA_ID_5,
+            characteristics5
+        )
     }
 
     private fun createPreview(suggestedStreamSpecResolution: Size) =
         Preview.Builder()
             .setCaptureOptionUnpacker { _, _ -> }
-            .setSessionOptionUnpacker() { _, _ -> }
+            .setSessionOptionUnpacker() { _, _, _ -> }
             .build().apply {
                 setSurfaceProvider(
                     CameraXExecutors.mainThreadExecutor(),
@@ -1481,7 +1556,10 @@
         cameraId: String = CAMERA_ID_0,
         properties: CameraProperties = cameraPropertiesMap[cameraId]!!,
         useCaseCamera: UseCaseCamera = fakeUseCaseCamera,
-    ) = State3AControl(properties).apply {
+    ) = State3AControl(
+        properties,
+        NoOpAutoFlashAEModeDisabler,
+    ).apply {
         this.useCaseCamera = useCaseCamera
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
index 362d5b9..2095721 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/adapter/SessionConfigAdapterTest.kt
@@ -28,6 +28,7 @@
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.core.impl.utils.futures.Futures
+import androidx.camera.core.streamsharing.StreamSharing
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
 import androidx.testutils.MainDispatcherRule
@@ -209,6 +210,18 @@
     }
 
     @Test
+    fun populateSurfaceToStreamUseCaseMappingStreamSharing() {
+        Mockito.`when`(mockSurface.containerClass).thenReturn(StreamSharing::class.java)
+        Mockito.`when`(mockSessionConfig.surfaces).thenReturn(listOf(mockSurface))
+        Mockito.`when`(mockSessionConfig.implementationOptions).thenReturn(mockImplementationOption)
+        val sessionConfigs: MutableCollection<SessionConfig> = ArrayList()
+        sessionConfigs.add(mockSessionConfig)
+        val mapping = sessionConfigAdapter.getSurfaceToStreamUseCaseMapping(sessionConfigs, true)
+        TestCase.assertTrue(mapping.isNotEmpty())
+        TestCase.assertTrue(mapping[mockSurface] == 3L)
+    }
+
+    @Test
     fun populateSurfaceToStreamUseCaseMappingCustomized() {
         Mockito.`when`(mockSurface.containerClass).thenReturn(MediaCodec::class.java)
         Mockito.`when`(mockSessionConfig.surfaces).thenReturn(listOf(mockSurface))
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/OutputSizesCorrectorTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/OutputSizesCorrectorTest.kt
new file mode 100644
index 0000000..b81db73
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/OutputSizesCorrectorTest.kt
@@ -0,0 +1,241 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.graphics.ImageFormat
+import android.graphics.SurfaceTexture
+import android.hardware.camera2.CameraCharacteristics
+import android.os.Build
+import android.util.Size
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.impl.ImageFormatConstants
+import com.google.common.truth.Truth
+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.StreamConfigurationMapBuilder
+import org.robolectric.util.ReflectionHelpers
+
+private const val CAMERA_ID_0 = "0"
+
+private const val MOTOROLA_BRAND_NAME = "motorola"
+private const val MOTOROLA_E5_PLAY_MODEL_NAME = "moto e5 play"
+private const val SAMSUNG_BRAND_NAME = "SAMSUNG"
+private const val SAMSUNG_J7_DEVICE_NAME = "J7XELTE"
+
+private val outputSizes = arrayOf(
+    // Samsung J7 API 27 above excluded sizes
+    Size(4128, 3096),
+    Size(4128, 2322),
+    Size(3088, 3088),
+    Size(3264, 2448),
+    Size(3264, 1836),
+    Size(2048, 1536),
+    Size(2048, 1152),
+    Size(1920, 1080),
+
+    // Add some other sizes
+    Size(1280, 960),
+    Size(1280, 720),
+    Size(640, 480),
+    Size(320, 240),
+)
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class OutputSizesCorrectorTest {
+    @Test
+    fun canAddExtraSupportedSizesForMotoE5PlayByFormat() {
+        val outputSizesCorrector = createOutputSizesCorrector(
+            MOTOROLA_BRAND_NAME,
+            null,
+            MOTOROLA_E5_PLAY_MODEL_NAME,
+            CAMERA_ID_0,
+            CameraCharacteristics.LENS_FACING_BACK,
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+
+        val resultList = outputSizesCorrector.applyQuirks(
+            arrayOf(
+                Size(4128, 3096),
+                Size(4128, 2322),
+                Size(3088, 3088),
+            ),
+            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+        )!!.toList()
+
+        Truth.assertThat(resultList).containsExactlyElementsIn(
+            listOf(
+                Size(4128, 3096),
+                Size(4128, 2322),
+                Size(3088, 3088),
+
+                // Added extra supported sizes for Motorola E5 Play device
+                Size(1920, 1080),
+                Size(1440, 1080),
+                Size(1280, 720),
+                Size(960, 720),
+                Size(864, 480),
+                Size(720, 480),
+            )
+        ).inOrder()
+    }
+
+    @Test
+    fun canAddExtraSupportedSizesForMotoE5PlayByClass() {
+        val outputSizesCorrector = createOutputSizesCorrector(
+            MOTOROLA_BRAND_NAME,
+            null,
+            MOTOROLA_E5_PLAY_MODEL_NAME,
+            CAMERA_ID_0,
+            CameraCharacteristics.LENS_FACING_BACK,
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+        )
+
+        val resultList = outputSizesCorrector.applyQuirks(
+            arrayOf(
+                Size(4128, 3096),
+                Size(4128, 2322),
+                Size(3088, 3088),
+            ),
+            SurfaceTexture::class.java
+        )!!.toList()
+
+        Truth.assertThat(resultList).containsExactlyElementsIn(
+            listOf(
+                Size(4128, 3096),
+                Size(4128, 2322),
+                Size(3088, 3088),
+
+                // Added extra supported sizes for Motorola E5 Play device
+                Size(1920, 1080),
+                Size(1440, 1080),
+                Size(1280, 720),
+                Size(960, 720),
+                Size(864, 480),
+                Size(720, 480),
+            )
+        ).inOrder()
+    }
+
+    @Test
+    @Config(minSdk = 27)
+    fun canExcludeSamsungJ7Api27AboveProblematicSizesByFormat() {
+        val outputSizesCorrector = createOutputSizesCorrector(
+            SAMSUNG_BRAND_NAME,
+            SAMSUNG_J7_DEVICE_NAME,
+            null,
+            CAMERA_ID_0,
+            CameraCharacteristics.LENS_FACING_BACK,
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+        )
+
+        val sizesWithQuirks: Array<Size> =
+            outputSizesCorrector.applyQuirks(outputSizes, ImageFormat.YUV_420_888)!!
+        val resultList = mutableListOf<Size>().apply {
+            sizesWithQuirks.forEach { size ->
+                add(size)
+            }
+        }
+
+        Truth.assertThat(resultList).containsExactlyElementsIn(
+            listOf(
+                Size(4128, 3096),
+                Size(4128, 2322),
+                Size(3088, 3088),
+                Size(3264, 2448),
+                Size(3264, 1836),
+
+                // Size(2048, 1536), Size(2048, 1152), Size(1920, 1080) are excluded for YUV format
+
+                Size(1280, 960),
+                Size(1280, 720),
+                Size(640, 480),
+                Size(320, 240),
+            )
+        ).inOrder()
+    }
+
+    @Test
+    @Config(minSdk = 27)
+    fun canExcludeSamsungJ7Api27AboveProblematicSizesByClass() {
+        val outputSizesCorrector = createOutputSizesCorrector(
+            SAMSUNG_BRAND_NAME,
+            SAMSUNG_J7_DEVICE_NAME,
+            null,
+            CAMERA_ID_0,
+            CameraCharacteristics.LENS_FACING_BACK,
+            CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY
+        )
+
+        val resultList: List<Size> = outputSizesCorrector.applyQuirks(
+            outputSizes,
+            SurfaceTexture::class.java
+        )!!.toList()
+
+        Truth.assertThat(resultList).containsExactlyElementsIn(
+            listOf(
+                // Size(4128, 3096), Size(4128, 2322), Size(3088, 3088), Size(3264, 2448),
+                // Size(3264, 1836), Size(2048, 1536), Size(2048, 1152), Size(1920, 1080)
+                // are excluded for SurfaceTexture class
+
+                Size(1280, 960),
+                Size(1280, 720),
+                Size(640, 480),
+                Size(320, 240),
+            )
+        ).inOrder()
+    }
+
+    private fun createOutputSizesCorrector(
+        brand: String,
+        device: String?,
+        model: String?,
+        cameraId: String,
+        lensFacing: Int,
+        hardwareLevel: Int
+    ): OutputSizesCorrector {
+        ReflectionHelpers.setStaticField(Build::class.java, "BRAND", brand)
+        device?.let {
+            ReflectionHelpers.setStaticField(Build::class.java, "DEVICE", it)
+        }
+        model?.let {
+            ReflectionHelpers.setStaticField(Build::class.java, "MODEL", it)
+        }
+
+        return OutputSizesCorrector(
+            FakeCameraMetadata(
+                cameraId = CameraId(cameraId), characteristics = mapOf(
+                    CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL to hardwareLevel,
+                    CameraCharacteristics.LENS_FACING to lensFacing,
+                    CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP to
+                        StreamConfigurationMapBuilder.newBuilder()
+                            .apply {
+                                outputSizes.forEach { outputSize ->
+                                    addOutputSize(outputSize)
+                                }
+                            }.build()
+                )
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
new file mode 100644
index 0000000..f972ee6
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/StreamConfigurationMapCompatTest.kt
@@ -0,0 +1,80 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat
+
+import android.graphics.SurfaceTexture
+import android.os.Build
+import android.util.Size
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import androidx.camera.core.impl.ImageFormatConstants
+import com.google.common.truth.Truth
+import org.junit.Before
+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.StreamConfigurationMapBuilder
+
+/**
+ * Unit tests for [StreamConfigurationMapCompat].
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class StreamConfigurationMapCompatTest {
+
+    companion object {
+        private val SIZE_480P = Size(640, 480)
+        private val SIZE_720P = Size(1080, 720)
+        private val SIZE_1080P = Size(1920, 1080)
+        private const val FORMAT_PRIVATE =
+            ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+    }
+
+    private lateinit var streamConfigurationMapCompat: StreamConfigurationMapCompat
+    private val privateFormatOutputSizes = listOf(SIZE_1080P, SIZE_720P, SIZE_480P)
+
+    @Before
+    fun setUp() {
+        val builder = StreamConfigurationMapBuilder.newBuilder().apply {
+            privateFormatOutputSizes.forEach { size ->
+                addOutputSize(FORMAT_PRIVATE, size)
+            }
+        }
+        streamConfigurationMapCompat =
+            StreamConfigurationMapCompat(
+                builder.build(),
+            OutputSizesCorrector(FakeCameraMetadata())
+            )
+    }
+
+    @Test
+    fun getOutputSizes_withFormat_callGetOutputSizes() {
+        Truth.assertThat(
+            streamConfigurationMapCompat.getOutputSizes(FORMAT_PRIVATE)?.toList()
+        ).containsExactlyElementsIn(privateFormatOutputSizes)
+    }
+
+    @Test
+    fun getOutputSizes_withClass_callGetOutputSizes() {
+        Truth.assertThat(
+            streamConfigurationMapCompat.getOutputSizes(SurfaceTexture::class.java)?.toList()
+        ).containsExactlyElementsIn(privateFormatOutputSizes)
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/AfRegionFlipHorizontallyQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/AfRegionFlipHorizontallyQuirkTest.kt
index 586cd21..5f4c468 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/AfRegionFlipHorizontallyQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/AfRegionFlipHorizontallyQuirkTest.kt
@@ -17,6 +17,8 @@
 package androidx.camera.camera2.pipe.integration.compat.quirk
 
 import android.hardware.camera2.CameraCharacteristics
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.impl.Quirks
 import com.google.common.truth.Truth
@@ -28,6 +30,7 @@
 import org.robolectric.shadow.api.Shadow
 import org.robolectric.shadows.ShadowBuild
 import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.StreamConfigurationMapBuilder
 
 @RunWith(ParameterizedRobolectricTestRunner::class)
 @DoNotInstrument
@@ -60,11 +63,19 @@
             lensFacing
         )
 
-        val cameraMetadata = FakeCameraMetadata(characteristics = mapOf(
-            CameraCharacteristics.LENS_FACING to lensFacing
-        ))
+        val cameraMetadata = FakeCameraMetadata(
+            characteristics = mapOf(
+                CameraCharacteristics.LENS_FACING to lensFacing
+            )
+        )
 
-        return CameraQuirks(cameraMetadata).quirks
+        return CameraQuirks(
+            cameraMetadata,
+            StreamConfigurationMapCompat(
+                StreamConfigurationMapBuilder.newBuilder().build(),
+                OutputSizesCorrector(cameraMetadata)
+            )
+        ).quirks
     }
 
     @Test
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirkTest.kt
index 65cadaa..7b2f9e2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/CamcorderProfileResolutionQuirkTest.kt
@@ -22,6 +22,8 @@
 import android.hardware.camera2.params.StreamConfigurationMap
 import android.os.Build
 import android.util.Size
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.testing.EncoderProfilesUtil
 import com.google.common.truth.Truth.assertThat
@@ -69,7 +71,12 @@
                     EncoderProfilesUtil.RESOLUTION_1080P
                 )
             )
-        val quirk = CamcorderProfileResolutionQuirk(cameraMetadata)
+        val quirk = CamcorderProfileResolutionQuirk(
+            StreamConfigurationMapCompat(
+                cameraMetadata[CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP]!!,
+                OutputSizesCorrector(cameraMetadata)
+            )
+        )
 
         assertThat(quirk.supportedResolutions[0])
             .isEqualTo(EncoderProfilesUtil.RESOLUTION_2160P)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/JpegHalCorruptImageQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/JpegHalCorruptImageQuirkTest.kt
index bad1d44..bc371e4 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/JpegHalCorruptImageQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/JpegHalCorruptImageQuirkTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2.pipe.integration.compat.quirk
 
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -24,6 +26,7 @@
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 import org.robolectric.shadows.ShadowBuild
+import org.robolectric.shadows.StreamConfigurationMapBuilder
 
 @RunWith(ParameterizedRobolectricTestRunner::class)
 @DoNotInstrument
@@ -48,7 +51,13 @@
     fun canEnableQuirkCorrectly() {
         ShadowBuild.setDevice(device)
 
-        val cameraQuirks = CameraQuirks(FakeCameraMetadata()).quirks
+        val cameraQuirks = CameraQuirks(
+            FakeCameraMetadata(),
+            StreamConfigurationMapCompat(
+                StreamConfigurationMapBuilder.newBuilder().build(),
+                OutputSizesCorrector(FakeCameraMetadata())
+            )
+        ).quirks
 
         assertThat(cameraQuirks.contains(JpegHalCorruptImageQuirk::class.java))
             .isEqualTo(quirkEnablingExpected)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/YuvImageOnePixelShiftQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/YuvImageOnePixelShiftQuirkTest.kt
index b6841ee..43d8e7f 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/YuvImageOnePixelShiftQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/quirk/YuvImageOnePixelShiftQuirkTest.kt
@@ -16,6 +16,8 @@
 
 package androidx.camera.camera2.pipe.integration.compat.quirk
 
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
 import androidx.camera.core.internal.compat.quirk.OnePixelShiftQuirk
 import com.google.common.truth.Truth.assertThat
@@ -25,6 +27,7 @@
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
 import org.robolectric.shadows.ShadowBuild
+import org.robolectric.shadows.StreamConfigurationMapBuilder
 
 @RunWith(ParameterizedRobolectricTestRunner::class)
 @DoNotInstrument
@@ -39,7 +42,13 @@
         ShadowBuild.setBrand(brand)
         ShadowBuild.setModel(model)
 
-        val cameraQuirks = CameraQuirks(FakeCameraMetadata()).quirks
+        val cameraQuirks = CameraQuirks(
+            FakeCameraMetadata(),
+            StreamConfigurationMapCompat(
+                StreamConfigurationMapBuilder.newBuilder().build(),
+                OutputSizesCorrector(FakeCameraMetadata())
+            )
+        ).quirks
 
         assertThat(cameraQuirks.contains(OnePixelShiftQuirk::class.java))
             .isEqualTo(quirkEnablingExpected)
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisablerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisablerTest.kt
new file mode 100644
index 0000000..04025fc
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/AutoFlashAEModeDisablerTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
+import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
+import androidx.camera.camera2.pipe.testing.FakeCameraMetadata
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadows.StreamConfigurationMapBuilder
+import org.robolectric.util.ReflectionHelpers
+
+@RunWith(RobolectricCameraPipeTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+@DoNotInstrument
+class AutoFlashAEModeDisablerTest {
+    private val anyCameraId = "0"
+
+    @Test
+    fun changeAeAutoFlashToAeOn_onSamsungA300H() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A300H")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun changeOnAutoFlashToOn_onSamsungA300YZ() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A300YZ")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun keepAeOn_onSamsungA300H() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A300H")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun keepAeAlwaysOn_onSamsungA300H() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A300H")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON_ALWAYS_FLASH)
+    }
+
+    @Test
+    fun changeOnAutoFlashToOn_onSamsungJ5() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-J510FN")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun keepAeAutoFlash_onSamsungOtherDevices() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Samsung")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A3XXX")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+    }
+
+    @Test
+    fun changeOnAutoFlashToOn_onSamsungJ7FrontCamera() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "sm-j710f")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_FRONT)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON)
+    }
+
+    @Test
+    fun keepAeAutoFlash_onSamsungJ7MainCamera() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "sm-j710f")
+        val aeMode: Int = createAutoFlashAEModeDisabler(CameraCharacteristics.LENS_FACING_BACK)
+            .getCorrectedAeMode(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+        assertThat(aeMode).isEqualTo(CaptureRequest.CONTROL_AE_MODE_ON_AUTO_FLASH)
+    }
+
+    private fun createAutoFlashAEModeDisabler(lensFacing: Int): AutoFlashAEModeDisabler {
+        val metadata = FakeCameraMetadata(mapOf(CameraCharacteristics.LENS_FACING to lensFacing))
+        return AutoFlashAEModeDisabler.Bindings.provideAEModeDisabler(
+            CameraQuirks(
+                metadata, StreamConfigurationMapCompat(
+                    StreamConfigurationMapBuilder.newBuilder().build(),
+                    OutputSizesCorrector(FakeCameraMetadata())
+                )
+            )
+        )
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityCheckerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityCheckerTest.kt
new file mode 100644
index 0000000..60ddf46
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/FlashAvailabilityCheckerTest.kt
@@ -0,0 +1,147 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureResult
+import android.os.Build
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraMetadata
+import androidx.camera.camera2.pipe.Metadata
+import androidx.camera.camera2.pipe.integration.compat.workaround.FlashAvailabilityCheckerTest.TestCameraMetadata.Mode
+import androidx.camera.camera2.pipe.integration.impl.CameraProperties
+import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
+import com.google.common.truth.Truth
+import java.nio.BufferUnderflowException
+import kotlin.reflect.KClass
+import org.junit.Assert
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
+
+// @Config() is left out since there currently aren't any API level dependencies in this workaround
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+class FlashAvailabilityCheckerTest(
+    private val manufacturer: String,
+    private val model: String,
+    private val cameraProperties: CameraProperties
+) {
+    @Before
+    fun setup() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", manufacturer)
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", model)
+    }
+
+    @Test
+    fun isFlashAvailable_doesNotThrow_whenRethrowDisabled() {
+        cameraProperties.isFlashAvailable()
+    }
+
+    @Test
+    fun isFlashAvailable_throwsForUnexpectedDevice() {
+        Assume.assumeTrue(Build.MODEL == "unexpected_throwing_device")
+        Assert.assertThrows(BufferUnderflowException::class.java) {
+            cameraProperties.isFlashAvailable(/*rethrowOnError=*/true)
+        }
+    }
+
+    @Test
+    fun isFlashAvailable_returnsFalse_whenFlashAvailableReturnsNull() {
+        Assume.assumeTrue(Build.MODEL == "null_returning_device")
+
+        Truth.assertThat(cameraProperties.isFlashAvailable()).isFalse()
+    }
+
+    private class TestCameraMetadata(
+        private val mode: Mode = Mode.DEFAULT,
+        private val characteristics: Map<CameraCharacteristics.Key<*>, Any?> = emptyMap(),
+        val metadata: Map<Metadata.Key<*>, Any?> = emptyMap(),
+        cameraId: CameraId = CameraId("0"),
+        override val keys: Set<CameraCharacteristics.Key<*>> = emptySet(),
+        override val requestKeys: Set<CaptureRequest.Key<*>> = emptySet(),
+        override val resultKeys: Set<CaptureResult.Key<*>> = emptySet(),
+        override val sessionKeys: Set<CaptureRequest.Key<*>> = emptySet(),
+        val physicalMetadata: Map<CameraId, CameraMetadata> = emptyMap(),
+        override val physicalRequestKeys: Set<CaptureRequest.Key<*>> = emptySet(),
+    ) : CameraMetadata {
+        enum class Mode {
+            ALWAYS_NULL,
+            THROW_BUFFER_UNDERFLOW_EXCEPTION,
+            DEFAULT,
+        }
+
+        override fun <T> get(key: CameraCharacteristics.Key<T>): T? {
+            @Suppress("UNCHECKED_CAST")
+            return when (mode) {
+                Mode.ALWAYS_NULL -> null
+                Mode.THROW_BUFFER_UNDERFLOW_EXCEPTION -> throw BufferUnderflowException()
+                else -> characteristics[key] as T?
+            }
+        }
+
+        @Suppress("UNCHECKED_CAST")
+        override fun <T> get(key: Metadata.Key<T>): T? = metadata[key] as T?
+
+        override fun <T> getOrDefault(key: CameraCharacteristics.Key<T>, default: T): T =
+            get(key) ?: default
+
+        override fun <T> getOrDefault(key: Metadata.Key<T>, default: T): T = get(key) ?: default
+
+        override val camera: CameraId = cameraId
+        override val isRedacted: Boolean = false
+
+        override val physicalCameraIds: Set<CameraId> = physicalMetadata.keys
+        override suspend fun getPhysicalMetadata(cameraId: CameraId): CameraMetadata =
+            physicalMetadata[cameraId]!!
+
+        override fun awaitPhysicalMetadata(cameraId: CameraId): CameraMetadata =
+            physicalMetadata[cameraId]!!
+
+        override fun <T : Any> unwrapAs(type: KClass<T>): T? = null
+    }
+
+    companion object {
+        private const val FAKE_OEM = "fake_oem"
+        private val flashAvailabilityTrueProvider = FakeCameraProperties(
+            metadata = TestCameraMetadata(
+                characteristics = mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true)
+            )
+        )
+        private val bufferUnderflowProvider = FakeCameraProperties(
+            metadata = TestCameraMetadata(mode = Mode.THROW_BUFFER_UNDERFLOW_EXCEPTION)
+        )
+        private val flashAvailabilityNullProvider = FakeCameraProperties(
+            metadata = TestCameraMetadata(mode = Mode.ALWAYS_NULL)
+        )
+
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(name = "manufacturer={0}, model={1}")
+        fun data() = mutableListOf<Array<Any?>>().apply {
+            add(arrayOf("sprd", "LEMP", bufferUnderflowProvider))
+            add(arrayOf("sprd", "DM20C", bufferUnderflowProvider))
+            add(arrayOf(FAKE_OEM, "unexpected_throwing_device", bufferUnderflowProvider))
+            add(arrayOf(FAKE_OEM, "not_a_real_device", flashAvailabilityTrueProvider))
+            add(arrayOf(FAKE_OEM, "null_returning_device", flashAvailabilityNullProvider))
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnetQuirkTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnetQuirkTest.kt
index ca4f2ef..a04699a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnetQuirkTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/PreviewPixelHDRnetQuirkTest.kt
@@ -81,6 +81,9 @@
         }
     }
 
+    private val resolutionHD: Size = Size(1280, 720)
+    private val resolutionVGA: Size = Size(640, 480)
+
     private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
 
     @Before
@@ -100,6 +103,7 @@
     fun previewShouldApplyToneModeForHDRNet() {
         // Arrange
         val cameraUseCaseAdapter = configureCameraUseCaseAdapter(
+            resolutionVGA,
             configType = PreviewConfig::class.java
         )
         val preview = Preview.Builder().build()
@@ -127,6 +131,7 @@
     fun otherUseCasesNotApplyHDRNet() {
         // Arrange
         cameraUseCaseAdapter = configureCameraUseCaseAdapter(
+            resolutionVGA,
             configType = ImageCaptureConfig::class.java
         )
 
@@ -141,7 +146,27 @@
         ).isNull()
     }
 
+    @Test
+    fun resolution16x9NotApplyHDRNet() {
+        // Arrange
+        cameraUseCaseAdapter = configureCameraUseCaseAdapter(
+            resolutionHD,
+            configType = PreviewConfig::class.java
+        )
+
+        // Act. Update UseCase to create SessionConfig
+        val preview = Preview.Builder().build()
+        cameraUseCaseAdapter.addUseCases(setOf<UseCase>(preview))
+
+        assertThat(
+            Camera2ImplConfig(
+                preview.sessionConfig.repeatingCaptureConfig.implementationOptions
+            ).getCaptureRequestOption(CaptureRequest.TONEMAP_MODE)
+        ).isNull()
+    }
+
     private fun configureCameraUseCaseAdapter(
+        resolution: Size,
         fakeCameraId: String = "0",
         configType: Class<out UseCaseConfig<*>?>,
     ): CameraUseCaseAdapter {
@@ -152,7 +177,7 @@
                 setSuggestedStreamSpec(
                     fakeCameraId,
                     configType,
-                    StreamSpec.builder(Size(640, 480)).build()
+                    StreamSpec.builder(resolution).build()
                 )
             },
             androidx.camera.camera2.pipe.integration.adapter.CameraUseCaseAdapter(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlowTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlowTest.kt
new file mode 100644
index 0000000..04b3c21
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/compat/workaround/StillCaptureFlowTest.kt
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.pipe.integration.compat.workaround
+
+import android.hardware.camera2.CameraDevice.TEMPLATE_PREVIEW
+import android.hardware.camera2.CameraDevice.TEMPLATE_STILL_CAPTURE
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_AUTO_FLASH
+import android.hardware.camera2.CaptureRequest
+import android.os.Build
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.RequestTemplate
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.ParameterizedRobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
+
+@RunWith(ParameterizedRobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = 21)
+class StillCaptureFlowTest(
+    private val brand: String,
+    private val model: String,
+    private val aeMode: Int,
+    private val template: Int,
+    private val stopRepeatingExpected: Boolean,
+) {
+    @Test
+    fun shouldStopRepeating() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", brand)
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", model)
+
+        assertThat(listOf(Request(
+                streams = emptyList(),
+                parameters = mapOf(CaptureRequest.CONTROL_AE_MODE to aeMode),
+                template = RequestTemplate(template),
+        )).shouldStopRepeatingBeforeCapture()).isEqualTo(stopRepeatingExpected)
+    }
+
+    companion object {
+        @JvmStatic
+        @ParameterizedRobolectricTestRunner.Parameters(
+            name = "brand={0}, model={1}, aeMode={2}, template={3}"
+        )
+        fun data() = mutableListOf<Array<Any?>>().apply {
+            add(arrayOf(
+                "Samsung", "SM-A716B", CONTROL_AE_MODE_ON, TEMPLATE_STILL_CAPTURE, false
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716B", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_STILL_CAPTURE, true
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716B", CONTROL_AE_MODE_ON_ALWAYS_FLASH, TEMPLATE_STILL_CAPTURE, true
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716B", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_PREVIEW, false
+            ))
+
+            add(arrayOf(
+                "Samsung", "SM-A716U", CONTROL_AE_MODE_ON, TEMPLATE_STILL_CAPTURE, false
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716U", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_STILL_CAPTURE, true
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716U", CONTROL_AE_MODE_ON_ALWAYS_FLASH, TEMPLATE_STILL_CAPTURE, true
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A716U", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_PREVIEW, false
+            ))
+
+            add(arrayOf(
+                "Google", "Pixel 2", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_STILL_CAPTURE, false
+            ))
+            add(arrayOf(
+                "Moto", "G3", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_STILL_CAPTURE, false
+            ))
+            add(arrayOf(
+                "Samsung", "SM-A722", CONTROL_AE_MODE_ON_AUTO_FLASH, TEMPLATE_STILL_CAPTURE, false
+            ))
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
index f6a563e..c04fcb5 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/CapturePipelineTest.kt
@@ -18,8 +18,10 @@
 
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_ON_ALWAYS_FLASH
 import android.hardware.camera2.CaptureFailure
 import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.params.MeteringRectangle
 import android.os.Build
@@ -31,9 +33,11 @@
 import androidx.camera.camera2.pipe.Request
 import androidx.camera.camera2.pipe.RequestTemplate
 import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
 import androidx.camera.camera2.pipe.integration.adapter.asListenableFuture
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.config.UseCaseGraphConfig
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraph
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraGraphSession
@@ -69,6 +73,7 @@
 import org.mockito.Mockito.mock
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
 
 @RunWith(RobolectricCameraPipeTestRunner::class)
 @DoNotInstrument
@@ -166,6 +171,11 @@
         extras = emptyMap(),
         template = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE),
     )
+    private val fakeCameraProperties = FakeCameraProperties(
+        FakeCameraMetadata(
+            mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
+        )
+    )
     private var runningRepeatingStream: ScheduledFuture<*>? = null
         set(value) {
             runningRepeatingStream?.cancel(false)
@@ -175,18 +185,18 @@
     private lateinit var torchControl: TorchControl
     private lateinit var capturePipeline: CapturePipeline
 
+    private lateinit var fakeUseCaseCameraState: UseCaseCameraState
+
     @Before
     fun setUp() {
         val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
-        val fakeCameraProperties = FakeCameraProperties(
-            FakeCameraMetadata(
-                mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
-            )
-        )
 
         torchControl = TorchControl(
             fakeCameraProperties,
-            State3AControl(fakeCameraProperties).apply {
+            State3AControl(
+                fakeCameraProperties,
+                NoOpAutoFlashAEModeDisabler,
+            ).apply {
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeUseCaseThreads,
@@ -200,15 +210,24 @@
             fakeRequestControl.torchUpdateEventList.clear()
         }
 
+        val fakeUseCaseGraphConfig = UseCaseGraphConfig(
+            graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession),
+            surfaceToStreamMap = emptyMap(),
+            cameraStateAdapter = CameraStateAdapter(),
+        )
+
+        fakeUseCaseCameraState = UseCaseCameraState(
+            fakeUseCaseGraphConfig,
+            fakeUseCaseThreads
+        )
+
         capturePipeline = CapturePipelineImpl(
             torchControl = torchControl,
             threads = fakeUseCaseThreads,
             requestListener = comboRequestListener,
-            useCaseGraphConfig = UseCaseGraphConfig(
-                graph = FakeCameraGraph(fakeCameraGraphSession = fakeCameraGraphSession),
-                surfaceToStreamMap = emptyMap(),
-                cameraStateAdapter = CameraStateAdapter(),
-            ),
+            cameraProperties = fakeCameraProperties,
+            useCaseGraphConfig = fakeUseCaseGraphConfig,
+            useCaseCameraState = fakeUseCaseCameraState
         )
     }
 
@@ -588,6 +607,88 @@
         )
     }
 
+    @Test
+    fun stillCaptureWithFlashStopRepeatingQuirk_shouldStopRepeatingTemporarily() = runBlocking {
+        // Arrange
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "SAMSUNG")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "SM-A716")
+
+        val submittedRequestList = mutableListOf<Request>()
+        fakeCameraGraphSession.requestHandler = { requests ->
+            submittedRequestList.addAll(requests)
+        }
+        fakeUseCaseCameraState.update(streams = setOf(StreamId(0)))
+
+        // Act.
+        capturePipeline.submitStillCaptures(
+            requests = listOf(Request(
+                streams = emptyList(),
+                parameters = mapOf(CONTROL_AE_MODE to CONTROL_AE_MODE_ON_ALWAYS_FLASH),
+                template = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE)
+            )),
+            captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
+            flashMode = ImageCapture.FLASH_MODE_ON,
+            flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
+        )
+
+        // Assert, stopRepeating -> submit -> startRepeating flow should be used.
+        assertThat(
+            fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isTrue()
+
+        assertThat(
+            fakeCameraGraphSession.submitSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isTrue()
+
+        // Completing the submitted capture request.
+        submittedRequestList.complete()
+
+        assertThat(
+            fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isTrue()
+    }
+
+    @Test
+    fun stillCaptureWithFlashStopRepeatingQuirkNotEnabled_shouldNotStopRepeating() = runBlocking {
+        // Arrange
+        val submittedRequestList = mutableListOf<Request>()
+        fakeCameraGraphSession.requestHandler = { requests ->
+            submittedRequestList.addAll(requests)
+        }
+        fakeUseCaseCameraState.update(streams = setOf(StreamId(0)))
+
+        // Act.
+        capturePipeline.submitStillCaptures(
+            requests = listOf(Request(
+                streams = emptyList(),
+                parameters = mapOf(CONTROL_AE_MODE to CONTROL_AE_MODE_ON_ALWAYS_FLASH),
+                template = RequestTemplate(CameraDevice.TEMPLATE_STILL_CAPTURE)
+            )),
+            captureMode = ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY,
+            flashMode = ImageCapture.FLASH_MODE_ON,
+            flashType = ImageCapture.FLASH_TYPE_ONE_SHOT_FLASH,
+        )
+
+        // Assert, repeating should not be stopped when quirk not enabled.
+        assertThat(
+            fakeCameraGraphSession.stopRepeatingSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isFalse()
+
+        assertThat(
+            fakeCameraGraphSession.submitSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isTrue()
+
+        // Resetting repeatingRequestSemaphore because startRepeating can be called before
+        fakeCameraGraphSession.repeatingRequestSemaphore = Semaphore(0)
+
+        // Completing the submitted capture request.
+        submittedRequestList.complete()
+
+        assertThat(
+            fakeCameraGraphSession.repeatingRequestSemaphore.tryAcquire(5, TimeUnit.SECONDS)
+        ).isFalse()
+    }
+
     // TODO(wenhungteng@): Porting overrideAeModeForStillCapture_quirkAbsent_notOverride,
     //  overrideAeModeForStillCapture_aePrecaptureStarted_override,
     //  overrideAeModeForStillCapture_aePrecaptureFinish_notOverride,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
index e3559f1..1666ff2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/FlashControlTest.kt
@@ -20,6 +20,7 @@
 import android.hardware.camera2.CaptureRequest
 import android.os.Build
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
@@ -70,9 +71,10 @@
     )
     private val fakeRequestControl = FakeUseCaseCameraRequestControl()
     private val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
-    private val state3AControl = State3AControl(FakeCameraProperties(metadata)).apply {
-        useCaseCamera = fakeUseCaseCamera
-    }
+    private val state3AControl =
+        State3AControl(FakeCameraProperties(metadata), NoOpAutoFlashAEModeDisabler).apply {
+            useCaseCamera = fakeUseCaseCamera
+        }
     private lateinit var flashControl: FlashControl
 
     @Before
@@ -90,7 +92,7 @@
         val fakeCameraProperties = FakeCameraProperties()
 
         val flashControl = FlashControl(
-            State3AControl(fakeCameraProperties).apply {
+            State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeUseCaseThreads,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
index 8aeaf43..aba1a20 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/TorchControlTest.kt
@@ -20,6 +20,7 @@
 import android.os.Build
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
 import androidx.camera.camera2.pipe.integration.testing.FakeCameraProperties
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCamera
 import androidx.camera.camera2.pipe.integration.testing.FakeUseCaseCameraRequestControl
@@ -88,7 +89,10 @@
         val fakeCameraProperties = FakeCameraProperties(metadata)
         torchControl = TorchControl(
             fakeCameraProperties,
-            State3AControl(fakeCameraProperties).apply {
+            State3AControl(
+                fakeCameraProperties,
+                NoOpAutoFlashAEModeDisabler,
+            ).apply {
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeUseCaseThreads,
@@ -105,7 +109,7 @@
             // Without a flash unit, this Job will complete immediately with a IllegalStateException
             TorchControl(
                 fakeCameraProperties,
-                State3AControl(fakeCameraProperties).apply {
+                State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
                     useCaseCamera = fakeUseCaseCamera
                 },
                 fakeUseCaseThreads,
@@ -122,7 +126,8 @@
 
         val torchState = TorchControl(
             fakeCameraProperties,
-            State3AControl(fakeCameraProperties).apply {
+            State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
+
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeUseCaseThreads,
@@ -141,7 +146,7 @@
 
             TorchControl(
                 fakeCameraProperties,
-                State3AControl(fakeCameraProperties).apply {
+                State3AControl(fakeCameraProperties, NoOpAutoFlashAEModeDisabler).apply {
                     useCaseCamera = fakeUseCaseCamera
                 },
                 fakeUseCaseThreads,
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
index a33fc33..008fea7 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseManagerTest.kt
@@ -282,7 +282,7 @@
     private fun createImageCapture(): ImageCapture =
         ImageCapture.Builder()
             .setCaptureOptionUnpacker { _, _ -> }
-            .setSessionOptionUnpacker() { _, _ -> }
+            .setSessionOptionUnpacker() { _, _, _ -> }
             .build().also {
                 it.simulateActivation()
                 useCaseList.add(it)
@@ -291,7 +291,7 @@
     private fun createPreview(): Preview =
         Preview.Builder()
             .setCaptureOptionUnpacker { _, _ -> }
-            .setSessionOptionUnpacker() { _, _ -> }
+            .setSessionOptionUnpacker() { _, _, _ -> }
             .build().apply {
                 setSurfaceProvider(
                     CameraXExecutors.mainThreadExecutor(),
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
index 47ef42c..a69eb5a2 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeCameraGraphSession.kt
@@ -32,7 +32,7 @@
 open class FakeCameraGraphSession : CameraGraph.Session {
 
     val repeatingRequests = mutableListOf<Request>()
-    val repeatingRequestSemaphore = Semaphore(0)
+    var repeatingRequestSemaphore = Semaphore(0)
     val stopRepeatingSemaphore = Semaphore(0)
 
     override fun abort() {
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 6f362bf..b687bf0 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
@@ -26,8 +26,11 @@
 import androidx.camera.camera2.pipe.integration.adapter.CameraInfoAdapter
 import androidx.camera.camera2.pipe.integration.adapter.CameraStateAdapter
 import androidx.camera.camera2.pipe.integration.adapter.EncoderProfilesProviderAdapter
+import androidx.camera.camera2.pipe.integration.compat.StreamConfigurationMapCompat
 import androidx.camera.camera2.pipe.integration.compat.quirk.CameraQuirks
 import androidx.camera.camera2.pipe.integration.compat.workaround.MeteringRegionCorrection
+import androidx.camera.camera2.pipe.integration.compat.workaround.NoOpAutoFlashAEModeDisabler
+import androidx.camera.camera2.pipe.integration.compat.workaround.OutputSizesCorrector
 import androidx.camera.camera2.pipe.integration.config.CameraConfig
 import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
@@ -92,10 +95,17 @@
         zoomControl: ZoomControl = this.zoomControl,
     ): CameraInfoAdapter {
         val fakeUseCaseCamera = FakeUseCaseCamera()
-        val state3AControl = State3AControl(cameraProperties).apply {
+        val state3AControl = State3AControl(cameraProperties, NoOpAutoFlashAEModeDisabler).apply {
             useCaseCamera = fakeUseCaseCamera
         }
-        val fakeCameraQuirks = CameraQuirks(FakeCameraMetadata())
+        val fakeStreamConfigurationMap = StreamConfigurationMapCompat(
+            streamConfigurationMap,
+            OutputSizesCorrector(cameraProperties.metadata)
+        )
+        val fakeCameraQuirks = CameraQuirks(
+            cameraProperties.metadata,
+            fakeStreamConfigurationMap,
+        )
         return CameraInfoAdapter(
             cameraProperties,
             CameraConfig(cameraId),
@@ -109,7 +119,7 @@
             FocusMeteringControl(
                 cameraProperties,
                 MeteringRegionCorrection.Bindings.provideMeteringRegionCorrection(
-                    CameraQuirks(cameraProperties.metadata)
+                    fakeCameraQuirks
                 ),
                 state3AControl,
                 useCaseThreads,
@@ -118,7 +128,8 @@
                 useCaseCamera = fakeUseCaseCamera
             },
             fakeCameraQuirks,
-            EncoderProfilesProviderAdapter(cameraId.value)
+            EncoderProfilesProviderAdapter(cameraId.value),
+            fakeStreamConfigurationMap,
         )
     }
 }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
index 1b62d59..215e70a 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/testing/FakeUseCaseCamera.kt
@@ -98,9 +98,9 @@
     var cancelFocusMeteringResult = CompletableDeferred(Result3A(status = Result3A.Status.OK))
 
     override suspend fun startFocusAndMeteringAsync(
-        aeRegions: List<MeteringRectangle>,
-        afRegions: List<MeteringRectangle>,
-        awbRegions: List<MeteringRectangle>,
+        aeRegions: List<MeteringRectangle>?,
+        afRegions: List<MeteringRectangle>?,
+        awbRegions: List<MeteringRectangle>?,
         afTriggerStartAeMode: AeMode?
     ): Deferred<Result3A> {
         focusMeteringCalls.add(
@@ -124,9 +124,9 @@
     }
 
     data class FocusMeteringParams(
-        val aeRegions: List<MeteringRectangle> = emptyList(),
-        val afRegions: List<MeteringRectangle> = emptyList(),
-        val awbRegions: List<MeteringRectangle> = emptyList(),
+        val aeRegions: List<MeteringRectangle>? = null,
+        val afRegions: List<MeteringRectangle>? = null,
+        val awbRegions: List<MeteringRectangle>? = null,
         val afTriggerStartAeMode: AeMode? = null
     )
 
diff --git a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphState3A.kt b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphState3A.kt
index c7a4604..f43640a 100644
--- a/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphState3A.kt
+++ b/camera/camera-camera2-pipe/src/main/java/androidx/camera/camera2/pipe/graph/GraphState3A.kt
@@ -85,9 +85,9 @@
             afMode?.let { this.afMode = it }
             awbMode?.let { this.awbMode = it }
             flashMode?.let { this.flashMode = it }
-            aeRegions?.let { this.aeRegions = it }
-            afRegions?.let { this.afRegions = it }
-            awbRegions?.let { this.awbRegions = it }
+            aeRegions?.let { this.aeRegions = it.ifEmpty { null } }
+            afRegions?.let { this.afRegions = it.ifEmpty { null } }
+            awbRegions?.let { this.awbRegions = it.ifEmpty { null } }
             aeLock?.let { this.aeLock = it }
             awbLock?.let { this.awbLock = it }
         }
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
index a7a5edc..2d941f0 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
@@ -1130,7 +1130,8 @@
         @NonNull
         protected StreamSpec onSuggestedStreamSpecUpdated(
                 @NonNull StreamSpec suggestedStreamSpec) {
-            SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfig);
+            SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfig,
+                    suggestedStreamSpec.getResolution());
 
             builder.setTemplateType(mTemplate);
             builder.addRepeatingCameraCaptureCallback(mRepeatingCaptureCallback);
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
index ba14a0e..65da641 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
@@ -452,7 +452,8 @@
         }
 
         private void createPipeline(StreamSpec streamSpec) {
-            SessionConfig.Builder builder = SessionConfig.Builder.createFrom(getCurrentConfig());
+            SessionConfig.Builder builder = SessionConfig.Builder.createFrom(getCurrentConfig(),
+                    streamSpec.getResolution());
 
             builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
             if (mDeferrableSurface != null) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index 1a64c2e..e893e30 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -121,7 +121,7 @@
      * Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
      * called, camera control related API (torch/exposure/zoom) will return default values.
      */
-    Camera2CameraInfoImpl(@NonNull String cameraId,
+    public Camera2CameraInfoImpl(@NonNull String cameraId,
             @NonNull CameraManagerCompat cameraManager) throws CameraAccessExceptionCompat {
         mCameraId = Preconditions.checkNotNull(cameraId);
         mCameraManager = cameraManager;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
index 12349de..17ca148 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpacker.java
@@ -16,16 +16,20 @@
 
 package androidx.camera.camera2.internal;
 
+import android.util.Size;
+
 import androidx.annotation.NonNull;
 import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.camera.camera2.impl.Camera2ImplConfig;
 import androidx.camera.camera2.impl.CameraEventCallbacks;
 import androidx.camera.camera2.internal.compat.params.OutputConfigurationCompat;
+import androidx.camera.camera2.internal.compat.workaround.PreviewPixelHDRnet;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.OptionsBundle;
+import androidx.camera.core.impl.PreviewConfig;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.UseCaseConfig;
 
@@ -40,7 +44,9 @@
 
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Override
-    public void unpack(@NonNull UseCaseConfig<?> config,
+    public void unpack(
+            @NonNull Size resolution,
+            @NonNull UseCaseConfig<?> config,
             @NonNull final SessionConfig.Builder builder) {
         SessionConfig defaultSessionConfig =
                 config.getDefaultSessionConfig(/*valueIfMissing=*/ null);
@@ -61,6 +67,11 @@
         // Set the any additional implementation options
         builder.setImplementationOptions(implOptions);
 
+        // Apply quirks
+        if (config instanceof PreviewConfig) {
+            PreviewPixelHDRnet.setHDRnet(resolution, builder);
+        }
+
         // Get Camera2 extended options
         final Camera2ImplConfig camera2Config = new Camera2ImplConfig(config);
 
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2UseCaseConfigFactory.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2UseCaseConfigFactory.java
index 1689d53..fe5cae3 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2UseCaseConfigFactory.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2UseCaseConfigFactory.java
@@ -29,7 +29,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
-import androidx.camera.camera2.internal.compat.workaround.PreviewPixelHDRnet;
 import androidx.camera.core.ImageCapture;
 import androidx.camera.core.ImageCapture.CaptureMode;
 import androidx.camera.core.impl.CaptureConfig;
@@ -79,11 +78,6 @@
                 break;
         }
 
-        if (captureType == CaptureType.PREVIEW) {
-            // Set the WYSIWYG preview for CAPTURE_TYPE_PREVIEW
-            PreviewPixelHDRnet.setHDRnet(sessionBuilder);
-        }
-
         mutableConfig.insertOption(OPTION_DEFAULT_SESSION_CONFIG, sessionBuilder.build());
 
         mutableConfig.insertOption(OPTION_SESSION_CONFIG_UNPACKER,
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/MeteringRepeatingSession.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/MeteringRepeatingSession.java
index b323a73..86f8333 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/MeteringRepeatingSession.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/MeteringRepeatingSession.java
@@ -80,7 +80,8 @@
                 meteringSurfaceSize.getHeight());
         Surface surface = new Surface(surfaceTexture);
 
-        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfigWithDefaults);
+        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mConfigWithDefaults,
+                meteringSurfaceSize);
         builder.setTemplateType(CameraDevice.TEMPLATE_PREVIEW);
 
         mDeferrableSurface = new ImmediateSurface(surface);
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
index d2e5dd8..5d8196d 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/StreamUseCaseUtil.java
@@ -36,6 +36,7 @@
 import androidx.camera.core.UseCase;
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.streamsharing.StreamSharing;
 
 import java.util.Collection;
 import java.util.HashMap;
@@ -149,6 +150,8 @@
                     Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_STILL_CAPTURE));
             sUseCaseToStreamUseCaseMapping.put(MediaCodec.class,
                     Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
+            sUseCaseToStreamUseCaseMapping.put(StreamSharing.class,
+                    Long.valueOf(CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD));
         }
         return sUseCaseToStreamUseCaseMapping;
     }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index 6c25d96..93b1c56 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.internal;
 
 import static android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT;
+import static android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES;
 
 import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_1080P;
 import static androidx.camera.core.internal.utils.SizeUtil.RESOLUTION_480P;
@@ -204,6 +205,164 @@
     }
 
     /**
+     *
+     * @param range
+     * @return the length of the range
+     */
+    private static int getRangeLength(Range<Integer> range) {
+        return (range.getUpper() - range.getLower()) + 1;
+    }
+
+    /**
+     * @return the distance between the nearest limits of two non-intersecting ranges
+     */
+    private static int getRangeDistance(Range<Integer> firstRange, Range<Integer> secondRange) {
+        Preconditions.checkState(
+                !firstRange.contains(secondRange.getUpper())
+                        && !firstRange.contains(secondRange.getLower()),
+                "Ranges must not intersect");
+        if (firstRange.getLower() > secondRange.getUpper()) {
+            return firstRange.getLower() - secondRange.getUpper();
+        } else {
+            return secondRange.getLower() - firstRange.getUpper();
+        }
+    }
+
+    /**
+     * @param targetFps the target frame rate range used while comparing to device-supported ranges
+     * @param storedRange the device-supported range that is currently saved and intersects with
+     *                    targetFps
+     * @param newRange a new potential device-supported range that intersects with targetFps
+     * @return the device-supported range that better matches the target fps
+     */
+    private static Range<Integer> compareIntersectingRanges(Range<Integer> targetFps,
+            Range<Integer> storedRange, Range<Integer> newRange) {
+        // TODO(b/272075984): some ranges may may have a larger intersection but may also have an
+        //  excessively large portion that is non-intersecting. Will want to do further
+        //  investigation to find a more optimized way to decide when a potential range has too
+        //  much non-intersecting value and discard it
+
+        double storedIntersectionsize = getRangeLength(storedRange.intersect(targetFps));
+        double newIntersectionSize = getRangeLength(newRange.intersect(targetFps));
+
+        double newRangeRatio = newIntersectionSize / getRangeLength(newRange);
+        double storedRangeRatio = storedIntersectionsize / getRangeLength(storedRange);
+
+        if (newIntersectionSize > storedIntersectionsize) {
+            // if new, the new range must have at least 50% of its range intersecting, OR has a
+            // larger percentage of intersection than the previous stored range
+            if (newRangeRatio >= .5 || newRangeRatio >= storedRangeRatio) {
+                return newRange;
+            }
+        } else if (newIntersectionSize == storedIntersectionsize) {
+            // if intersecting ranges have same length... pick the one that has the higher
+            // intersection ratio
+            if (newRangeRatio > storedRangeRatio) {
+                return newRange;
+            } else if (newRangeRatio == storedRangeRatio
+                    && newRange.getLower() > storedRange.getLower()) {
+                // if equal intersection size AND ratios pick the higher range
+                return newRange;
+            }
+
+        } else if (storedRangeRatio < .5
+                && newRangeRatio > storedRangeRatio) {
+            // if the new one has a smaller range... only change if existing has an intersection
+            // ratio < 50% and the new one has an intersection ratio > than the existing one
+            return newRange;
+        }
+        return storedRange;
+    }
+
+    /**
+     * Finds a frame rate range supported by the device that is closest to the target framerate
+     *
+     * @param targetFrameRate the Target Frame Rate resolved from all current existing surfaces
+     *                        and incoming new use cases
+     * @return a frame rate range supported by the device that is closest to targetFrameRate
+     */
+
+    @NonNull
+    private Range<Integer> getClosestSupportedDeviceFrameRate(Range<Integer> targetFrameRate,
+            int maxFps) {
+        if (targetFrameRate == null) {
+            return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
+        }
+
+        // get all fps ranges supported by device
+        Range<Integer>[] availableFpsRanges =
+                mCharacteristics.get(CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES);
+
+        if (availableFpsRanges == null) {
+            return StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
+        }
+        // if  whole target framerate range > maxFps of configuration, the target for this
+        // calculation will be [max,max].
+
+        // if the range is partially larger than  maxFps, the target for this calculation will be
+        // [target.lower, max] for the sake of this calculation
+        targetFrameRate = new Range<>(
+                Math.min(targetFrameRate.getLower(), maxFps),
+                Math.min(targetFrameRate.getUpper(), maxFps)
+        );
+
+        Range<Integer> bestRange = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED;
+        int currentIntersectSize = 0;
+
+
+        for (Range<Integer> potentialRange : availableFpsRanges) {
+            // ignore ranges completely larger than configuration's maximum fps
+            if (maxFps >= potentialRange.getLower()) {
+                if (bestRange.equals(StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED)) {
+                    bestRange = potentialRange;
+                }
+                // take if range is a perfect match
+                if (potentialRange.equals(targetFrameRate)) {
+                    bestRange = potentialRange;
+                    break;
+                }
+
+                try {
+                    // bias towards a range that intersects on the upper end
+                    Range<Integer> newIntersection = potentialRange.intersect(targetFrameRate);
+                    int newIntersectSize = getRangeLength(newIntersection);
+                    // if this range intersects our target + no other range was already
+                    if (currentIntersectSize == 0) {
+                        bestRange = potentialRange;
+                        currentIntersectSize = newIntersectSize;
+                    } else if (newIntersectSize >= currentIntersectSize) {
+                        // if the currently stored range + new range both intersect, check to see
+                        // which one should be picked over the other
+                        bestRange = compareIntersectingRanges(targetFrameRate, bestRange,
+                                potentialRange);
+                        currentIntersectSize = getRangeLength(targetFrameRate.intersect(bestRange));
+                    }
+                } catch (IllegalArgumentException e) {
+                    // if no intersection is present, pick the range that is closer to our target
+                    if (currentIntersectSize == 0) {
+                        if (getRangeDistance(potentialRange, targetFrameRate)
+                                < getRangeDistance(bestRange, targetFrameRate)) {
+                            bestRange = potentialRange;
+                        } else if (getRangeDistance(potentialRange, targetFrameRate)
+                                == getRangeDistance(bestRange, targetFrameRate)) {
+                            if (potentialRange.getLower() > bestRange.getUpper()) {
+                                // if they both have the same distance, pick the higher range
+                                bestRange = potentialRange;
+                            } else if (getRangeLength(potentialRange) < getRangeLength(bestRange)) {
+                                // if one isn't higher than the other, pick the range with the
+                                // shorter length
+                                bestRange = potentialRange;
+                            }
+                        }
+                    }
+                }
+            }
+
+        }
+        return bestRange;
+    }
+
+    /**
      * @param newTargetFramerate    an incoming framerate range
      * @param storedTargetFramerate a stored framerate range to be modified
      * @return adjusted target frame rate
@@ -405,12 +564,22 @@
 
         // Map the saved supported SurfaceConfig combination
         if (savedSizes != null) {
+            Range<Integer> targetFramerateForDevice = null;
+            if (targetFramerateForConfig != null) {
+                targetFramerateForDevice =
+                        getClosestSupportedDeviceFrameRate(targetFramerateForConfig,
+                                savedConfigMaxFps);
+            }
             suggestedStreamSpecMap = new HashMap<>();
             for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
                 suggestedStreamSpecMap.put(
                         useCaseConfig,
-                        StreamSpec.builder(savedSizes.get(useCasesPriorityOrder.indexOf(
-                                newUseCaseConfigs.indexOf(useCaseConfig)))).build());
+                        targetFramerateForDevice != null
+                                ? StreamSpec.builder(savedSizes.get(useCasesPriorityOrder.indexOf(
+                                        newUseCaseConfigs.indexOf(useCaseConfig))))
+                                .setExpectedFrameRateRange(targetFramerateForDevice).build()
+                                : StreamSpec.builder(savedSizes.get(useCasesPriorityOrder.indexOf(
+                                        newUseCaseConfigs.indexOf(useCaseConfig)))).build());
             }
         } else {
             throw new IllegalArgumentException(
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatApi23Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatApi23Impl.java
index 23716af..d25a899 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatApi23Impl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/StreamConfigurationMapCompatApi23Impl.java
@@ -23,7 +23,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 
-@RequiresApi(21)
+@RequiresApi(23)
 class StreamConfigurationMapCompatApi23Impl extends StreamConfigurationMapCompatBaseImpl {
 
     StreamConfigurationMapCompatApi23Impl(@NonNull StreamConfigurationMap map) {
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompat.java
new file mode 100644
index 0000000..e7d3d64
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompat.java
@@ -0,0 +1,166 @@
+/*
+ * 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.compat.params;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.params.DynamicRangeProfiles;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.DynamicRange;
+import androidx.core.util.Preconditions;
+
+import java.util.Set;
+
+/**
+ * Helper for accessing features in DynamicRangeProfiles in a backwards compatible fashion.
+ */
+@RequiresApi(21)
+public final class DynamicRangesCompat {
+
+    private final DynamicRangeProfilesCompatImpl mImpl;
+
+    DynamicRangesCompat(@NonNull DynamicRangeProfilesCompatImpl impl) {
+        mImpl = impl;
+    }
+
+    /**
+     * Returns a set of supported {@link DynamicRange} that can be referenced in a single
+     * capture request.
+     *
+     * <p>For example if a particular 10-bit output capable device returns (STANDARD,
+     * HLG10, HDR10) as result from calling {@link #getSupportedDynamicRanges()} and
+     * getProfileCaptureRequestConstraints(long) returns (STANDARD, HLG10) when given an argument
+     * of STANDARD. This means that the corresponding camera device will only accept and process
+     * capture requests that reference outputs configured using HDR10 dynamic range or
+     * alternatively some combination of STANDARD and HLG10. However trying to queue capture
+     * requests to outputs that reference both HDR10 and STANDARD/HLG10 will result in
+     * IllegalArgumentException.
+     *
+     * <p>The list will be empty in case there are no constraints for the given dynamic range.
+     *
+     * @param dynamicRange The dynamic range that will be checked for constraints
+     * @return non-modifiable set of dynamic ranges
+     * @throws IllegalArgumentException If the dynamic range argument is not within the set
+     * returned by {@link #getSupportedDynamicRanges()}.
+     */
+    @NonNull
+    public Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
+            @NonNull DynamicRange dynamicRange) {
+        return mImpl.getDynamicRangeCaptureRequestConstraints(dynamicRange);
+    }
+
+    /**
+     * Returns a set of supported dynamic ranges.
+     *
+     * @return a non-modifiable set of dynamic ranges.
+     */
+    @NonNull
+    public Set<DynamicRange> getSupportedDynamicRanges() {
+        return mImpl.getSupportedDynamicRanges();
+    }
+
+    /**
+     * Checks whether a given dynamic range is suitable for latency sensitive use cases.
+     *
+     * <p>Due to internal lookahead logic, camera outputs configured with some dynamic range
+     * profiles may experience additional latency greater than 3 buffers. Using camera outputs
+     * with such dynamic ranges for latency sensitive use cases such as camera preview is not
+     * recommended. Dynamic ranges that have such extra streaming delay are typically utilized for
+     * scenarios such as offscreen video recording.
+     *
+     * @param dynamicRange The dynamic range to check for extra latency
+     * @return {@code true} if the given profile is not suitable for latency sensitive use cases,
+     * {@code false} otherwise.
+     * @throws IllegalArgumentException If the dynamic range argument is not within the set
+     * returned by {@link #getSupportedDynamicRanges()}.
+     */
+    public boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange) {
+        return mImpl.isExtraLatencyPresent(dynamicRange);
+    }
+
+    /**
+     * Returns a {@link DynamicRangesCompat} using the capabilities derived from the provided
+     * characteristics.
+     *
+     * @param characteristics the characteristics used to derive dynamic range information.
+     * @return a {@link DynamicRangesCompat} object.
+     */
+    @NonNull
+    public static DynamicRangesCompat fromCameraCharacteristics(
+            @NonNull CameraCharacteristicsCompat characteristics) {
+        DynamicRangesCompat rangesCompat = null;
+        if (Build.VERSION.SDK_INT >= 33) {
+            rangesCompat = toDynamicRangesCompat(characteristics.get(
+                    CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES));
+        }
+
+        return (rangesCompat == null) ? DynamicRangesCompatBaseImpl.COMPAT_INSTANCE : rangesCompat;
+    }
+
+    /**
+     * Creates an instance from a framework android.hardware.camera2.params.DynamicRangeProfiles
+     * object.
+     *
+     * @param dynamicRangeProfiles a {@link android.hardware.camera2.params.DynamicRangeProfiles).
+     * @return an equivalent {@link DynamicRangesCompat} object.
+     */
+    @Nullable
+    @RequiresApi(33)
+    public static DynamicRangesCompat toDynamicRangesCompat(
+            @Nullable DynamicRangeProfiles dynamicRangeProfiles) {
+        if (dynamicRangeProfiles == null) {
+            return null;
+        }
+
+        Preconditions.checkState(Build.VERSION.SDK_INT >= 33, "DynamicRangeProfiles can only be "
+                + "converted to DynamicRangesCompat on API 33 or higher.");
+
+        return new DynamicRangesCompat(new DynamicRangesCompatApi33Impl(dynamicRangeProfiles));
+    }
+
+    /**
+     * Returns the underlying framework
+     * {@link android.hardware.camera2.params.DynamicRangeProfiles).
+     *
+     * @return the underlying {@link android.hardware.camera2.params.DynamicRangeProfiles).
+     */
+    @NonNull
+    @RequiresApi(33)
+    public DynamicRangeProfiles toDynamicRangeProfiles() {
+        Preconditions.checkState(Build.VERSION.SDK_INT >= 33, "DynamicRangesCompat can only be "
+                + "converted to DynamicRangeProfiles on API 33 or higher.");
+        return Preconditions.checkNotNull(mImpl.unwrap());
+    }
+
+    interface DynamicRangeProfilesCompatImpl {
+        @NonNull
+        Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
+                @NonNull DynamicRange dynamicRange);
+
+        @NonNull
+        Set<DynamicRange> getSupportedDynamicRanges();
+
+        boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange);
+
+        @Nullable
+        DynamicRangeProfiles unwrap();
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatApi33Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatApi33Impl.java
new file mode 100644
index 0000000..1c9a492
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatApi33Impl.java
@@ -0,0 +1,171 @@
+/*
+ * 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.compat.params;
+
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM_PO;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_REF_PO;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM_PO;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF;
+import static android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_REF_PO;
+import static android.hardware.camera2.params.DynamicRangeProfiles.HDR10;
+import static android.hardware.camera2.params.DynamicRangeProfiles.HDR10_PLUS;
+import static android.hardware.camera2.params.DynamicRangeProfiles.HLG10;
+import static android.hardware.camera2.params.DynamicRangeProfiles.STANDARD;
+
+import android.hardware.camera2.params.DynamicRangeProfiles;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.DynamicRange;
+import androidx.core.util.Preconditions;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+
+@RequiresApi(33)
+class DynamicRangesCompatApi33Impl implements DynamicRangesCompat.DynamicRangeProfilesCompatImpl {
+    private static final Map<Long, DynamicRange> PROFILE_TO_DR_MAP = new HashMap<>();
+    private static final Map<DynamicRange, List<Long>> DR_TO_PROFILE_MAP = new HashMap<>();
+
+    static {
+        // SDR
+        PROFILE_TO_DR_MAP.put(STANDARD, DynamicRange.SDR);
+        DR_TO_PROFILE_MAP.put(DynamicRange.SDR, Collections.singletonList(STANDARD));
+
+        // HLG
+        PROFILE_TO_DR_MAP.put(HLG10,
+                new DynamicRange(DynamicRange.FORMAT_HLG, DynamicRange.BIT_DEPTH_10_BIT));
+        DR_TO_PROFILE_MAP.put(PROFILE_TO_DR_MAP.get(HLG10), Collections.singletonList(HLG10));
+
+        // HDR10
+        DynamicRange hdr10 = new DynamicRange(DynamicRange.FORMAT_HDR10,
+                DynamicRange.BIT_DEPTH_10_BIT);
+        PROFILE_TO_DR_MAP.put(HDR10, hdr10);
+        DR_TO_PROFILE_MAP.put(hdr10, Collections.singletonList(HDR10));
+
+        // HDR10+
+        DynamicRange hdr10Plus = new DynamicRange(DynamicRange.FORMAT_HDR10_PLUS,
+                DynamicRange.BIT_DEPTH_10_BIT);
+        PROFILE_TO_DR_MAP.put(HDR10_PLUS, hdr10Plus);
+        DR_TO_PROFILE_MAP.put(hdr10Plus, Collections.singletonList(HDR10_PLUS));
+
+        // Dolby Vision 10-bit
+        DynamicRange dolbyVision10Bit = new DynamicRange(DynamicRange.FORMAT_DOLBY_VISION,
+                DynamicRange.BIT_DEPTH_10_BIT);
+        // A list of the Camera2 10-bit dolby vision profiles ordered by priority. Any API that
+        // takes a DynamicRange with dolby vision format will attempt to convert to these
+        // profiles in order, using the first one that is supported. We will need to add a
+        // mechanism for choosing between these
+        List<Long> dolbyVision10BitProfilesOrdered = Arrays.asList(DOLBY_VISION_10B_HDR_OEM,
+                DOLBY_VISION_10B_HDR_OEM_PO, DOLBY_VISION_10B_HDR_REF, DOLBY_VISION_10B_HDR_REF_PO);
+        for (Long profile : dolbyVision10BitProfilesOrdered) {
+            PROFILE_TO_DR_MAP.put(profile, dolbyVision10Bit);
+        }
+        DR_TO_PROFILE_MAP.put(dolbyVision10Bit, dolbyVision10BitProfilesOrdered);
+
+        // Dolby vision 8-bit
+        DynamicRange dolbyVision8Bit = new DynamicRange(DynamicRange.FORMAT_DOLBY_VISION,
+                DynamicRange.BIT_DEPTH_8_BIT);
+        List<Long> dolbyVision8BitProfilesOrdered = Arrays.asList(DOLBY_VISION_8B_HDR_OEM,
+                DOLBY_VISION_8B_HDR_OEM_PO, DOLBY_VISION_8B_HDR_REF, DOLBY_VISION_8B_HDR_REF_PO);
+        for (Long profile : dolbyVision8BitProfilesOrdered) {
+            PROFILE_TO_DR_MAP.put(profile, dolbyVision8Bit);
+        }
+        DR_TO_PROFILE_MAP.put(dolbyVision8Bit, dolbyVision8BitProfilesOrdered);
+    }
+
+    private final DynamicRangeProfiles mDynamicRangeProfiles;
+
+    DynamicRangesCompatApi33Impl(@NonNull Object dynamicRangeProfiles) {
+        mDynamicRangeProfiles = (DynamicRangeProfiles) dynamicRangeProfiles;
+    }
+
+    @NonNull
+    @Override
+    public Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
+            @NonNull DynamicRange dynamicRange) {
+        Long dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange);
+        Preconditions.checkArgument(dynamicRangeProfile != null,
+                "DynamicRange is not supported: " + dynamicRange);
+        return profileSetToDynamicRangeSet(
+                mDynamicRangeProfiles.getProfileCaptureRequestConstraints(dynamicRangeProfile));
+    }
+
+    @NonNull
+    @Override
+    public Set<DynamicRange> getSupportedDynamicRanges() {
+        return profileSetToDynamicRangeSet(mDynamicRangeProfiles.getSupportedProfiles());
+    }
+
+    @Override
+    public boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange) {
+        Long dynamicRangeProfile = dynamicRangeToFirstSupportedProfile(dynamicRange);
+        Preconditions.checkArgument(dynamicRangeProfile != null,
+                "DynamicRange is not supported: " + dynamicRange);
+        return mDynamicRangeProfiles.isExtraLatencyPresent(dynamicRangeProfile);
+    }
+
+    @Nullable
+    @Override
+    public DynamicRangeProfiles unwrap() {
+        return mDynamicRangeProfiles;
+    }
+
+    @NonNull
+    private static DynamicRange profileToDynamicRange(long profile) {
+        return Preconditions.checkNotNull(PROFILE_TO_DR_MAP.get(profile),
+                "Dynamic range profile cannot be converted to a DynamicRange object: " + profile);
+    }
+
+    @Nullable
+    private Long dynamicRangeToFirstSupportedProfile(@NonNull DynamicRange dynamicRange) {
+        List<Long> orderedProfiles =
+                Preconditions.checkNotNull(DR_TO_PROFILE_MAP.get(dynamicRange),
+                        "DynamicRange object does not have an associated camera2 profile: "
+                                + dynamicRange);
+        Set<Long> supportedList = mDynamicRangeProfiles.getSupportedProfiles();
+        for (Long profile : orderedProfiles) {
+            if (supportedList.contains(profile)) {
+                return profile;
+            }
+        }
+
+        // No profile supported
+        return null;
+    }
+
+    @NonNull
+    private static Set<DynamicRange> profileSetToDynamicRangeSet(@NonNull Set<Long> profileSet) {
+        if (profileSet.isEmpty()) {
+            return Collections.emptySet();
+        }
+        Set<DynamicRange> dynamicRangeSet = new HashSet<>(profileSet.size());
+        for (long profile : profileSet) {
+            dynamicRangeSet.add(profileToDynamicRange(profile));
+        }
+        return Collections.unmodifiableSet(dynamicRangeSet);
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatBaseImpl.java
new file mode 100644
index 0000000..312978f
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatBaseImpl.java
@@ -0,0 +1,64 @@
+/*
+ * 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.compat.params;
+
+import android.hardware.camera2.params.DynamicRangeProfiles;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.DynamicRange;
+import androidx.core.util.Preconditions;
+
+import java.util.Collections;
+import java.util.Set;
+
+@RequiresApi(21)
+class DynamicRangesCompatBaseImpl implements DynamicRangesCompat.DynamicRangeProfilesCompatImpl {
+
+    static final DynamicRangesCompat COMPAT_INSTANCE =
+            new DynamicRangesCompat(new DynamicRangesCompatBaseImpl());
+
+    private static final Set<DynamicRange> SDR_ONLY = Collections.singleton(DynamicRange.SDR);
+    @NonNull
+    @Override
+    public Set<DynamicRange> getDynamicRangeCaptureRequestConstraints(
+            @NonNull DynamicRange dynamicRange) {
+        Preconditions.checkArgument(DynamicRange.SDR.equals(dynamicRange),
+                "DynamicRange is not supported: " + dynamicRange);
+        return SDR_ONLY;
+    }
+
+    @NonNull
+    @Override
+    public Set<DynamicRange> getSupportedDynamicRanges() {
+        return SDR_ONLY;
+    }
+
+    @Override
+    public boolean isExtraLatencyPresent(@NonNull DynamicRange dynamicRange) {
+        Preconditions.checkArgument(DynamicRange.SDR.equals(dynamicRange),
+                "DynamicRange is not supported: " + dynamicRange);
+        return false;
+    }
+
+    @Nullable
+    @Override
+    public DynamicRangeProfiles unwrap() {
+        return null;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewPixelHDRnetQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewPixelHDRnetQuirk.java
index 3c5827a..1cf284e 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewPixelHDRnetQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/PreviewPixelHDRnetQuirk.java
@@ -35,6 +35,8 @@
  *                  image quality than the viewfinder output. To align the viewfinder quality
  *                  with the final photo, we need to set TONEMAP_MODE to HIGH_QUALITY (the
  *                  default is FAST) on the viewfinder stream to enable the WYSIWYG preview.
+ *                  This quirk will not be applied when preview resolution is 16:9, more details in
+ *                  b/266459202.
  *     Device(s): Pixel 4a, Pixel 4a (5G), Pixel 5, Pixel 5a (5G)
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
index 0465d6d..7615d3d 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/workaround/PreviewPixelHDRnet.java
@@ -17,6 +17,8 @@
 package androidx.camera.camera2.internal.compat.workaround;
 
 import android.hardware.camera2.CaptureRequest;
+import android.util.Rational;
+import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.OptIn;
@@ -35,6 +37,8 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class PreviewPixelHDRnet {
 
+    public static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
+
     private PreviewPixelHDRnet() {
     }
 
@@ -42,15 +46,27 @@
      * Turns on WYSIWYG viewfinder on Pixel devices
      */
     @OptIn(markerClass = ExperimentalCamera2Interop.class)
-    public static void setHDRnet(@NonNull SessionConfig.Builder sessionBuilder) {
+    public static void setHDRnet(
+            @NonNull Size resolution,
+            @NonNull SessionConfig.Builder sessionBuilder) {
         final PreviewPixelHDRnetQuirk quirk = DeviceQuirks.get(PreviewPixelHDRnetQuirk.class);
         if (quirk == null) {
             return;
         }
 
+        if (isAspectRatioMatch(resolution, ASPECT_RATIO_16_9)) {
+            return;
+        }
+
         Camera2ImplConfig.Builder camera2ConfigBuilder = new Camera2ImplConfig.Builder();
         camera2ConfigBuilder.setCaptureRequestOption(CaptureRequest.TONEMAP_MODE,
                 CaptureRequest.TONEMAP_MODE_HIGH_QUALITY);
         sessionBuilder.addImplementationOptions(camera2ConfigBuilder.build());
     }
+
+    private static boolean isAspectRatioMatch(
+            @NonNull Size resolution,
+            @NonNull Rational aspectRatio) {
+        return aspectRatio.equals(new Rational(resolution.getWidth(), resolution.getHeight()));
+    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
index 8505fdc..16d20ce 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinator.java
@@ -50,8 +50,8 @@
 
     @NonNull private final CameraManagerCompat mCameraManager;
     @NonNull private final List<ConcurrentCameraModeListener> mConcurrentCameraModeListeners;
-    @NonNull private final Map<String, String> mConcurrentCameraIdMap;
-    @NonNull private List<CameraSelector> mActiveConcurrentCameraSelectors;
+    @NonNull private final Map<String, List<String>> mConcurrentCameraIdMap;
+    @NonNull private List<CameraInfo> mActiveConcurrentCameraInfos;
     @NonNull private Set<Set<String>> mConcurrentCameraIds;
 
     @CameraOperatingMode private int mCameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED;
@@ -61,7 +61,7 @@
         mConcurrentCameraIdMap = new HashMap<>();
         mConcurrentCameraIds = new HashSet<>();
         mConcurrentCameraModeListeners = new ArrayList<>();
-        mActiveConcurrentCameraSelectors = new ArrayList<>();
+        mActiveConcurrentCameraInfos = new ArrayList<>();
         retrieveConcurrentCameraIds();
     }
 
@@ -81,20 +81,28 @@
 
     @NonNull
     @Override
-    public List<CameraSelector> getActiveConcurrentCameraSelectors() {
-        return mActiveConcurrentCameraSelectors;
+    public List<CameraInfo> getActiveConcurrentCameraInfos() {
+        return mActiveConcurrentCameraInfos;
     }
 
     @Override
-    public void setActiveConcurrentCameraSelectors(@NonNull List<CameraSelector> cameraSelectors) {
-        mActiveConcurrentCameraSelectors = cameraSelectors;
+    public void setActiveConcurrentCameraInfos(@NonNull List<CameraInfo> cameraInfos) {
+        mActiveConcurrentCameraInfos = new ArrayList<>(cameraInfos);
     }
 
+    @OptIn(markerClass = ExperimentalCamera2Interop.class)
     @Nullable
     @Override
     public String getPairedConcurrentCameraId(@NonNull String cameraId) {
-        if (mConcurrentCameraIdMap.containsKey(cameraId)) {
-            return mConcurrentCameraIdMap.get(cameraId);
+        if (!mConcurrentCameraIdMap.containsKey(cameraId)) {
+            return null;
+        }
+        for (String pairedCameraId : mConcurrentCameraIdMap.get(cameraId)) {
+            for (CameraInfo cameraInfo : mActiveConcurrentCameraInfos) {
+                if (pairedCameraId.equals(Camera2CameraInfo.from(cameraInfo).getCameraId())) {
+                    return pairedCameraId;
+                }
+            }
         }
         return null;
     }
@@ -114,10 +122,10 @@
                         cameraOperatingMode);
             }
         }
-        // Clear the cached camera selectors if concurrent mode is off.
+        // Clear the cached active camera infos if concurrent mode is off.
         if (mCameraOperatingMode == CAMERA_OPERATING_MODE_CONCURRENT
                 && cameraOperatingMode != CAMERA_OPERATING_MODE_CONCURRENT) {
-            mActiveConcurrentCameraSelectors.clear();
+            mActiveConcurrentCameraInfos.clear();
         }
         mCameraOperatingMode = cameraOperatingMode;
     }
@@ -142,11 +150,17 @@
         for (Set<String> concurrentCameraIdList: mConcurrentCameraIds) {
             List<String> cameraIdList = new ArrayList<>(concurrentCameraIdList);
 
-            // TODO(b/268531569): enumerate concurrent camera ids and convert to a map for
-            //  paired camera id lookup.
             if (cameraIdList.size() >= 2) {
-                mConcurrentCameraIdMap.put(cameraIdList.get(0), cameraIdList.get(1));
-                mConcurrentCameraIdMap.put(cameraIdList.get(1), cameraIdList.get(0));
+                String cameraId1 = cameraIdList.get(0);
+                String cameraId2 = cameraIdList.get(1);
+                if (!mConcurrentCameraIdMap.containsKey(cameraId1)) {
+                    mConcurrentCameraIdMap.put(cameraId1, new ArrayList<>());
+                }
+                if (!mConcurrentCameraIdMap.containsKey(cameraId2)) {
+                    mConcurrentCameraIdMap.put(cameraId2, new ArrayList<>());
+                }
+                mConcurrentCameraIdMap.get(cameraIdList.get(0)).add(cameraIdList.get(1));
+                mConcurrentCameraIdMap.get(cameraIdList.get(1)).add(cameraIdList.get(0));
             }
         }
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
index 50bb91d..bc4a710 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2SessionOptionUnpackerTest.java
@@ -26,6 +26,7 @@
 import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CaptureRequest;
 import android.os.Build;
+import android.util.Size;
 
 import androidx.annotation.OptIn;
 import androidx.camera.camera2.impl.Camera2ImplConfig;
@@ -33,10 +34,12 @@
 import androidx.camera.camera2.interop.Camera2Interop;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
 import androidx.camera.core.ImageCapture;
+import androidx.camera.core.Preview;
 import androidx.camera.core.impl.CameraCaptureCallback;
 import androidx.camera.core.impl.Config;
 import androidx.camera.core.impl.Config.OptionPriority;
 import androidx.camera.core.impl.ImageCaptureConfig;
+import androidx.camera.core.impl.PreviewConfig;
 import androidx.camera.core.impl.SessionConfig;
 
 import org.junit.Before;
@@ -44,6 +47,7 @@
 import org.junit.runner.RunWith;
 import org.robolectric.RobolectricTestRunner;
 import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.util.ReflectionHelpers;
 
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
@@ -51,6 +55,9 @@
         instrumentedPackages = { "androidx.camera.camera2.impl" })
 public final class Camera2SessionOptionUnpackerTest {
 
+    private static final Size RESOLUTION_HD = new Size(1280, 720);
+    private static final Size RESOLUTION_VGA = new Size(640, 480);
+
     private Camera2SessionOptionUnpacker mUnpacker;
 
     @Before
@@ -77,7 +84,7 @@
                 .setCameraEventCallback(cameraEventCallbacks);
 
         SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
-        mUnpacker.unpack(imageCaptureBuilder.getUseCaseConfig(), sessionBuilder);
+        mUnpacker.unpack(RESOLUTION_VGA, imageCaptureBuilder.getUseCaseConfig(), sessionBuilder);
         SessionConfig sessionConfig = sessionBuilder.build();
 
         CameraCaptureCallback interopCallback =
@@ -116,7 +123,7 @@
                 CaptureRequest.FLASH_MODE);
 
         SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
-        mUnpacker.unpack(useCaseConfig, sessionBuilder);
+        mUnpacker.unpack(RESOLUTION_VGA, useCaseConfig, sessionBuilder);
         SessionConfig sessionConfig = sessionBuilder.build();
 
         Camera2ImplConfig config = new Camera2ImplConfig(sessionConfig.getImplementationOptions());
@@ -133,6 +140,45 @@
                 .isEqualTo(priorityAfMode);
         assertThat(getCaptureRequestOptionPriority(config, CaptureRequest.CONTROL_AF_MODE))
                 .isEqualTo(priorityFlashMode);
+
+        assertThat(config.getCaptureRequestOption(CaptureRequest.TONEMAP_MODE)).isNull();
+    }
+
+    @Test
+    public void unpackerExtractsOptionsForPreviewResolution16x9() {
+        ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "Google");
+        ReflectionHelpers.setStaticField(Build.class, "DEVICE", "sunfish");
+
+        Preview.Builder previewConfigBuilder = new Preview.Builder();
+
+        PreviewConfig useCaseConfig = previewConfigBuilder.getUseCaseConfig();
+
+        SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
+        mUnpacker.unpack(RESOLUTION_HD, useCaseConfig, sessionBuilder);
+        SessionConfig sessionConfig = sessionBuilder.build();
+
+        Camera2ImplConfig config = new Camera2ImplConfig(sessionConfig.getImplementationOptions());
+
+        assertThat(config.getCaptureRequestOption(CaptureRequest.TONEMAP_MODE)).isNull();
+    }
+
+    @Test
+    public void unpackerExtractsOptionsForPreviewResolution4x3() {
+        ReflectionHelpers.setStaticField(Build.class, "MANUFACTURER", "Google");
+        ReflectionHelpers.setStaticField(Build.class, "DEVICE", "sunfish");
+
+        Preview.Builder previewConfigBuilder = new Preview.Builder();
+
+        PreviewConfig useCaseConfig = previewConfigBuilder.getUseCaseConfig();
+
+        SessionConfig.Builder sessionBuilder = new SessionConfig.Builder();
+        mUnpacker.unpack(RESOLUTION_VGA, useCaseConfig, sessionBuilder);
+        SessionConfig sessionConfig = sessionBuilder.build();
+
+        Camera2ImplConfig config = new Camera2ImplConfig(sessionConfig.getImplementationOptions());
+
+        assertThat(config.getCaptureRequestOption(CaptureRequest.TONEMAP_MODE))
+                .isEqualTo(CaptureRequest.TONEMAP_MODE_HIGH_QUALITY);
     }
 
     private OptionPriority getCaptureRequestOptionPriority(Config config,
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
index 9afa8b9..3c1f547 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/StreamUseCaseTest.java
@@ -36,6 +36,7 @@
 import androidx.camera.core.impl.DeferrableSurface;
 import androidx.camera.core.impl.MutableOptionsBundle;
 import androidx.camera.core.impl.SessionConfig;
+import androidx.camera.core.streamsharing.StreamSharing;
 import androidx.camera.testing.fakes.FakeUseCase;
 import androidx.concurrent.futures.ResolvableFuture;
 import androidx.test.filters.SdkSuppress;
@@ -118,6 +119,21 @@
     }
 
     @Test
+    public void getStreamUseCaseFromUseCaseStreamSharing() {
+        Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
+        mMockSurface.setContainerClass(StreamSharing.class);
+        SessionConfig sessionConfig =
+                new SessionConfig.Builder()
+                        .addSurface(mMockSurface).build();
+        ArrayList<SessionConfig> sessionConfigs = new ArrayList<>();
+        sessionConfigs.add(sessionConfig);
+        StreamUseCaseUtil.populateSurfaceToStreamUseCaseMapping(
+                sessionConfigs, streamUseCaseMap, getCameraCharacteristicsCompat(), true);
+        assertTrue(streamUseCaseMap.get(mMockSurface)
+                == CameraMetadata.SCALER_AVAILABLE_STREAM_USE_CASES_VIDEO_RECORD);
+    }
+
+    @Test
     public void getStreamUseCaseFromUseCasePreview() {
         Map<DeferrableSurface, Long> streamUseCaseMap = new HashMap<>();
         mMockSurface.setContainerClass(Preview.class);
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedOutputSizesCollectorTest.kt
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
index d81d6890..fad6f14 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.kt
@@ -43,6 +43,7 @@
 import androidx.camera.core.impl.AttachedSurfaceInfo
 import androidx.camera.core.impl.CameraDeviceSurfaceManager
 import androidx.camera.core.impl.ImageFormatConstants.INTERNAL_DEFINED_IMAGE_FORMAT_PRIVATE
+import androidx.camera.core.impl.StreamSpec
 import androidx.camera.core.impl.SurfaceCombination
 import androidx.camera.core.impl.SurfaceConfig
 import androidx.camera.core.impl.SurfaceConfig.ConfigSize
@@ -1332,7 +1333,8 @@
         attachedSurfaceInfoList: List<AttachedSurfaceInfo> = emptyList(),
         hardwareLevel: Int = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
         capabilities: IntArray? = null,
-        compareWithAtMost: Boolean = false
+        compareWithAtMost: Boolean = false,
+        compareExpectedFps: Range<Int>? = null
     ) {
         setupCameraAndInitCameraX(
             hardwareLevel = hardwareLevel,
@@ -1359,6 +1361,11 @@
             } else {
                 assertThat(sizeIsAtMost(resultSize, expectedSize)).isTrue()
             }
+
+            if (compareExpectedFps != null) {
+                assertThat(suggestedStreamSpecs[useCaseConfigMap[it]]!!.expectedFrameRateRange
+                == compareExpectedFps)
+            }
         }
     }
 
@@ -1383,14 +1390,6 @@
         return resultMap
     }
 
-    /**
-     * Helper function that returns whether size is <= maxSize
-     *
-     */
-    private fun sizeIsAtMost(size: Size, maxSize: Size): Boolean {
-        return (size.height * size.width) <= (maxSize.height * maxSize.width)
-    }
-
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Resolution selection tests for FPS settings
@@ -1411,7 +1410,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_single_invalid_targetFPS() {
+    fun getSuggestedStreamSpec_single_invalid_targetFPS() {
         // an invalid target means the device would neve be able to reach that fps
         val useCase = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(65, 70))
         val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
@@ -1424,7 +1423,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_multiple_targetFPS_first_is_larger() {
+    fun getSuggestedStreamSpec_multiple_targetFPS_first_is_larger() {
         // a valid target means the device is capable of that fps
         val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 35))
         val useCase2 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(15, 25))
@@ -1441,7 +1440,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_multiple_targetFPS_first_is_smaller() {
+    fun getSuggestedStreamSpec_multiple_targetFPS_first_is_smaller() {
         // a valid target means the device is capable of that fps
         val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 35))
         val useCase2 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(45, 50))
@@ -1458,7 +1457,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_multiple_targetFPS_intersect() {
+    fun getSuggestedStreamSpec_multiple_targetFPS_intersect() {
         // first and second new use cases have target fps that intersect each other
         val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 40))
         val useCase2 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(35, 45))
@@ -1476,7 +1475,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_multiple_cases_first_has_targetFPS() {
+    fun getSuggestedStreamSpec_multiple_cases_first_has_targetFPS() {
         // first new use case has a target fps, second new use case does not
         val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 35))
         val useCase2 = createUseCase(CaptureType.PREVIEW)
@@ -1493,7 +1492,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_multiple_cases_second_has_targetFPS() {
+    fun getSuggestedStreamSpec_multiple_cases_second_has_targetFPS() {
         // second new use case does not have a target fps, first new use case does not
         val useCase1 = createUseCase(CaptureType.PREVIEW)
         val useCase2 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 35))
@@ -1510,7 +1509,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_attached_with_targetFPS_no_new_targetFPS() {
+    fun getSuggestedStreamSpec_attached_with_targetFPS_no_new_targetFPS() {
         // existing surface with target fps + new use case without a target fps
         val useCase = createUseCase(CaptureType.PREVIEW)
         val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
@@ -1534,7 +1533,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_attached_with_targetFPS_and_new_targetFPS_no_intersect() {
+    fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_no_intersect() {
         // existing surface with target fps + new use case with target fps that does not intersect
         val useCase = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 35))
         val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
@@ -1558,7 +1557,7 @@
     }
 
     @Test
-    fun getSupportedOutputSizes_attached_with_targetFPS_and_new_targetFPS_with_intersect() {
+    fun getSuggestedStreamSpec_attached_with_targetFPS_and_new_targetFPS_with_intersect() {
         // existing surface with target fps + new use case with target fps that intersect each other
         val useCase = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(45, 50))
         val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
@@ -1581,6 +1580,154 @@
         )
     }
 
+    @Test
+    fun getSuggestedStreamSpec_has_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(15, 25))
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(4032, 3024))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(10, 22)
+        )
+        // expected fps 10,22 because it has the largest intersection
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_exact_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(CaptureType.PREVIEW, targetFrameRate = Range<Int>(30, 40))
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 40)
+        )
+        // expected fps 30,40 because it is an exact intersection
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_supported_expectedFrameRateRange() {
+        // use case with target fps
+        val useCase1 = createUseCase(
+            CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(65, 65)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(800, 450))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(60, 60)
+        )
+        // expected fps 60,60 because it is the closest range available
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_multiple_device_supported_expectedFrameRateRange() {
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(36, 45)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1280, 960))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 40)
+        )
+        // expected size will give a maximum of 40 fps
+        // expected range 30,40. another range with the same intersection size was 30,50, but 30,40
+        // was selected instead because its range has a larger ratio of intersecting value vs
+        // non-intersecting
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_intersection_expectedFrameRateRange() {
+        // target fps is between ranges, but within device capability (for some reason lol)
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(26, 27)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 30)
+        )
+        // 30,30 was expected because it is the closest and shortest range to our target fps
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_device_intersection_equidistant_expectedFrameRateRange() {
+
+        // use case with target fps
+        val useCase1 = createUseCase(
+            CaptureType.PREVIEW,
+            targetFrameRate = Range<Int>(26, 26)
+        )
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(1920, 1440))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareWithAtMost = true,
+            compareExpectedFps = Range(30, 30)
+        )
+        // 30,30 selected because although there are other ranges that  have the same distance to
+        // the target, 30,30 is the shortest range that also happens to be on the upper side of the
+        // target range
+    }
+
+    @Test
+    fun getSuggestedStreamSpec_has_no_expectedFrameRateRange() {
+        // a valid target means the device is capable of that fps
+
+        // use case with no target fps
+        val useCase1 = createUseCase(CaptureType.PREVIEW)
+
+        val useCaseExpectedResultMap = mutableMapOf<UseCase, Size>().apply {
+            put(useCase1, Size(4032, 3024))
+        }
+        getSuggestedSpecsAndVerify(
+            useCaseExpectedResultMap,
+            hardwareLevel = CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL,
+            compareExpectedFps = StreamSpec.FRAME_RATE_RANGE_UNSPECIFIED
+        )
+        // since no target fps present, no specific device fps will be selected, and is set to
+        // unspecified: (0,0)
+    }
+
+    /**
+     * Helper function that returns whether size is <= maxSize
+     *
+     */
+    private fun sizeIsAtMost(size: Size, maxSize: Size): Boolean {
+        return (size.height * size.width) <= (maxSize.height * maxSize.width)
+    }
+
     // //////////////////////////////////////////////////////////////////////////////////////////
     //
     // Other tests
@@ -1751,13 +1898,13 @@
                 ArgumentMatchers.anyInt(),
                 ArgumentMatchers.eq(Size(1920, 1440))
             ))
-                .thenReturn(30000000L) // 30
+                .thenReturn(33333333L) // 30
 
             Mockito.`when`(it.getOutputMinFrameDuration(
                 ArgumentMatchers.anyInt(),
                 ArgumentMatchers.eq(Size(1920, 1080))
             ))
-                .thenReturn(28000000L) // 35
+                .thenReturn(28571428L) // 35
 
             Mockito.`when`(it.getOutputMinFrameDuration(
                 ArgumentMatchers.anyInt(),
@@ -1769,7 +1916,7 @@
                 ArgumentMatchers.anyInt(),
                 ArgumentMatchers.eq(Size(1280, 720))
             ))
-                .thenReturn(22000000L) // 45, size preview/display
+                .thenReturn(22222222L) // 45, size preview/display
 
             Mockito.`when`(it.getOutputMinFrameDuration(
                 ArgumentMatchers.anyInt(),
@@ -1781,13 +1928,13 @@
                 ArgumentMatchers.anyInt(),
                 ArgumentMatchers.eq(Size(800, 450))
             ))
-                .thenReturn(16666000L) // 60fps
+                .thenReturn(16666666L) // 60fps
 
             Mockito.`when`(it.getOutputMinFrameDuration(
                 ArgumentMatchers.anyInt(),
                 ArgumentMatchers.eq(Size(640, 480))
             ))
-                .thenReturn(16666000L) // 60fps
+                .thenReturn(16666666L) // 60fps
 
             // Sets up the supported high resolution sizes
             if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
@@ -1796,6 +1943,16 @@
             }
         }
 
+        val deviceFPSRanges: Array<Range<Int>?> = arrayOf(
+            Range(10, 22),
+            Range(22, 22),
+            Range(30, 30),
+            Range(30, 50),
+            Range(30, 40),
+            Range(30, 60),
+            Range(50, 60),
+            Range(60, 60))
+
         val characteristics = ShadowCameraCharacteristics.newCameraCharacteristics()
         Shadow.extract<ShadowCameraCharacteristics>(characteristics).apply {
             set(CameraCharacteristics.LENS_FACING, CameraCharacteristics.LENS_FACING_BACK)
@@ -1803,6 +1960,8 @@
             set(CameraCharacteristics.SENSOR_ORIENTATION, sensorOrientation)
             set(CameraCharacteristics.SENSOR_INFO_PIXEL_ARRAY_SIZE, pixelArraySize)
             set(CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP, mockMap)
+            set(CameraCharacteristics.CONTROL_AE_AVAILABLE_TARGET_FPS_RANGES, deviceFPSRanges)
+
             capabilities?.let {
                 set(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES, it)
             }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatTest.kt
new file mode 100644
index 0000000..686debe
--- /dev/null
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/params/DynamicRangesCompatTest.kt
@@ -0,0 +1,360 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.compat.params
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.params.DynamicRangeProfiles
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_10B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.DOLBY_VISION_8B_HDR_OEM
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10
+import android.hardware.camera2.params.DynamicRangeProfiles.HDR10_PLUS
+import android.hardware.camera2.params.DynamicRangeProfiles.HLG10
+import android.hardware.camera2.params.DynamicRangeProfiles.STANDARD
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat
+import androidx.camera.core.DynamicRange
+import androidx.camera.core.DynamicRange.BIT_DEPTH_10_BIT
+import androidx.camera.core.DynamicRange.BIT_DEPTH_8_BIT
+import androidx.camera.core.DynamicRange.FORMAT_DOLBY_VISION
+import androidx.camera.core.DynamicRange.FORMAT_HDR10
+import androidx.camera.core.DynamicRange.FORMAT_HDR10_PLUS
+import androidx.camera.core.DynamicRange.FORMAT_HLG
+import androidx.camera.core.DynamicRange.SDR
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+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.shadow.api.Shadow
+import org.robolectric.shadows.ShadowCameraCharacteristics
+
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class DynamicRangesCompatTest {
+
+    companion object {
+        val HLG10_UNCONSTRAINED by lazy {
+            DynamicRangeProfiles(longArrayOf(HLG10, 0, 0))
+        }
+
+        val HLG10_CONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, HLG10, LATENCY_NONE
+                )
+            )
+        }
+
+        val HLG10_SDR_CONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, HLG10 or STANDARD, LATENCY_NONE
+                )
+            )
+        }
+
+        val HLG10_HDR10_CONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, HLG10 or HDR10, LATENCY_NONE,
+                    HDR10, HDR10 or HLG10, LATENCY_NONE
+                )
+            )
+        }
+
+        val HDR10_UNCONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+                    HDR10, CONSTRAINTS_NONE, LATENCY_NONE
+                )
+            )
+        }
+
+        val HDR10_PLUS_UNCONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+                    HDR10_PLUS, CONSTRAINTS_NONE, LATENCY_NONE
+                )
+            )
+        }
+
+        val DOLBY_VISION_10B_UNCONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+                    DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+                )
+            )
+        }
+
+        val DOLBY_VISION_10B_UNCONSTRAINED_SLOW by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    HLG10, CONSTRAINTS_NONE, LATENCY_NONE, // HLG is mandated
+                    DOLBY_VISION_10B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NON_ZERO
+                )
+            )
+        }
+
+        val DOLBY_VISION_8B_UNCONSTRAINED by lazy {
+            DynamicRangeProfiles(
+                longArrayOf(
+                    DOLBY_VISION_8B_HDR_OEM, CONSTRAINTS_NONE, LATENCY_NONE
+                )
+            )
+        }
+
+        private const val LATENCY_NONE = 0L
+        private const val LATENCY_NON_ZERO = 3L
+        private const val CONSTRAINTS_NONE = 0L
+
+        val DYNAMIC_RANGE_HLG10 = DynamicRange(FORMAT_HLG, BIT_DEPTH_10_BIT)
+        val DYNAMIC_RANGE_HDR10 = DynamicRange(FORMAT_HDR10, BIT_DEPTH_10_BIT)
+        val DYNAMIC_RANGE_HDR10_PLUS = DynamicRange(FORMAT_HDR10_PLUS, BIT_DEPTH_10_BIT)
+        val DYNAMIC_RANGE_DOLBY_VISION_10B = DynamicRange(FORMAT_DOLBY_VISION, BIT_DEPTH_10_BIT)
+        val DYNAMIC_RANGE_DOLBY_VISION_8B = DynamicRange(FORMAT_DOLBY_VISION, BIT_DEPTH_8_BIT)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canWrapAndUnwrapDynamicRangeProfiles() {
+        val dynamicRangesCompat = DynamicRangesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+
+        assertThat(dynamicRangesCompat).isNotNull()
+        assertThat(dynamicRangesCompat?.toDynamicRangeProfiles()).isEqualTo(HLG10_UNCONSTRAINED)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHlg10Profile() {
+        val dynamicRangesCompat = DynamicRangesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+        assertThat(dynamicRangesCompat?.supportedDynamicRanges).contains(DYNAMIC_RANGE_HLG10)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHdr10Profile() {
+        val dynamicRangesCompat = DynamicRangesCompat.toDynamicRangesCompat(HDR10_UNCONSTRAINED)
+        assertThat(dynamicRangesCompat?.supportedDynamicRanges).contains(DYNAMIC_RANGE_HDR10)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromHdr10PlusProfile() {
+        val dynamicRangesCompat =
+            DynamicRangesCompat.toDynamicRangesCompat(HDR10_PLUS_UNCONSTRAINED)
+        assertThat(dynamicRangesCompat?.supportedDynamicRanges).contains(DYNAMIC_RANGE_HDR10_PLUS)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromDolbyVision10bProfile() {
+        val dynamicRangesCompat =
+            DynamicRangesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED)
+        assertThat(dynamicRangesCompat?.supportedDynamicRanges).contains(
+            DYNAMIC_RANGE_DOLBY_VISION_10B
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canSupportDynamicRangeFromDolbyVision8bProfile() {
+        val dynamicRangesCompat =
+            DynamicRangesCompat.toDynamicRangesCompat(DOLBY_VISION_8B_UNCONSTRAINED)
+        assertThat(dynamicRangesCompat?.supportedDynamicRanges).contains(
+            DYNAMIC_RANGE_DOLBY_VISION_8B
+        )
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceConcurrentDynamicRangeConstraints() {
+        val hlg10ConstrainedWrapped = DynamicRangesCompat.toDynamicRangesCompat(HLG10_CONSTRAINED)
+        assertThat(
+            hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(SDR)
+        ).containsExactly(SDR)
+        assertThat(
+            hlg10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DYNAMIC_RANGE_HLG10
+            )
+        ).containsExactly(DYNAMIC_RANGE_HLG10)
+
+        val hlg10SdrConstrainedWrapped =
+            DynamicRangesCompat.toDynamicRangesCompat(HLG10_SDR_CONSTRAINED)
+        assertThat(
+            hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(SDR)
+        ).containsExactly(SDR, DYNAMIC_RANGE_HLG10)
+        assertThat(
+            hlg10SdrConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DYNAMIC_RANGE_HLG10
+            )
+        ).containsExactly(DYNAMIC_RANGE_HLG10, SDR)
+
+        val hlg10Hdr10ConstrainedWrapped =
+            DynamicRangesCompat.toDynamicRangesCompat(HLG10_HDR10_CONSTRAINED)
+        assertThat(
+            hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(SDR)
+        ).containsExactly(SDR)
+        assertThat(
+            hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DYNAMIC_RANGE_HLG10
+            )
+        ).containsExactly(DYNAMIC_RANGE_HLG10, DYNAMIC_RANGE_HDR10)
+        assertThat(
+            hlg10Hdr10ConstrainedWrapped?.getDynamicRangeCaptureRequestConstraints(
+                DYNAMIC_RANGE_HDR10
+            )
+        ).containsExactly(DYNAMIC_RANGE_HDR10, DYNAMIC_RANGE_HLG10)
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun producesDynamicRangeWithCorrectLatency() {
+        val dynamicRangesCompat =
+            DynamicRangesCompat.toDynamicRangesCompat(DOLBY_VISION_10B_UNCONSTRAINED_SLOW)
+        assertThat(dynamicRangesCompat?.isExtraLatencyPresent(SDR)).isFalse()
+        assertThat(
+            dynamicRangesCompat?.isExtraLatencyPresent(
+                DYNAMIC_RANGE_DOLBY_VISION_10B
+            )
+        ).isTrue()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceDynamicRangeWithoutConstraints() {
+        val dynamicRangesCompat = DynamicRangesCompat.toDynamicRangesCompat(HLG10_UNCONSTRAINED)
+        assertThat(
+            dynamicRangesCompat?.getDynamicRangeCaptureRequestConstraints(DYNAMIC_RANGE_HLG10)
+        ).isEmpty()
+    }
+
+    @Config(minSdk = Build.VERSION_CODES.TIRAMISU)
+    @Test
+    fun canProduceDynamicRangesCompatFromCharacteristics() {
+        val characteristics = newCameraCharacteristicsCompat()
+        Shadow.extract<ShadowCameraCharacteristics>(
+            characteristics.toCameraCharacteristics()
+        ).addDynamicRangeProfiles(HLG10_CONSTRAINED)
+
+        val dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(characteristics)
+
+        assertThat(dynamicRangesCompat.toDynamicRangeProfiles()).isEqualTo(HLG10_CONSTRAINED)
+    }
+
+    @Test
+    fun alwaysSupportsOnlySdrWithoutDynamicRangeProfilesInCharacteristics() {
+        val characteristics = newCameraCharacteristicsCompat()
+
+        val dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(characteristics)
+
+        assertThat(dynamicRangesCompat.supportedDynamicRanges).containsExactly(SDR)
+        assertThat(
+            dynamicRangesCompat.getDynamicRangeCaptureRequestConstraints(SDR)
+        ).containsExactly(SDR)
+    }
+
+    @Test
+    fun unsupportedDynamicRangeAlwaysThrowsException() {
+        val characteristics = newCameraCharacteristicsCompat()
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            Shadow.extract<ShadowCameraCharacteristics>(
+                characteristics.toCameraCharacteristics()
+            ).addDynamicRangeProfiles(DOLBY_VISION_8B_UNCONSTRAINED)
+        }
+
+        val dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(characteristics)
+
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.TIRAMISU) {
+            assertThat(dynamicRangesCompat.supportedDynamicRanges).containsExactly(SDR)
+        } else {
+            assertThat(dynamicRangesCompat.supportedDynamicRanges).containsExactly(
+                SDR, DYNAMIC_RANGE_DOLBY_VISION_8B
+            )
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            dynamicRangesCompat.getDynamicRangeCaptureRequestConstraints(
+                DYNAMIC_RANGE_DOLBY_VISION_10B
+            )
+        }
+
+        assertThrows(IllegalArgumentException::class.java) {
+            dynamicRangesCompat.isExtraLatencyPresent(
+                DYNAMIC_RANGE_DOLBY_VISION_10B
+            )
+        }
+    }
+
+    @Test
+    fun sdrHasNoExtraLatency() {
+        val characteristics = newCameraCharacteristicsCompat()
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            Shadow.extract<ShadowCameraCharacteristics>(
+                characteristics.toCameraCharacteristics()
+            ).addDynamicRangeProfiles(HLG10_CONSTRAINED)
+        }
+
+        val dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(characteristics)
+
+        assertThat(dynamicRangesCompat.isExtraLatencyPresent(SDR)).isFalse()
+    }
+
+    @Test
+    fun sdrHasSdrConstraint_whenConcurrentDynamicRangesNotSupported() {
+        val characteristics = newCameraCharacteristicsCompat()
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+            Shadow.extract<ShadowCameraCharacteristics>(
+                characteristics.toCameraCharacteristics()
+            ).addDynamicRangeProfiles(HLG10_CONSTRAINED)
+        }
+
+        val dynamicRangesCompat = DynamicRangesCompat.fromCameraCharacteristics(characteristics)
+
+        assertThat(dynamicRangesCompat.getDynamicRangeCaptureRequestConstraints(SDR))
+            .containsExactly(SDR)
+    }
+}
+
+private const val CAMERA_ID = "0"
+
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+fun newCameraCharacteristicsCompat(): CameraCharacteristicsCompat {
+    return CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
+        ShadowCameraCharacteristics.newCameraCharacteristics(),
+        CAMERA_ID
+    )
+}
+
+@RequiresApi(Build.VERSION_CODES.TIRAMISU)
+fun ShadowCameraCharacteristics.addDynamicRangeProfiles(
+    dynamicRangeProfiles: DynamicRangeProfiles
+) {
+    set(
+        CameraCharacteristics.REQUEST_AVAILABLE_DYNAMIC_RANGE_PROFILES,
+        dynamicRangeProfiles
+    )
+}
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
index c485a90..c526cd1e 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/concurrent/Camera2CameraCoordinatorTest.kt
@@ -16,16 +16,20 @@
 
 package androidx.camera.camera2.internal.concurrent
 
+import android.content.Context
 import android.hardware.camera2.CameraCharacteristics
 import android.hardware.camera2.CameraDevice
 import android.hardware.camera2.CameraManager
 import android.os.Build
+import androidx.camera.camera2.internal.Camera2CameraInfoImpl
 import androidx.camera.camera2.internal.compat.CameraManagerCompat
+import androidx.camera.core.CameraSelector
 import androidx.camera.core.concurrent.CameraCoordinator
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE
 import androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED
 import androidx.camera.core.impl.utils.MainThreadAsyncHandler
+import androidx.camera.testing.fakes.FakeCameraInfoInternal
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.Executor
@@ -41,6 +45,10 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.shadow.api.Shadow
+
+import org.robolectric.shadows.ShadowCameraCharacteristics
+import org.robolectric.shadows.ShadowCameraManager
 
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
@@ -50,6 +58,8 @@
 )
 class Camera2CameraCoordinatorTest {
 
+    private val mContext = ApplicationProvider.getApplicationContext<Context>()
+
     private lateinit var cameraCoordinator: CameraCoordinator
 
     @Before
@@ -82,24 +92,45 @@
         // Concurrent -> Single
         cameraCoordinator.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
         assertThat(cameraCoordinator.concurrentCameraSelectors).isNotEmpty()
-        cameraCoordinator.activeConcurrentCameraSelectors =
-            cameraCoordinator.concurrentCameraSelectors[0]
-        assertThat(cameraCoordinator.activeConcurrentCameraSelectors).isNotEmpty()
+        cameraCoordinator.activeConcurrentCameraInfos =
+            listOf(FakeCameraInfoInternal(0, CameraSelector.LENS_FACING_BACK))
+        assertThat(cameraCoordinator.activeConcurrentCameraInfos).isNotEmpty()
 
         cameraCoordinator.cameraOperatingMode = CAMERA_OPERATING_MODE_SINGLE
-        assertThat(cameraCoordinator.activeConcurrentCameraSelectors).isEmpty()
+        assertThat(cameraCoordinator.activeConcurrentCameraInfos).isEmpty()
 
         // Concurrent -> Unspecified
         cameraCoordinator.cameraOperatingMode = CAMERA_OPERATING_MODE_CONCURRENT
-        cameraCoordinator.activeConcurrentCameraSelectors =
-            cameraCoordinator.concurrentCameraSelectors[0]
+        cameraCoordinator.activeConcurrentCameraInfos =
+            listOf(FakeCameraInfoInternal(0, CameraSelector.LENS_FACING_BACK))
 
         cameraCoordinator.cameraOperatingMode = CAMERA_OPERATING_MODE_UNSPECIFIED
-        assertThat(cameraCoordinator.activeConcurrentCameraSelectors).isEmpty()
+        assertThat(cameraCoordinator.activeConcurrentCameraInfos).isEmpty()
     }
 
     @Test
     fun getPairedCameraId() {
+        val characteristics0 = ShadowCameraCharacteristics.newCameraCharacteristics()
+        (Shadow.extract<Any>(
+            ApplicationProvider.getApplicationContext<Context>()
+                .getSystemService(Context.CAMERA_SERVICE)
+        ) as ShadowCameraManager)
+            .addCamera("0", characteristics0)
+        val characteristics1 = ShadowCameraCharacteristics.newCameraCharacteristics()
+        (Shadow.extract<Any>(
+            ApplicationProvider.getApplicationContext<Context>()
+                .getSystemService(Context.CAMERA_SERVICE)
+        ) as ShadowCameraManager)
+            .addCamera("1", characteristics1)
+
+        val mCameraManagerCompat =
+            CameraManagerCompat.from((ApplicationProvider.getApplicationContext() as Context))
+
+        cameraCoordinator.activeConcurrentCameraInfos = listOf(
+            Camera2CameraInfoImpl("0", mCameraManagerCompat),
+            Camera2CameraInfoImpl("1", mCameraManagerCompat)
+        )
+
         assertThat(cameraCoordinator.getPairedConcurrentCameraId("0")).isEqualTo("1")
         assertThat(cameraCoordinator.getPairedConcurrentCameraId("1")).isEqualTo("0")
     }
diff --git a/camera/camera-core/api/current.txt b/camera/camera-core/api/current.txt
index 391ebc9a..4614cf8 100644
--- a/camera/camera-core/api/current.txt
+++ b/camera/camera-core/api/current.txt
@@ -32,6 +32,7 @@
     method public int getTargets();
     field public static final int IMAGE_CAPTURE = 4; // 0x4
     field public static final int PREVIEW = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 2; // 0x2
   }
 
   @RequiresApi(21) public interface CameraFilter {
@@ -198,6 +199,7 @@
     method public boolean isOutputImageRotationEnabled();
     method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
     field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
     field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
@@ -235,6 +237,7 @@
     method public void setCropAspectRatio(android.util.Rational);
     method public void setFlashMode(int);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
     method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
     field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
diff --git a/camera/camera-core/api/public_plus_experimental_current.txt b/camera/camera-core/api/public_plus_experimental_current.txt
index 10bd0e1..fc38de4 100644
--- a/camera/camera-core/api/public_plus_experimental_current.txt
+++ b/camera/camera-core/api/public_plus_experimental_current.txt
@@ -32,6 +32,7 @@
     method public int getTargets();
     field public static final int IMAGE_CAPTURE = 4; // 0x4
     field public static final int PREVIEW = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 2; // 0x2
   }
 
   @RequiresApi(21) public interface CameraFilter {
@@ -213,6 +214,7 @@
     method public boolean isOutputImageRotationEnabled();
     method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
     field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
     field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
@@ -250,6 +252,7 @@
     method public void setCropAspectRatio(android.util.Rational);
     method public void setFlashMode(int);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
     method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
     field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
diff --git a/camera/camera-core/api/restricted_current.txt b/camera/camera-core/api/restricted_current.txt
index 391ebc9a..4614cf8 100644
--- a/camera/camera-core/api/restricted_current.txt
+++ b/camera/camera-core/api/restricted_current.txt
@@ -32,6 +32,7 @@
     method public int getTargets();
     field public static final int IMAGE_CAPTURE = 4; // 0x4
     field public static final int PREVIEW = 1; // 0x1
+    field public static final int VIDEO_CAPTURE = 2; // 0x2
   }
 
   @RequiresApi(21) public interface CameraFilter {
@@ -198,6 +199,7 @@
     method public boolean isOutputImageRotationEnabled();
     method public void setAnalyzer(java.util.concurrent.Executor, androidx.camera.core.ImageAnalysis.Analyzer);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     field public static final int COORDINATE_SYSTEM_ORIGINAL = 0; // 0x0
     field public static final int OUTPUT_IMAGE_FORMAT_RGBA_8888 = 2; // 0x2
     field public static final int OUTPUT_IMAGE_FORMAT_YUV_420_888 = 1; // 0x1
@@ -235,6 +237,7 @@
     method public void setCropAspectRatio(android.util.Rational);
     method public void setFlashMode(int);
     method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public void takePicture(java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageCapturedCallback);
     method public void takePicture(androidx.camera.core.ImageCapture.OutputFileOptions, java.util.concurrent.Executor, androidx.camera.core.ImageCapture.OnImageSavedCallback);
     field public static final int CAPTURE_MODE_MAXIMIZE_QUALITY = 0; // 0x0
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
index 85809c2cb..a385c37 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageCaptureTest.java
@@ -540,7 +540,7 @@
         ImageCapture imageCapture = new ImageCapture.Builder()
                 .setCaptureOptionUnpacker((config, builder) -> {
                 })
-                .setSessionOptionUnpacker((config, builder) -> {
+                .setSessionOptionUnpacker((resolution, config, builder) -> {
                 }).build();
         imageCapture.setCropAspectRatio(new Rational(16, 9));
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
index b6d5748..c40c0f1 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraEffect.java
@@ -97,7 +97,6 @@
     /**
      * Bitmask options for the effect targets.
      *
-     * @hide
      */
     @Retention(RetentionPolicy.SOURCE)
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -112,9 +111,7 @@
 
     /**
      * Bitmask option to indicate that CameraX should apply this effect to {@code VideoCapture}.
-     *
      */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public static final int VIDEO_CAPTURE = 1 << 1;
 
     /**
@@ -227,7 +224,6 @@
      * <p>Throws {@link IllegalArgumentException} if the effect does not contain a
      * {@link SurfaceProcessor}.
      *
-     * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
index 79e1c4a..d790a43 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/CameraProvider.java
@@ -70,7 +70,6 @@
      *
      * @return list of combinations of {@link CameraInfo}.
      *
-     * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @NonNull
@@ -81,7 +80,6 @@
      *
      * @return true if concurrent mode is enabled, otherwise false.
      *
-     * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     boolean isConcurrentCameraModeOn();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java b/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java
new file mode 100644
index 0000000..62c1199
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/DynamicRange.java
@@ -0,0 +1,176 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.RetentionPolicy;
+
+/** A representation of the dynamic range of an image. */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public final class DynamicRange {
+    /** Standard Dynamic Range (SDR) format. */
+    public static final int FORMAT_SDR = 0;
+
+    //------------------------------------------------------------------------------//
+    //                            HDR Formats                                       //
+    //------------------------------------------------------------------------------//
+    /**
+     * An unspecified High Dynamic Range (HDR) format which allows the device to determine the
+     * underlying dynamic range format.
+     */
+    public static final int FORMAT_HDR_UNSPECIFIED = 1;
+    /** Hybrid Log Gamma (HLG) dynamic range format. */
+    public static final int FORMAT_HLG = FORMAT_HDR_UNSPECIFIED + 1;
+    /** HDR10 dynamic range format. */
+    public static final int FORMAT_HDR10 = FORMAT_HDR_UNSPECIFIED + 2;
+    /** HDR10+ dynamic range format. */
+    public static final int FORMAT_HDR10_PLUS = FORMAT_HDR_UNSPECIFIED + 3;
+    /** Dolby Vision dynamic range format. */
+    public static final int FORMAT_DOLBY_VISION = FORMAT_HDR_UNSPECIFIED + 4;
+    //------------------------------------------------------------------------------//
+
+    /** Bit depth is unspecified and may be determined automatically by the device. */
+    public static final int BIT_DEPTH_UNSPECIFIED = 0;
+    /** Eight-bit bit depth. */
+    public static final int BIT_DEPTH_8_BIT = 8;
+    /** Ten-bit bit depth. */
+    public static final int BIT_DEPTH_10_BIT = 10;
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({FORMAT_SDR, FORMAT_HDR_UNSPECIFIED, FORMAT_HLG, FORMAT_HDR10, FORMAT_HDR10_PLUS,
+            FORMAT_DOLBY_VISION})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface DynamicRangeFormat {
+    }
+
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({BIT_DEPTH_UNSPECIFIED, BIT_DEPTH_8_BIT, BIT_DEPTH_10_BIT})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface BitDepth {
+    }
+
+    /** A dynamic range representing 8-bit standard dynamic range (SDR). */
+    @NonNull
+    public static final DynamicRange SDR = new DynamicRange(FORMAT_SDR, BIT_DEPTH_8_BIT);
+
+    /**
+     * A dynamic range representing 10-bit high dynamic range (HDR) with unspecified format.
+     *
+     * <p>The HDR format is unspecified, and may defer to device defaults
+     * when used to select a dynamic range.
+     */
+    @NonNull
+    public static final DynamicRange HDR_UNSPECIFIED_10_BIT =
+            new DynamicRange(FORMAT_HDR_UNSPECIFIED, BIT_DEPTH_10_BIT);
+
+    private final @DynamicRangeFormat int mFormat;
+    private final @BitDepth int mBitDepth;
+
+    /**
+     * Creates a dynamic range representation from a format and bit depth.
+     *
+     * <p>This constructor is left public for testing purposes. It does not do any verification that
+     * the provided arguments are a valid combination of format and bit depth.
+     *
+     * @param format   The dynamic range format.
+     * @param bitDepth The bit depth.
+     */
+    public DynamicRange(
+            @DynamicRangeFormat int format,
+            @BitDepth int bitDepth) {
+        mFormat = format;
+        mBitDepth = bitDepth;
+    }
+
+    /**
+     * Returns the dynamic range format.
+     *
+     * @return The dynamic range format. Possible values are {@link #FORMAT_SDR},
+     * {@link #FORMAT_HLG}, {@link #FORMAT_HDR10}, {@link #FORMAT_HDR10_PLUS}, or
+     * {@link #FORMAT_DOLBY_VISION}.
+     */
+    @DynamicRangeFormat
+    public int getFormat() {
+        return mFormat;
+    }
+
+    /**
+     * Returns the bit depth used by this dynamic range configuration.
+     *
+     * <p>Common values are {@link #BIT_DEPTH_8_BIT}, such as for {@link #FORMAT_SDR} or
+     * {@link #BIT_DEPTH_10_BIT}, such as for {@link #FORMAT_HDR10}.
+     *
+     * @return The bit depth. Possible values are {@link #BIT_DEPTH_8_BIT},
+     * {@link #BIT_DEPTH_10_BIT}, or {@link #BIT_DEPTH_UNSPECIFIED}.
+     */
+    @BitDepth
+    public int getBitDepth() {
+        return mBitDepth;
+    }
+
+    @NonNull
+    @Override
+    public String toString() {
+        return "DynamicRange@" + Integer.toHexString(System.identityHashCode(this)) + "{"
+                + "format=" + getFormatLabel(mFormat) + ", "
+                + "bitDepth=" + mBitDepth
+                + "}";
+    }
+
+    @Override
+    public boolean equals(Object o) {
+        if (o == this) {
+            return true;
+        }
+        if (o instanceof DynamicRange) {
+            DynamicRange that = (DynamicRange) o;
+            return this.mFormat == that.getFormat()
+                    && this.mBitDepth == that.getBitDepth();
+        }
+        return false;
+    }
+
+    @Override
+    public int hashCode() {
+        int hashCode = 1;
+        hashCode *= 1000003;
+        hashCode ^= mFormat;
+        hashCode *= 1000003;
+        hashCode ^= mBitDepth;
+        return hashCode;
+    }
+
+    @NonNull
+    private static String getFormatLabel(@DynamicRangeFormat int format) {
+        switch (format) {
+            case FORMAT_SDR: return "FORMAT_SDR";
+            case FORMAT_HDR_UNSPECIFIED: return "FORMAT_HDR_UNSPECIFIED";
+            case FORMAT_HLG: return "FORMAT_HLG";
+            case FORMAT_HDR10: return "FORMAT_HDR10";
+            case FORMAT_HDR10_PLUS: return "FORMAT_HDR10_PLUS";
+            case FORMAT_DOLBY_VISION: return "FORMAT_DOLBY_VISION";
+        }
+
+        return "<Unknown>";
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
index 506de73..59a07ee4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageAnalysis.java
@@ -358,7 +358,8 @@
         imageReaderProxy.setOnImageAvailableListener(mImageAnalysisAbstractAnalyzer,
                 backgroundExecutor);
 
-        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
 
         if (mDeferrableSurface != null) {
             mDeferrableSurface.close();
@@ -432,9 +433,10 @@
      * <p>
      * The rotation can be set when constructing an {@link ImageAnalysis} instance using
      * {@link ImageAnalysis.Builder#setTargetRotation(int)}, or dynamically by calling
-     * {@link ImageAnalysis#setTargetRotation(int)}. If not set, the target rotation defaults to
-     * the value of {@link Display#getRotation()} of the default display at the time the use case
-     * is created. The use case is fully created once it has been attached to a camera.
+     * {@link ImageAnalysis#setTargetRotation(int)} or
+     * {@link ImageAnalysis#setTargetRotationDegrees(int)}. If not set, the target rotation
+     * defaults to the value of {@link Display#getRotation()} of the default display at the time
+     * the use case is created. The use case is fully created once it has been attached to a camera.
      * </p>
      *
      * @return The rotation of the intended target for images.
@@ -462,23 +464,20 @@
      * which way is down for a given image.  This is important since display orientation may be
      * locked by device default, user setting, or app configuration, and some devices may not
      * transition to a reverse-portrait display orientation.  In these cases, use
-     * {@link ImageAnalysis#setTargetRotation} to set target rotation dynamically according to
-     * the {@link android.view.OrientationEventListener}, without re-creating the use case. Note
-     * the OrientationEventListener output of degrees in the range [0..359] should be converted to
-     * a surface rotation. The mapping values are listed as the following.
-     * <p>{@link android.view.OrientationEventListener#ORIENTATION_UNKNOWN}: orientation == -1
-     * <p>{@link Surface#ROTATION_0}: orientation >= 315 || orientation < 45
-     * <p>{@link Surface#ROTATION_90}: orientation >= 225 && orientation < 315
-     * <p>{@link Surface#ROTATION_180}: orientation >= 135 && orientation < 225
-     * <p>{@link Surface#ROTATION_270}: orientation >= 45 && orientation < 135
+     * {@link ImageAnalysis#setTargetRotationDegrees(int)} to set target rotation dynamically
+     * according to the {@link android.view.OrientationEventListener}, without re-creating the
+     * use case. See {@link #setTargetRotationDegrees} for more information.
      *
      * <p>When this function is called, value set by
      * {@link ImageAnalysis.Builder#setTargetResolution(Size)} will be updated automatically to
      * make sure the suitable resolution can be selected when the use case is bound.
      *
      * <p>If not set here or by configuration, the target rotation will default to the value of
-     * {@link Display#getRotation()} of the default display at the time the use case is created.
-     * The use case is fully created once it has been attached to a camera.
+     * {@link Display#getRotation()} of the default display at the time the use case is bound. To
+     * return to the default value, set the value to
+     * <pre>{@code
+     * context.getSystemService(WindowManager.class).getDefaultDisplay().getRotation();
+     * }</pre>
      *
      * @param rotation Target rotation of the output image, expressed as one of
      *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
@@ -491,6 +490,74 @@
     }
 
     /**
+     * Sets the target rotation in degrees.
+     *
+     * <p>In general, it is best to use an {@link  android.view.OrientationEventListener} to set
+     * the target rotation. This way, the rotation output will indicate which way is down for a
+     * given image. This is important since display orientation may be locked by device default,
+     * user setting, or app configuration, and some devices may not transition to a
+     * reverse-portrait display orientation. In these cases, use
+     * {@code setTargetRotationDegrees()} to set target rotation dynamically according
+     * to the {@link  android.view.OrientationEventListener}, without re-creating the use case.
+     * The sample code is as below:
+     * <pre>{@code
+     * public class CameraXActivity extends AppCompatActivity {
+     *
+     *     private OrientationEventListener mOrientationEventListener;
+     *
+     *     @Override
+     *     protected void onStart() {
+     *         super.onStart();
+     *         if (mOrientationEventListener == null) {
+     *             mOrientationEventListener = new OrientationEventListener(this) {
+     *                 @Override
+     *                 public void onOrientationChanged(int orientation) {
+     *                     if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+     *                         return;
+     *                     }
+     *                     mImageAnalysis.setTargetRotationDegrees(orientation);
+     *                 }
+     *             };
+     *         }
+     *         mOrientationEventListener.enable();
+     *     }
+     *
+     *     @Override
+     *     protected void onStop() {
+     *         super.onStop();
+     *         mOrientationEventListener.disable();
+     *     }
+     * }
+     * }</pre>
+     *
+     * <p>{@code setTargetRotationDegrees()} cannot rotate the camera image to an arbitrary angle,
+     * instead it maps the angle to one of {@link Surface#ROTATION_0},
+     * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and {@link Surface#ROTATION_270}
+     * as the input of {@link #setTargetRotation(int)}. The rule is as follows:
+     * <p>If the input degrees is not in the range [0..359], it will be converted to the equivalent
+     * degrees in the range [0..359]. And then take the following mapping based on the input
+     * degrees.
+     * <p>degrees >= 315 || degrees < 45 -> {@link Surface#ROTATION_0}
+     * <p>degrees >= 225 && degrees < 315 -> {@link Surface#ROTATION_90}
+     * <p>degrees >= 135 && degrees < 225 -> {@link Surface#ROTATION_180}
+     * <p>degrees >= 45 && degrees < 135 -> {@link Surface#ROTATION_270}
+     * <p>The rotation value can be obtained by {@link #getTargetRotation()}. This means the
+     * rotation previously set by {@link #setTargetRotation(int)} will be overridden by
+     * {@code setTargetRotationDegrees(int)}, and vice versa.
+     *
+     * <p>When this function is called, value set by
+     * {@link ImageAnalysis.Builder#setTargetResolution(Size)} will be updated automatically to
+     * make sure the suitable resolution can be selected when the use case is bound.
+     *
+     * @param degrees Desired rotation degree of the output image.
+     * @see #setTargetRotation(int)
+     * @see #getTargetRotation()
+     */
+    public void setTargetRotationDegrees(int degrees) {
+        setTargetRotation(orientationDegreesToSurfaceRotation(degrees));
+    }
+
+    /**
      * Sets an analyzer to receive and analyze images.
      *
      * <p>Setting an analyzer will signal to the camera that it should begin sending data. The
@@ -1219,7 +1286,7 @@
          *
          * <p>In general, it is best to additionally set the target rotation dynamically on the use
          * case.  See
-         * {@link androidx.camera.core.ImageAnalysis#setTargetRotation(int)} for additional
+         * {@link androidx.camera.core.ImageAnalysis#setTargetRotationDegrees(int)} for additional
          * documentation.
          *
          * <p>If not set, the target rotation will default to the value of
@@ -1229,6 +1296,7 @@
          * @param rotation The rotation of the intended target.
          * @return The current Builder.
          * @see androidx.camera.core.ImageAnalysis#setTargetRotation(int)
+         * @see androidx.camera.core.ImageAnalysis#setTargetRotationDegrees(int)
          * @see android.view.OrientationEventListener
          */
         @NonNull
@@ -1241,7 +1309,6 @@
         /**
          * setMirrorMode is not supported on ImageAnalysis.
          *
-         * @hide
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index ee6811d..ebae469 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -373,7 +373,8 @@
         if (isNodeEnabled()) {
             return createPipelineWithNode(cameraId, config, streamSpec);
         }
-        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
 
         if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
             getCameraControl().addZslConfig(sessionConfigBuilder);
@@ -701,9 +702,10 @@
      *
      * <p>The rotation can be set prior to constructing an ImageCapture using
      * {@link ImageCapture.Builder#setTargetRotation(int)} or dynamically by calling
-     * {@link ImageCapture#setTargetRotation(int)}. The rotation of an image taken is determined
-     * by the rotation value set at the time image capture is initiated, such as when calling
-     * {@link #takePicture(Executor, OnImageCapturedCallback)}.
+     * {@link ImageCapture#setTargetRotation(int)} or
+     * {@link ImageCapture#setTargetRotationDegrees(int)}. The rotation of an image taken is
+     * determined by the rotation value set at the time image capture is initiated, such as when
+     * calling {@link #takePicture(Executor, OnImageCapturedCallback)}.
      *
      * <p>If no target rotation is set by the application, it is set to the value of
      * {@link Display#getRotation()} of the default display at the time the use case is
@@ -732,15 +734,9 @@
      * a given image.  This is important since display orientation may be locked by device
      * default, user setting, or app configuration, and some devices may not transition to a
      * reverse-portrait display orientation. In these cases,
-     * use {@code setTargetRotation} to set target rotation dynamically according to the
-     * {@link android.view.OrientationEventListener}, without re-creating the use case.  Note
-     * the OrientationEventListener output of degrees in the range [0..359] should be converted to
-     * a surface rotation. The mapping values are listed as the following.
-     * <p>{@link android.view.OrientationEventListener#ORIENTATION_UNKNOWN}: orientation == -1
-     * <p>{@link Surface#ROTATION_0}: orientation >= 315 || orientation < 45
-     * <p>{@link Surface#ROTATION_90}: orientation >= 225 && orientation < 315
-     * <p>{@link Surface#ROTATION_180}: orientation >= 135 && orientation < 225
-     * <p>{@link Surface#ROTATION_270}: orientation >= 45 && orientation < 135
+     * use {@link #setTargetRotationDegrees} to set target rotation dynamically according to the
+     * {@link android.view.OrientationEventListener}, without re-creating the use case.
+     * See {@link #setTargetRotationDegrees} for more information.
      *
      * <p>When this function is called, value set by
      * {@link ImageCapture.Builder#setTargetResolution(Size)} will be updated automatically to make
@@ -749,8 +745,11 @@
      * make sure the output image is cropped into expected aspect ratio.
      *
      * <p>If no target rotation is set by the application, it is set to the value of
-     * {@link Display#getRotation()} of the default display at the time the use case is created. The
-     * use case is fully created once it has been attached to a camera.
+     * {@link Display#getRotation()} of the default display at the time the use case is bound. To
+     * return to the default value, set the value to
+     * <pre>{@code
+     * context.getSystemService(WindowManager.class).getDefaultDisplay().getRotation();
+     * }</pre>
      *
      * <p>takePicture uses the target rotation at the time it begins executing (which may be delayed
      * waiting on a previous takePicture call to complete).
@@ -779,6 +778,79 @@
     }
 
     /**
+     * Sets the desired rotation of the output image in degrees.
+     *
+     * <p>In general, it is best to use an {@link  android.view.OrientationEventListener} to set
+     * the target rotation. This way, the rotation output will indicate which way is down for a
+     * given image. This is important since display orientation may be locked by device default,
+     * user setting, or app configuration, and some devices may not transition to a
+     * reverse-portrait display orientation. In these cases, use
+     * {@code setTargetRotationDegrees()} to set target rotation dynamically according
+     * to the {@link  android.view.OrientationEventListener}, without re-creating the use case.
+     * The sample code is as below:
+     * <pre>{@code
+     * public class CameraXActivity extends AppCompatActivity {
+     *
+     *     private OrientationEventListener mOrientationEventListener;
+     *
+     *     @Override
+     *     protected void onStart() {
+     *         super.onStart();
+     *         if (mOrientationEventListener == null) {
+     *             mOrientationEventListener = new OrientationEventListener(this) {
+     *                 @Override
+     *                 public void onOrientationChanged(int orientation) {
+     *                     if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+     *                         return;
+     *                     }
+     *                     mImageCapture.setTargetRotationDegrees(orientation);
+     *                 }
+     *             };
+     *         }
+     *         mOrientationEventListener.enable();
+     *     }
+     *
+     *     @Override
+     *     protected void onStop() {
+     *         super.onStop();
+     *         mOrientationEventListener.disable();
+     *     }
+     * }
+     * }</pre>
+     *
+     * <p>{@code setTargetRotationDegrees()} cannot rotate the camera image to an arbitrary angle,
+     * instead it maps the angle to one of {@link Surface#ROTATION_0},
+     * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and {@link Surface#ROTATION_270}
+     * as the input of {@link #setTargetRotation(int)}. The rule is as follows:
+     * <p>If the input degrees is not in the range [0..359], it will be converted to the equivalent
+     * degrees in the range [0..359]. And then take the following mapping based on the input
+     * degrees.
+     * <p>degrees >= 315 || degrees < 45 -> {@link Surface#ROTATION_0}
+     * <p>degrees >= 225 && degrees < 315 -> {@link Surface#ROTATION_90}
+     * <p>degrees >= 135 && degrees < 225 -> {@link Surface#ROTATION_180}
+     * <p>degrees >= 45 && degrees < 135 -> {@link Surface#ROTATION_270}
+     * <p>The rotation value can be obtained by {@link #getTargetRotation()}. This means the
+     * rotation previously set by {@link #setTargetRotation(int)} will be overridden by
+     * {@code setTargetRotationDegrees(int)}, and vice versa.
+     *
+     * <p>When this function is called, value set by
+     * {@link ImageCapture.Builder#setTargetResolution(Size)} will be updated automatically to make
+     * sure the suitable resolution can be selected when the use case is bound. Value set by
+     * {@link ImageCapture#setCropAspectRatio(Rational)} will also be updated automatically to
+     * make sure the output image is cropped into expected aspect ratio.
+     *
+     * <p>takePicture uses the target rotation at the time it begins executing (which may be delayed
+     * waiting on a previous takePicture call to complete).
+     *
+     * @param degrees Desired rotation degree of the output image.
+     * @see #setTargetRotation(int)
+     * @see #getTargetRotation()
+     */
+    public void setTargetRotationDegrees(int degrees) {
+        setTargetRotation(orientationDegreesToSurfaceRotation(degrees));
+    }
+
+    /**
      * Returns the set capture mode.
      *
      * <p>This is set when constructing an ImageCapture using
@@ -1660,7 +1732,8 @@
         }
         mTakePictureManager.setImagePipeline(mImagePipeline);
 
-        SessionConfig.Builder sessionConfigBuilder = mImagePipeline.createSessionConfigBuilder();
+        SessionConfig.Builder sessionConfigBuilder =
+                mImagePipeline.createSessionConfigBuilder(streamSpec.getResolution());
         if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
             getCameraControl().addZslConfig(sessionConfigBuilder);
         }
@@ -2707,7 +2780,7 @@
          * Rotation values are relative to the "natural" rotation, {@link Surface#ROTATION_0}.
          *
          * <p>In general, it is best to additionally set the target rotation dynamically on the use
-         * case.  See {@link androidx.camera.core.ImageCapture#setTargetRotation(int)} for
+         * case.  See {@link androidx.camera.core.ImageCapture#setTargetRotationDegrees(int)} for
          * additional documentation.
          *
          * <p>If not set, the target rotation will default to the value of
@@ -2717,6 +2790,7 @@
          * @param rotation The rotation of the intended target.
          * @return The current Builder.
          * @see androidx.camera.core.ImageCapture#setTargetRotation(int)
+         * @see androidx.camera.core.ImageCapture#setTargetRotationDegrees(int)
          * @see android.view.OrientationEventListener
          */
         @NonNull
@@ -2729,7 +2803,6 @@
         /**
          * setMirrorMode is not supported on ImageCapture.
          *
-         * @hide
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
index a4ca7f7..72e2efa 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MirrorMode.java
@@ -27,7 +27,6 @@
 /**
  * The mirror mode.
  *
- * @hide
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -48,7 +47,6 @@
     }
 
     /**
-     * @hide
      */
     @IntDef({MIRROR_MODE_OFF, MIRROR_MODE_ON, MIRROR_MODE_FRONT_ON})
     @Retention(RetentionPolicy.SOURCE)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index 35eff5f..862dce9 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -216,7 +216,8 @@
         }
 
         checkMainThread();
-        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
 
         // Close previous session's deferrable surface before creating new one
         clearPipeline();
@@ -285,7 +286,8 @@
         }
 
         // Send the camera Surface to the camera2.
-        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
         addCameraSurfaceAndErrorListener(sessionConfigBuilder, cameraId, config, streamSpec);
         return sessionConfigBuilder;
     }
@@ -645,7 +647,6 @@
 
     /**
      * @inheritDoc
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
@@ -959,7 +960,6 @@
         /**
          * setMirrorMode is not supported on Preview.
          *
-         * @hide
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
index b83f3e2..be20322 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SurfaceOutput.java
@@ -68,7 +68,6 @@
     /**
      * This field indicates the format of the {@link Surface}.
      *
-     * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     @CameraEffect.Formats
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
index 86465a5..362e463 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCase.java
@@ -275,6 +275,24 @@
     }
 
     /**
+     * Converts orientation degrees to {@link Surface} rotation.
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @ImageOutputConfig.RotationValue
+    protected static int orientationDegreesToSurfaceRotation(int degrees) {
+        int degreesWithin360 = within360(degrees);
+        if (degreesWithin360 >= 315 || degreesWithin360 < 45) {
+            return Surface.ROTATION_0;
+        } else if (degreesWithin360 >= 225) {
+            return Surface.ROTATION_90;
+        } else if (degreesWithin360 >= 135) {
+            return Surface.ROTATION_180;
+        } else {
+            return Surface.ROTATION_270;
+        }
+    }
+
+    /**
      * Updates the target rotation of the use case config.
      *
      * @param targetRotation Target rotation of the output image, expressed as one of
@@ -325,7 +343,6 @@
      *
      * <p>If mirror mode is not set, defaults to {@link MirrorMode#MIRROR_MODE_OFF}.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @MirrorMode.Mirror
@@ -336,7 +353,6 @@
     /**
      * Returns if the mirroring is required with the associated camera.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public boolean isMirroringRequired(@NonNull CameraInternal camera) {
@@ -378,7 +394,6 @@
     /**
      * Gets the relative rotation degrees given whether the output should be mirrored.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @IntRange(from = 0, to = 359)
@@ -585,7 +600,6 @@
     /**
      * Offers suggested stream specification for the UseCase.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public void updateSuggestedStreamSpec(@NonNull StreamSpec suggestedStreamSpec) {
@@ -791,7 +805,6 @@
      * Sets the {@link CameraEffect} associated with this use case.
      *
      * @throws IllegalArgumentException if the effect targets are not supported by this use case.
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public void setEffect(@Nullable CameraEffect effect) {
@@ -802,7 +815,6 @@
     /**
      * Gets the {@link CameraEffect} associated with this use case.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Nullable
@@ -915,7 +927,6 @@
      * <p>The method returns an empty set if this {@link UseCase} does not support effects. By
      * default, this method returns an empty set.
      *
-     * @hide
      */
     @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -926,7 +937,6 @@
     /**
      * Returns whether the targets can be applied to this {@link UseCase} or one of its ancestors.
      *
-     * @hide
      * @see #getSupportedEffectTargets()
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
index e82f10e..8b8284c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/UseCaseGroup.java
@@ -23,8 +23,6 @@
 import static androidx.camera.core.processing.TargetUtils.getHumanReadableName;
 import static androidx.core.util.Preconditions.checkArgument;
 
-import static java.util.Objects.requireNonNull;
-
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
@@ -32,10 +30,8 @@
 
 import java.util.ArrayList;
 import java.util.Arrays;
-import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
-import java.util.Map;
 
 /**
  * Represents a collection of {@link UseCase}.
@@ -94,7 +90,8 @@
         private static final List<Integer> SUPPORTED_TARGETS = Arrays.asList(
                 PREVIEW,
                 VIDEO_CAPTURE,
-                IMAGE_CAPTURE);
+                IMAGE_CAPTURE,
+                PREVIEW | VIDEO_CAPTURE);
 
         private ViewPort mViewPort;
         private final List<UseCase> mUseCases;
@@ -118,13 +115,21 @@
         /**
          * Adds a {@link CameraEffect} to the collection.
          *
-         * <p>The value of {@link CameraEffect#getTargets()} must be unique and must be one of
-         * the supported values below:
+         * <p>The value of {@link CameraEffect#getTargets()} must be one of the supported values
+         * below:
          * <ul>
          * <li>{@link CameraEffect#PREVIEW}
+         * <li>{@link CameraEffect#VIDEO_CAPTURE}
          * <li>{@link CameraEffect#IMAGE_CAPTURE}
+         * <li>{@link CameraEffect#VIDEO_CAPTURE} | {@link CameraEffect#PREVIEW}
          * </ul>
          *
+         * <p>The targets must be mutually exclusive of each other, otherwise, the {@link #build()}
+         * method will throw {@link IllegalArgumentException}. For example, it's invalid to have
+         * one {@link CameraEffect} with target {@link CameraEffect#PREVIEW} and another
+         * {@link CameraEffect} with target {@link CameraEffect#PREVIEW} |
+         * {@link CameraEffect#VIDEO_CAPTURE}, since they both target {@link Preview}.
+         *
          * <p>Once added, CameraX will use the {@link CameraEffect}s to process the outputs of
          * the {@link UseCase}s.
          */
@@ -137,23 +142,21 @@
         /**
          * Checks effect targets and throw {@link IllegalArgumentException}.
          *
-         * <p>Throws exception if the effects 1) contains duplicate targets or 2) contains
+         * <p>Throws exception if the effects 1) contains conflicting targets or 2) contains
          * effects that is not in the allowlist.
          */
         private void checkEffectTargets() {
-            Map<Integer, CameraEffect> targetEffectMap = new HashMap<>();
+            int existingTargets = 0;
             for (CameraEffect effect : mEffects) {
                 int targets = effect.getTargets();
                 checkSupportedTargets(SUPPORTED_TARGETS, targets);
-                if (targetEffectMap.containsKey(effect.getTargets())) {
+                int overlappingTargets = existingTargets & targets;
+                if (overlappingTargets > 0) {
                     throw new IllegalArgumentException(String.format(Locale.US,
-                            "Effects %s and %s contain duplicate targets %s.",
-                            requireNonNull(
-                                    targetEffectMap.get(effect.getTargets())).getClass().getName(),
-                            effect.getClass().getName(),
-                            getHumanReadableName(targets)));
+                            "More than one effects has targets %s.",
+                            getHumanReadableName(overlappingTargets)));
                 }
-                targetEffectMap.put(effect.getTargets(), effect);
+                existingTargets |= targets;
             }
         }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java b/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
index 7b37c34..60d6be3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/concurrent/CameraCoordinator.java
@@ -24,6 +24,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.impl.CameraStateRegistry;
 
@@ -38,7 +39,6 @@
  * All camera devices intended to be operated concurrently, must be opened before configuring
  * sessions on any of the camera devices.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 @RequiresApi(21)
@@ -71,19 +71,19 @@
     List<List<CameraSelector>> getConcurrentCameraSelectors();
 
     /**
-     * Gets active concurrent camera selectors.
+     * Gets active concurrent camera infos.
      *
-     * @return list of active concurrent camera selectors.
+     * @return list of active concurrent camera infos.
      */
     @NonNull
-    List<CameraSelector> getActiveConcurrentCameraSelectors();
+    List<CameraInfo> getActiveConcurrentCameraInfos();
 
     /**
-     * Sets active concurrent camera selectors.
+     * Sets active concurrent camera infos.
      *
-     * @param cameraSelectors list of active concurrent camera selectors.
+     * @param cameraInfos list of active concurrent camera infos.
      */
-    void setActiveConcurrentCameraSelectors(@NonNull List<CameraSelector> cameraSelectors);
+    void setActiveConcurrentCameraInfos(@NonNull List<CameraInfo> cameraInfos);
 
     /**
      * Returns paired camera id in concurrent mode.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
index 7f18d0f..fc8a4af 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
@@ -118,8 +118,9 @@
      * Creates a {@link SessionConfig.Builder} for configuring camera.
      */
     @NonNull
-    public SessionConfig.Builder createSessionConfigBuilder() {
-        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mUseCaseConfig);
+    public SessionConfig.Builder createSessionConfigBuilder(@NonNull Size resolution) {
+        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mUseCaseConfig,
+                resolution);
         builder.addNonRepeatingSurface(mPipelineIn.getSurface());
         return builder;
     }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
index 66ff1c9..f7ec5a0 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/CameraInfoInternal.java
@@ -121,6 +121,7 @@
                     throw new IllegalStateException("Unable to find camera with id " + cameraId
                             + " from list of available cameras.");
                 })
+                .addCameraFilter(new LensFacingCameraFilter(getLensFacing()))
                 .build();
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
index 2809cde..8662d08b 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SessionConfig.java
@@ -22,6 +22,7 @@
 import android.hardware.camera2.CaptureRequest;
 import android.hardware.camera2.params.InputConfiguration;
 import android.util.Range;
+import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -318,10 +319,14 @@
         /**
          * Apply the options from the config onto the builder
          *
+         * @param resolution the suggested resolution
          * @param config  the set of options to apply
          * @param builder the builder on which to apply the options
          */
-        void unpack(@NonNull UseCaseConfig<?> config, @NonNull SessionConfig.Builder builder);
+        void unpack(
+                @NonNull Size resolution,
+                @NonNull UseCaseConfig<?> config,
+                @NonNull SessionConfig.Builder builder);
     }
 
     /**
@@ -349,7 +354,9 @@
          * <p>Populates the builder with all the properties defined in the base configuration.
          */
         @NonNull
-        public static Builder createFrom(@NonNull UseCaseConfig<?> config) {
+        public static Builder createFrom(
+                @NonNull UseCaseConfig<?> config,
+                @NonNull Size resolution) {
             OptionUnpacker unpacker = config.getSessionOptionUnpacker(null);
             if (unpacker == null) {
                 throw new IllegalStateException(
@@ -360,7 +367,7 @@
             Builder builder = new Builder();
 
             // Unpack the configuration into this builder
-            unpacker.unpack(config, builder);
+            unpacker.unpack(resolution, config, builder);
             return builder;
         }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
index da96fe7..5eabff9 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/StreamSharing.java
@@ -223,7 +223,8 @@
         mVirtualCamera.setChildrenEdges(outputEdges);
 
         // Send the camera edge Surface to the camera2.
-        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder builder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
         builder.addSurface(mCameraEdge.getDeferrableSurface());
         builder.addRepeatingCameraCaptureCallback(mVirtualCamera.getParentMetadataCallback());
         addCameraErrorListener(builder, cameraId, config, streamSpec);
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/package-info.java b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/package-info.java
index fd92612..eef66e9 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/package-info.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/streamsharing/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.core.streamsharing;
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/DynamicRangeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/DynamicRangeTest.kt
new file mode 100644
index 0000000..c89e49a
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/DynamicRangeTest.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core
+
+import android.os.Build
+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 DynamicRangeTest {
+
+    @Test
+    fun canCreateUnspecifiedDynamicRange() {
+        val dynamicRange = DynamicRange(
+            DynamicRange.FORMAT_HDR_UNSPECIFIED,
+            DynamicRange.BIT_DEPTH_UNSPECIFIED
+        )
+        assertThat(dynamicRange.format).isEqualTo(DynamicRange.FORMAT_HDR_UNSPECIFIED)
+        assertThat(dynamicRange.bitDepth).isEqualTo(DynamicRange.BIT_DEPTH_UNSPECIFIED)
+    }
+
+    @Test
+    fun sdrDynamicRange_is8Bit() {
+        assertThat(DynamicRange.SDR.format).isEqualTo(DynamicRange.FORMAT_SDR)
+        assertThat(DynamicRange.SDR.bitDepth).isEqualTo(DynamicRange.BIT_DEPTH_8_BIT)
+    }
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
index 60482af..30de277 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageAnalysisTest.java
@@ -160,6 +160,23 @@
     }
 
     @Test
+    public void setTargetRotationDegrees() {
+        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
+        imageAnalysis.setTargetRotationDegrees(45);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_270);
+        imageAnalysis.setTargetRotationDegrees(135);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_180);
+        imageAnalysis.setTargetRotationDegrees(225);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_90);
+        imageAnalysis.setTargetRotationDegrees(315);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_0);
+        imageAnalysis.setTargetRotationDegrees(405);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_270);
+        imageAnalysis.setTargetRotationDegrees(-45);
+        assertThat(imageAnalysis.getTargetRotation()).isEqualTo(Surface.ROTATION_0);
+    }
+
+    @Test
     public void defaultMirrorModeIsOff() {
         ImageAnalysis imageAnalysis = new ImageAnalysis.Builder().build();
         assertThat(imageAnalysis.getMirrorModeInternal()).isEqualTo(MIRROR_MODE_OFF);
@@ -492,7 +509,7 @@
                                     height, format, queueDepth, usage);
                             return mFakeImageReaderProxy;
                         })
-                .setSessionOptionUnpacker((config, builder) -> {
+                .setSessionOptionUnpacker((resolution, config, builder) -> {
                 })
                 .setOnePixelShiftEnabled(false)
                 .build();
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 5df4693..de6014c 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
@@ -25,6 +25,7 @@
 import android.os.Looper.getMainLooper
 import android.util.Pair
 import android.util.Rational
+import android.util.Size
 import android.view.Surface
 import androidx.camera.core.CameraEffect.IMAGE_CAPTURE
 import androidx.camera.core.CameraEffect.PREVIEW
@@ -95,6 +96,9 @@
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class ImageCaptureTest {
+
+    private val resolution = Size(640, 480)
+
     private lateinit var callbackHandler: Handler
     private lateinit var callbackThread: HandlerThread
     private lateinit var executor: Executor
@@ -164,6 +168,23 @@
     }
 
     @Test
+    fun setTargetRotationDegrees() {
+        val imageCapture = ImageCapture.Builder().build()
+        imageCapture.setTargetRotationDegrees(45)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_270)
+        imageCapture.setTargetRotationDegrees(135)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_180)
+        imageCapture.setTargetRotationDegrees(225)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_90)
+        imageCapture.setTargetRotationDegrees(315)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_0)
+        imageCapture.setTargetRotationDegrees(405)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_270)
+        imageCapture.setTargetRotationDegrees(-45)
+        assertThat(imageCapture.targetRotation).isEqualTo(Surface.ROTATION_0)
+    }
+
+    @Test
     fun defaultMirrorModeIsOff() {
         val imageCapture = ImageCapture.Builder().build()
         assertThat(imageCapture.mirrorModeInternal).isEqualTo(MIRROR_MODE_OFF)
@@ -247,8 +268,8 @@
 
     private fun assertTakePictureManagerHasTheSameSurface(imageCapture: ImageCapture) {
         val takePictureManagerSurface =
-            imageCapture.takePictureManager.imagePipeline.createSessionConfigBuilder()
-                .build().surfaces.single().surface.get()
+            imageCapture.takePictureManager.imagePipeline.createSessionConfigBuilder(
+                resolution).build().surfaces.single().surface.get()
         val useCaseSurface = imageCapture.sessionConfig.surfaces.single().surface.get()
         assertThat(takePictureManagerSurface).isEqualTo(useCaseSurface)
     }
@@ -696,7 +717,8 @@
             .setCaptureMode(captureMode)
             .setFlashMode(ImageCapture.FLASH_MODE_OFF)
             .setCaptureOptionUnpacker { _: UseCaseConfig<*>?, _: CaptureConfig.Builder? -> }
-            .setSessionOptionUnpacker { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            .setSessionOptionUnpacker { _: Size, _: UseCaseConfig<*>?,
+                _: SessionConfig.Builder? -> }
 
         builder.setBufferFormat(bufferFormat)
         if (imageReaderProxyProvider != null) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 1c0be28..6ef3e65 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -229,7 +229,7 @@
         // Arrange: attach Preview without a SurfaceProvider.
         // Build and bind use case.
         val sessionOptionUnpacker =
-            { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            { _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .setSessionOptionUnpacker(sessionOptionUnpacker)
@@ -511,7 +511,7 @@
     fun setTargetRotation_transformationInfoUpdated() {
         // Arrange: set up preview and verify target rotation in TransformationInfo.
         val sessionOptionUnpacker =
-            { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            { _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .setSessionOptionUnpacker(sessionOptionUnpacker)
@@ -544,7 +544,7 @@
     fun setSurfaceProviderAfterAttachment_receivesSurfaceProviderCallbacks() {
         // Arrange: attach Preview without a SurfaceProvider.
         val sessionOptionUnpacker =
-            { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            { _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .setSessionOptionUnpacker(sessionOptionUnpacker)
@@ -597,7 +597,7 @@
     fun setSurfaceProviderAfterDetach_receivesSurfaceRequestAfterAttach() {
         // Arrange: attach Preview without a SurfaceProvider.
         val sessionOptionUnpacker =
-            { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            { _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .setSessionOptionUnpacker(sessionOptionUnpacker)
@@ -668,7 +668,7 @@
         TransformationInfo> {
         // Arrange.
         val sessionOptionUnpacker =
-            { _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
+            { _: Size, _: UseCaseConfig<*>?, _: SessionConfig.Builder? -> }
         val preview = Preview.Builder()
             .setTargetRotation(Surface.ROTATION_0)
             .setSessionOptionUnpacker(sessionOptionUnpacker)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt
index 2899b75..7250e35 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/UseCaseGroupTest.kt
@@ -17,11 +17,15 @@
 package androidx.camera.core
 
 import android.os.Build
+import androidx.camera.core.CameraEffect.PREVIEW
+import androidx.camera.core.CameraEffect.VIDEO_CAPTURE
 import androidx.camera.core.impl.utils.executor.CameraXExecutors
 import androidx.camera.testing.fakes.FakeSurfaceEffect
-import androidx.camera.testing.fakes.FakeSurfaceProcessor
+import androidx.camera.testing.fakes.FakeSurfaceProcessorInternal
 import androidx.camera.testing.fakes.FakeUseCase
 import com.google.common.truth.Truth.assertThat
+import org.junit.After
+import org.junit.Before
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
@@ -36,30 +40,69 @@
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 class UseCaseGroupTest {
 
+    lateinit var processor: FakeSurfaceProcessorInternal
+
+    @Before
+    fun setUp() {
+        processor = FakeSurfaceProcessorInternal(CameraXExecutors.mainThreadExecutor())
+    }
+
+    @After
+    fun tearDown() {
+        processor.cleanUp()
+    }
+
     @Test
-    fun duplicateTargets_throwsException() {
+    fun setMutuallyExclusiveEffectsTargets_effectsSet() {
         // Arrange.
         val previewEffect = FakeSurfaceEffect(
-            CameraXExecutors.mainThreadExecutor(),
-            FakeSurfaceProcessor(CameraXExecutors.mainThreadExecutor())
+            PREVIEW,
+            processor
         )
-        val builder = UseCaseGroup.Builder().addUseCase(FakeUseCase())
-            .addEffect(previewEffect)
-            .addEffect(previewEffect)
+        val videoEffect = FakeSurfaceEffect(
+            VIDEO_CAPTURE,
+            processor
+        )
 
         // Act.
+        val useCaseGroup = UseCaseGroup.Builder().addUseCase(FakeUseCase())
+            .addEffect(previewEffect)
+            .addEffect(videoEffect)
+            .build()
+
+        // Assert.
+        assertThat(useCaseGroup.effects).containsExactly(previewEffect, videoEffect)
+    }
+
+    @Test
+    fun setConflictingEffectTargets_throwsException() {
+        // Arrange.
+        val previewEffect = FakeSurfaceEffect(
+            PREVIEW,
+            processor
+        )
+        val previewVideoEffect = FakeSurfaceEffect(
+            PREVIEW or VIDEO_CAPTURE,
+            processor
+        )
+        // Act.
+        val errorMessage = buildAndGetErrorMessage(
+            UseCaseGroup.Builder().addUseCase(FakeUseCase())
+                .addEffect(previewEffect)
+                .addEffect(previewVideoEffect)
+        )
+
+        // Assert.
+        assertThat(errorMessage).isEqualTo("More than one effects has targets PREVIEW.")
+    }
+
+    private fun buildAndGetErrorMessage(builder: UseCaseGroup.Builder): String? {
         var message: String? = null
         try {
             builder.build()
         } catch (e: IllegalArgumentException) {
             message = e.message
         }
-
-        // Assert.
-        assertThat(message).isEqualTo(
-            "Effects androidx.camera.testing.fakes.FakeSurfaceEffect " +
-                "and androidx.camera.testing.fakes.FakeSurfaceEffect " +
-                "contain duplicate targets PREVIEW."
-        )
+        return message
     }
 }
\ No newline at end of file
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionMode.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionMode.java
index bdafc4f..39edfa3 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionMode.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/ExtensionMode.java
@@ -65,7 +65,6 @@
      * not a specific Camera supports an extension mode use
      * {@link ExtensionsManager#isExtensionAvailable(CameraSelector, int)}.
      *
-     * @hide
      */
     @IntDef({NONE, BOKEH, HDR, NIGHT, FACE_RETOUCH, AUTO})
     @Retention(RetentionPolicy.SOURCE)
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/package-info.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/package-info.java
index b3ced1b..7ba5870 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/package-info.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.extensions.internal.compat.quirk;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/package-info.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/package-info.java
index cc91045..5613f74 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/package-info.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.extensions.internal.compat.workaround;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/package-info.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/package-info.java
index 878eaaf..42ce603 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/package-info.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.extensions.internal;
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/package-info.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/package-info.java
index b62b600..ded39ceb 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/package-info.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/package-info.java
@@ -16,7 +16,6 @@
 
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.extensions.internal.sessionprocessor;
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 1bf7e70..fdeb96f5 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
@@ -20,6 +20,7 @@
 import android.content.Context
 import android.content.ContextWrapper
 import android.content.pm.PackageManager
+import android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT
 import androidx.annotation.OptIn
 import androidx.annotation.RequiresApi
 import androidx.camera.core.CameraSelector
@@ -87,7 +88,7 @@
             mainThreadExecutor(),
             surfaceProcessor
         )
-        val preview = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+        val preview = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
         val useCaseGroup = UseCaseGroup.Builder().addUseCase(preview).addEffect(effect).build()
 
         runBlocking(MainScope().coroutineContext) {
@@ -215,7 +216,7 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA,
@@ -234,8 +235,8 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA,
@@ -263,7 +264,7 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0,
@@ -285,8 +286,8 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA,
@@ -310,7 +311,7 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA, useCase
@@ -331,8 +332,8 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(
                 lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA, useCase0, useCase1
@@ -351,13 +352,13 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             val camera0 = provider.bindToLifecycle(
                 lifecycleOwner0,
                 CameraSelector.DEFAULT_BACK_CAMERA, useCase0
             )
 
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             val camera1 = provider.bindToLifecycle(
                 lifecycleOwner1,
                 CameraSelector.DEFAULT_BACK_CAMERA, useCase1
@@ -390,8 +391,8 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val camera0 = provider.bindToLifecycle(
                 lifecycleOwner0,
@@ -418,8 +419,8 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             provider.bindToLifecycle(lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA, useCase0)
 
@@ -441,8 +442,8 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val camera0 = provider.bindToLifecycle(
                 lifecycleOwner0,
@@ -494,7 +495,7 @@
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
 
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             // The front camera is not defined, we should get the IllegalArgumentException when it
             // tries to get the camera.
@@ -542,7 +543,7 @@
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             val camera: LifecycleCamera =
                 provider.bindToLifecycle(
                     lifecycleOwner0, CameraSelector.DEFAULT_BACK_CAMERA,
@@ -559,7 +560,7 @@
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             lifecycleOwner0.startAndResume()
             val camera: LifecycleCamera =
                 provider.bindToLifecycle(
@@ -576,7 +577,7 @@
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             lifecycleOwner0.startAndResume()
             val camera: LifecycleCamera =
                 provider.bindToLifecycle(
@@ -595,7 +596,7 @@
         ProcessCameraProvider.configureInstance(FakeAppConfig.create())
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
             lifecycleOwner0.startAndResume()
             val camera: LifecycleCamera =
                 provider.bindToLifecycle(
@@ -673,8 +674,8 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val singleCameraConfig0 = SingleCameraConfig.Builder()
                 .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA)
@@ -695,13 +696,19 @@
                 .setCameraConfigs(listOf(singleCameraConfig0, singleCameraConfig1))
                 .build()
 
-            val concurrentCamera = provider.bindToLifecycle(concurrentCameraConfig)
+            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                val concurrentCamera = provider.bindToLifecycle(concurrentCameraConfig)
 
-            assertThat(concurrentCamera).isNotNull()
-            assertThat(concurrentCamera.cameras.size).isEqualTo(2)
-            assertThat(provider.isBound(useCase0)).isTrue()
-            assertThat(provider.isBound(useCase1)).isTrue()
-            assertThat(provider.isConcurrentCameraModeOn).isTrue()
+                assertThat(concurrentCamera).isNotNull()
+                assertThat(concurrentCamera.cameras.size).isEqualTo(2)
+                assertThat(provider.isBound(useCase0)).isTrue()
+                assertThat(provider.isBound(useCase1)).isTrue()
+                assertThat(provider.isConcurrentCameraModeOn).isTrue()
+            } else {
+                assertThrows<UnsupportedOperationException> {
+                    provider.bindToLifecycle(concurrentCameraConfig)
+                }
+            }
         }
     }
 
@@ -711,9 +718,9 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase2 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase2 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val singleCameraConfig0 = SingleCameraConfig.Builder()
                 .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA)
@@ -742,25 +749,37 @@
                 .setCameraConfigs(listOf(singleCameraConfig0, singleCameraConfig1))
                 .build()
 
-            val concurrentCamera0 = provider.bindToLifecycle(concurrentCameraConfig0)
+            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                val concurrentCamera0 = provider.bindToLifecycle(concurrentCameraConfig0)
 
-            assertThat(concurrentCamera0).isNotNull()
-            assertThat(concurrentCamera0.cameras.size).isEqualTo(2)
-            assertThat(provider.isBound(useCase0)).isTrue()
-            assertThat(provider.isBound(useCase1)).isTrue()
-            assertThat(provider.isConcurrentCameraModeOn).isTrue()
+                assertThat(concurrentCamera0).isNotNull()
+                assertThat(concurrentCamera0.cameras.size).isEqualTo(2)
+                assertThat(provider.isBound(useCase0)).isTrue()
+                assertThat(provider.isBound(useCase1)).isTrue()
+                assertThat(provider.isConcurrentCameraModeOn).isTrue()
+            } else {
+                assertThrows<UnsupportedOperationException> {
+                    provider.bindToLifecycle(concurrentCameraConfig0)
+                }
+            }
 
             val concurrentCameraConfig1 = ConcurrentCameraConfig.Builder()
                 .setCameraConfigs(listOf(singleCameraConfig0, singleCameraConfig2))
                 .build()
 
-            val concurrentCamera1 = provider.bindToLifecycle(concurrentCameraConfig1)
+            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                val concurrentCamera1 = provider.bindToLifecycle(concurrentCameraConfig1)
 
-            assertThat(concurrentCamera1).isNotNull()
-            assertThat(concurrentCamera1.cameras.size).isEqualTo(2)
-            assertThat(provider.isBound(useCase0)).isTrue()
-            assertThat(provider.isBound(useCase2)).isTrue()
-            assertThat(provider.isConcurrentCameraModeOn).isTrue()
+                assertThat(concurrentCamera1).isNotNull()
+                assertThat(concurrentCamera1.cameras.size).isEqualTo(2)
+                assertThat(provider.isBound(useCase0)).isTrue()
+                assertThat(provider.isBound(useCase2)).isTrue()
+                assertThat(provider.isConcurrentCameraModeOn).isTrue()
+            } else {
+                assertThrows<UnsupportedOperationException> {
+                    provider.bindToLifecycle(concurrentCameraConfig0)
+                }
+            }
         }
     }
 
@@ -770,7 +789,7 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val singleCameraConfig0 = SingleCameraConfig.Builder()
                 .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA)
@@ -784,10 +803,16 @@
                 .setCameraConfigs(listOf(singleCameraConfig0))
                 .build()
 
-            assertThrows<IllegalArgumentException> {
-                provider.bindToLifecycle(concurrentCameraConfig)
+            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                assertThrows<IllegalArgumentException> {
+                    provider.bindToLifecycle(concurrentCameraConfig)
+                }
+                assertThat(provider.isConcurrentCameraModeOn).isFalse()
+            } else {
+                assertThrows<UnsupportedOperationException> {
+                    provider.bindToLifecycle(concurrentCameraConfig)
+                }
             }
-            assertThat(provider.isConcurrentCameraModeOn).isFalse()
         }
     }
 
@@ -797,8 +822,8 @@
 
         runBlocking(MainScope().coroutineContext) {
             provider = ProcessCameraProvider.getInstance(context).await()
-            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
-            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _ -> }.build()
+            val useCase0 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
+            val useCase1 = Preview.Builder().setSessionOptionUnpacker { _, _, _ -> }.build()
 
             val singleCameraConfig0 = SingleCameraConfig.Builder()
                 .setCameraSelector(CameraSelector.DEFAULT_BACK_CAMERA)
@@ -830,10 +855,16 @@
                     singleCameraConfig2))
                 .build()
 
-            assertThrows<UnsupportedOperationException> {
-                provider.bindToLifecycle(concurrentCameraConfig)
+            if (context.packageManager.hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+                assertThrows<IllegalArgumentException> {
+                    provider.bindToLifecycle(concurrentCameraConfig)
+                }
+                assertThat(provider.isConcurrentCameraModeOn).isFalse()
+            } else {
+                assertThrows<java.lang.UnsupportedOperationException> {
+                    provider.bindToLifecycle(concurrentCameraConfig)
+                }
             }
-            assertThat(provider.isConcurrentCameraModeOn).isFalse()
         }
     }
 
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 d7c7640..d56893e 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
@@ -16,6 +16,8 @@
 
 package androidx.camera.lifecycle;
 
+import static android.content.pm.PackageManager.FEATURE_CAMERA_CONCURRENT;
+
 import static androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_CONCURRENT;
 import static androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_SINGLE;
 import static androidx.camera.core.concurrent.CameraCoordinator.CAMERA_OPERATING_MODE_UNSPECIFIED;
@@ -26,6 +28,7 @@
 
 import android.app.Application;
 import android.content.Context;
+import android.content.pm.PackageManager;
 import android.os.Handler;
 
 import androidx.annotation.GuardedBy;
@@ -415,14 +418,16 @@
      *
      * <p>The concurrent camera is only supporting two cameras currently. If the input
      * {@link ConcurrentCameraConfig} has less or more than two {@link SingleCameraConfig},
-     * {@link IllegalArgumentException} will be thrown.
+     * {@link IllegalArgumentException} will be thrown. If the device is not supporting
+     * {@link PackageManager#FEATURE_CAMERA_CONCURRENT} or cameras are already used by other
+     * {@link UseCase}s, {@link UnsupportedOperationException} will be thrown.
      *
      * @param concurrentCameraConfig input configuration for concurrent camera.
      * @return output concurrent camera instance.
      *
-     * @throws IllegalArgumentException If less than two camera configs are provided.
-     * @throws UnsupportedOperationException If more than two camera configs are provides or
-     * there is single camera already running.
+     * @throws IllegalArgumentException If less or more than two camera configs are provided.
+     * @throws UnsupportedOperationException If device is not supporting concurrent camera or
+     * cameras are already used by other {@link UseCase}s.
      *
      * @hide
      */
@@ -432,13 +437,9 @@
     @NonNull
     public ConcurrentCamera bindToLifecycle(
             @NonNull ConcurrentCameraConfig concurrentCameraConfig) {
-        if (concurrentCameraConfig.getSingleCameraConfigs().size() < 2) {
-            throw new IllegalArgumentException("Concurrent camera needs two camera configs");
-        }
-
-        if (concurrentCameraConfig.getSingleCameraConfigs().size() > 2) {
-            throw new UnsupportedOperationException("Concurrent camera is only supporting two  "
-                    + "cameras at maximum.");
+        if (!mContext.getPackageManager().hasSystemFeature(FEATURE_CAMERA_CONCURRENT)) {
+            throw new UnsupportedOperationException("Concurrent camera is not supported on the "
+                    + "device");
         }
 
         if (getCameraOperatingMode() == CAMERA_OPERATING_MODE_SINGLE) {
@@ -446,13 +447,30 @@
                     + "unbindAll() before binding more cameras");
         }
 
-        List<CameraSelector> cameraSelectorsToBind = new ArrayList<>();
-        cameraSelectorsToBind.add(
-                concurrentCameraConfig.getSingleCameraConfigs().get(0).getCameraSelector());
-        cameraSelectorsToBind.add(
-                concurrentCameraConfig.getSingleCameraConfigs().get(1).getCameraSelector());
-        if (!getActiveConcurrentCameraSelectors().isEmpty()
-                && !cameraSelectorsToBind.equals(getActiveConcurrentCameraSelectors())) {
+        if (concurrentCameraConfig.getSingleCameraConfigs().size() < 2) {
+            throw new IllegalArgumentException("Concurrent camera needs two camera configs");
+        }
+
+        if (concurrentCameraConfig.getSingleCameraConfigs().size() > 2) {
+            throw new IllegalArgumentException("Concurrent camera is only supporting two  "
+                    + "cameras at maximum.");
+        }
+
+        List<CameraInfo> cameraInfosToBind = new ArrayList<>();
+        List<CameraInfo> availableCameraInfos = getAvailableCameraInfos();
+        CameraInfo firstCameraInfo = getCameraInfoFromCameraSelector(
+                concurrentCameraConfig.getSingleCameraConfigs().get(0).getCameraSelector(),
+                availableCameraInfos);
+        CameraInfo secondCameraInfo = getCameraInfoFromCameraSelector(
+                concurrentCameraConfig.getSingleCameraConfigs().get(1).getCameraSelector(),
+                availableCameraInfos);
+        if (firstCameraInfo == null || secondCameraInfo == null) {
+            throw new IllegalArgumentException("Invalid camera selectors in camera configs");
+        }
+        cameraInfosToBind.add(firstCameraInfo);
+        cameraInfosToBind.add(secondCameraInfo);
+        if (!getActiveConcurrentCameraInfos().isEmpty()
+                && !cameraInfosToBind.equals(getActiveConcurrentCameraInfos())) {
             throw new UnsupportedOperationException("Cameras are already running, call "
                     + "unbindAll() before binding more cameras");
         }
@@ -468,7 +486,7 @@
                     config.getUseCaseGroup().getUseCases().toArray(new UseCase[0]));
             cameras.add(camera);
         }
-        setActiveConcurrentCameraSelectors(cameraSelectorsToBind);
+        setActiveConcurrentCameraInfos(cameraInfosToBind);
         return new ConcurrentCamera.Builder()
                 .setCameras(cameras)
                 .builder();
@@ -753,12 +771,10 @@
         for (final List<CameraSelector> cameraSelectors : concurrentCameraSelectorLists) {
             List<CameraInfo> cameraInfos = new ArrayList<>();
             for (CameraSelector cameraSelector : cameraSelectors) {
-                for (CameraInfo cameraInfo : availableCameraInfos) {
-                    if (cameraSelector.getLensFacing()
-                            == cameraInfo.getLensFacing()) {
-                        cameraInfos.add(cameraInfo);
-                        break;
-                    }
+                CameraInfo cameraInfo = getCameraInfoFromCameraSelector(cameraSelector,
+                        availableCameraInfos);
+                if (cameraInfo != null) {
+                    cameraInfos.add(cameraInfo);
                 }
             }
             availableConcurrentCameraInfos.add(cameraInfos);
@@ -794,20 +810,28 @@
     }
 
     @NonNull
-    private List<CameraSelector> getActiveConcurrentCameraSelectors() {
+    private List<CameraInfo> getActiveConcurrentCameraInfos() {
         if (mCameraX == null) {
             return new ArrayList<>();
         }
         return mCameraX.getCameraFactory().getCameraCoordinator()
-                .getActiveConcurrentCameraSelectors();
+                .getActiveConcurrentCameraInfos();
     }
 
-    private void setActiveConcurrentCameraSelectors(@NonNull List<CameraSelector> cameraSelectors) {
+    private void setActiveConcurrentCameraInfos(@NonNull List<CameraInfo> cameraInfos) {
         if (mCameraX == null) {
             return;
         }
         mCameraX.getCameraFactory().getCameraCoordinator()
-                .setActiveConcurrentCameraSelectors(cameraSelectors);
+                .setActiveConcurrentCameraInfos(cameraInfos);
+    }
+
+    @Nullable
+    private CameraInfo getCameraInfoFromCameraSelector(
+            @NonNull CameraSelector cameraSelector,
+            @NonNull List<CameraInfo> availableCameraInfos) {
+        List<CameraInfo> cameraInfos = cameraSelector.filter(availableCameraInfos);
+        return cameraInfos.isEmpty() ? null : cameraInfos.get(0);
     }
 
     private ProcessCameraProvider() {
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
index 5688338..a87cc14 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/CameraUtil.java
@@ -590,7 +590,6 @@
      *
      * @param context        The context used to initialize CameraX
      * @param cameraSelector The selector to select cameras with.
-     * @hide
      */
     @RestrictTo(RestrictTo.Scope.TESTS)
     @NonNull
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
index 128c998..3281ec1 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeCameraCoordinator.java
@@ -20,6 +20,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
+import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.concurrent.CameraCoordinator;
 
@@ -32,7 +33,6 @@
  * A {@link CameraCoordinator} implementation that contains concurrent camera mode and camera id
  * information.
  *
- * @hide
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -41,7 +41,7 @@
     @NonNull private Map<String, String> mConcurrentCameraIdMap;
     @NonNull private List<List<String>> mConcurrentCameraIds;
     @NonNull private List<List<CameraSelector>> mConcurrentCameraSelectors;
-    @NonNull private List<CameraSelector> mActiveConcurrentCameraSelectors;
+    @NonNull private List<CameraInfo> mActiveConcurrentCameraInfos;
     @NonNull private final List<ConcurrentCameraModeListener> mConcurrentCameraModeListeners;
 
     @CameraOperatingMode private int mCameraOperatingMode;
@@ -50,7 +50,7 @@
         mConcurrentCameraIdMap = new HashMap<>();
         mConcurrentCameraIds = new ArrayList<>();
         mConcurrentCameraSelectors = new ArrayList<>();
-        mActiveConcurrentCameraSelectors = new ArrayList<>();
+        mActiveConcurrentCameraInfos = new ArrayList<>();
         mConcurrentCameraModeListeners = new ArrayList<>();
     }
 
@@ -77,15 +77,15 @@
         return mConcurrentCameraSelectors;
     }
 
-    @Override
-    public void setActiveConcurrentCameraSelectors(@NonNull List<CameraSelector> cameraSelectors) {
-        mActiveConcurrentCameraSelectors = cameraSelectors;
-    }
-
     @NonNull
     @Override
-    public List<CameraSelector> getActiveConcurrentCameraSelectors() {
-        return mActiveConcurrentCameraSelectors;
+    public List<CameraInfo> getActiveConcurrentCameraInfos() {
+        return mActiveConcurrentCameraInfos;
+    }
+
+    @Override
+    public void setActiveConcurrentCameraInfos(@NonNull List<CameraInfo> cameraInfos) {
+        mActiveConcurrentCameraInfos = cameraInfos;
     }
 
     @Nullable
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCase.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCase.java
index 583f6a6..6b5abe0 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCase.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCase.java
@@ -85,7 +85,7 @@
     @Override
     public UseCaseConfig.Builder<?, ?, ?> getUseCaseConfigBuilder(@NonNull Config config) {
         return new FakeUseCaseConfig.Builder(config)
-                .setSessionOptionUnpacker((useCaseConfig, sessionConfigBuilder) -> {
+                .setSessionOptionUnpacker((resolution, useCaseConfig, sessionConfigBuilder) -> {
                 });
     }
 
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfigFactory.java b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfigFactory.java
index 8073a00..ce4b5f5 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfigFactory.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/fakes/FakeUseCaseConfigFactory.java
@@ -52,7 +52,8 @@
         MutableOptionsBundle mutableConfig = MutableOptionsBundle.create();
 
         mutableConfig.insertOption(OPTION_CAPTURE_CONFIG_UNPACKER, (config, builder) -> {});
-        mutableConfig.insertOption(OPTION_SESSION_CONFIG_UNPACKER, (config, builder) -> {});
+        mutableConfig.insertOption(OPTION_SESSION_CONFIG_UNPACKER,
+                (resolution, config, builder) -> {});
 
         return OptionsBundle.from(mutableConfig);
     }
diff --git a/camera/camera-video/api/current.txt b/camera/camera-video/api/current.txt
index fe467cc..208d9cd 100644
--- a/camera/camera-video/api/current.txt
+++ b/camera/camera-video/api/current.txt
@@ -133,9 +133,18 @@
 
   @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
     method public T getOutput();
+    method public int getTargetRotation();
+    method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
   }
 
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+    ctor public VideoCapture.Builder(T);
+    method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+  }
+
   @RequiresApi(21) public interface VideoOutput {
     method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
   }
diff --git a/camera/camera-video/api/public_plus_experimental_current.txt b/camera/camera-video/api/public_plus_experimental_current.txt
index fe467cc..208d9cd 100644
--- a/camera/camera-video/api/public_plus_experimental_current.txt
+++ b/camera/camera-video/api/public_plus_experimental_current.txt
@@ -133,9 +133,18 @@
 
   @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
     method public T getOutput();
+    method public int getTargetRotation();
+    method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
   }
 
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+    ctor public VideoCapture.Builder(T);
+    method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+  }
+
   @RequiresApi(21) public interface VideoOutput {
     method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
   }
diff --git a/camera/camera-video/api/restricted_current.txt b/camera/camera-video/api/restricted_current.txt
index fe467cc..208d9cd 100644
--- a/camera/camera-video/api/restricted_current.txt
+++ b/camera/camera-video/api/restricted_current.txt
@@ -133,9 +133,18 @@
 
   @RequiresApi(21) public final class VideoCapture<T extends androidx.camera.video.VideoOutput> extends androidx.camera.core.UseCase {
     method public T getOutput();
+    method public int getTargetRotation();
+    method public void setTargetRotation(int);
+    method public void setTargetRotationDegrees(int);
     method public static <T extends androidx.camera.video.VideoOutput> androidx.camera.video.VideoCapture<T!> withOutput(T);
   }
 
+  @RequiresApi(21) public static final class VideoCapture.Builder<T extends androidx.camera.video.VideoOutput> implements androidx.camera.core.ExtendableBuilder<androidx.camera.video.VideoCapture> {
+    ctor public VideoCapture.Builder(T);
+    method public androidx.camera.video.VideoCapture<T!> build();
+    method public androidx.camera.video.VideoCapture.Builder<T!> setTargetRotation(int);
+  }
+
   @RequiresApi(21) public interface VideoOutput {
     method public void onSurfaceRequested(androidx.camera.core.SurfaceRequest);
   }
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index 2296d9f..7dc5a90 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -159,7 +159,8 @@
     private static final String SURFACE_UPDATE_KEY =
             "androidx.camera.video.VideoCapture.streamUpdate";
     private static final Defaults DEFAULT_CONFIG = new Defaults();
-    private static final boolean ENABLE_SURFACE_PROCESSING_BY_QUIRK;
+    @VisibleForTesting
+    static boolean sEnableSurfaceProcessingByQuirk;
     private static final boolean USE_TEMPLATE_PREVIEW_BY_QUIRK;
 
     static {
@@ -175,7 +176,7 @@
                 DeviceQuirks.get(ExtraSupportedResolutionQuirk.class) != null;
         USE_TEMPLATE_PREVIEW_BY_QUIRK =
                 hasPreviewStretchQuirk || hasPreviewDelayQuirk || hasImageCaptureFailedQuirk;
-        ENABLE_SURFACE_PROCESSING_BY_QUIRK =
+        sEnableSurfaceProcessingByQuirk =
                 hasPreviewDelayQuirk || hasImageCaptureFailedQuirk
                         || hasVideoQualityQuirkAndWorkaroundBySurfaceProcessing
                         || hasExtraSupportedResolutionQuirk;
@@ -209,7 +210,7 @@
      */
     @NonNull
     public static <T extends VideoOutput> VideoCapture<T> withOutput(@NonNull T videoOutput) {
-        return new VideoCapture.Builder<T>(Preconditions.checkNotNull(videoOutput)).build();
+        return new VideoCapture.Builder<>(Preconditions.checkNotNull(videoOutput)).build();
     }
 
     /**
@@ -236,14 +237,16 @@
     /**
      * Returns the desired rotation of the output video.
      *
-     * <p>The rotation can be set by calling {@link VideoCapture#setTargetRotation(int)}. If not
-     * set, the target rotation defaults to the value of {@link Display#getRotation()} of the
-     * default display at the time the use case is created. The use case is fully created once it
-     * has been attached to a camera.
+     * <p>The rotation can be set prior to constructing a VideoCapture using
+     * {@link VideoCapture.Builder#setTargetRotation(int)} or dynamically by calling
+     * {@link VideoCapture#setTargetRotation(int)} or {@link #setTargetRotationDegrees(int)}.
+     * If not set, the target rotation defaults to the value of {@link Display#getRotation()} of
+     * the default display at the time the use case is bound.
      *
      * @return The rotation of the intended target.
+     * @see VideoCapture#setTargetRotation(int)
+     * @see VideoCapture#setTargetRotationDegrees(int)
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
     @RotationValue
     public int getTargetRotation() {
         return getTargetRotationInternal();
@@ -252,23 +255,117 @@
     /**
      * Sets the desired rotation of the output video.
      *
-     * <p>This is one of four valid values: {@link Surface#ROTATION_0},
-     * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+     * <p>Valid values include: {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
      * Rotation values are relative to the "natural" rotation, {@link Surface#ROTATION_0}.
      *
-     * <p>If not set, the target rotation will default to the value of
-     * {@link Display#getRotation()} of the default display at the time the use case is
-     * created. The use case is fully created once it has been attached to a camera.
+     * <p>While rotation can also be set via {@link Builder#setTargetRotation(int)}, using
+     * {@code setTargetRotation(int)} allows the target rotation to be set dynamically.
      *
-     * @param rotation Desired rotation of the output video.
+     * <p>In general, it is best to use an {@link android.view.OrientationEventListener} to set
+     * the target rotation. This way, the rotation output will indicate which way is down for a
+     * given video. This is important since display orientation may be locked by device default,
+     * user setting, or app configuration, and some devices may not transition to a
+     * reverse-portrait display orientation. In these cases, use
+     * {@link #setTargetRotationDegrees} to set target rotation dynamically according to the
+     * {@link android.view.OrientationEventListener}, without re-creating the use case.
+     * See {@link #setTargetRotationDegrees} for more information.
+     *
+     * <p>If not set, the target rotation will default to the value of
+     * {@link Display#getRotation()} of the default display at the time the use case is bound. To
+     * return to the default value, set the value to
+     * <pre>{@code
+     * context.getSystemService(WindowManager.class).getDefaultDisplay().getRotation();
+     * }</pre>
+     *
+     * <p>For a {@link Recorder} output, calling this method has no effect on the ongoing
+     * recording, but will affect recordings started after calling this method. The final
+     * rotation degrees of the video, including the degrees set by this method and the orientation
+     * of the camera sensor, will be reflected by several possibilities, 1) the rotation degrees is
+     * written into the video metadata, 2) the video content is directly rotated, 3) both, i.e.
+     * rotation metadata and rotated video content which combines to the target rotation. CameraX
+     * will choose a strategy according to the use case.
+     *
+     * @param rotation Desired rotation of the output video, expressed as one of
+     *                 {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+     *                 {@link Surface#ROTATION_180}, or {@link Surface#ROTATION_270}.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
     public void setTargetRotation(@RotationValue int rotation) {
         if (setTargetRotationInternal(rotation)) {
             sendTransformationInfoIfReady();
         }
     }
 
+    /**
+     * Sets the desired rotation of the output video in degrees.
+     *
+     * <p>In general, it is best to use an {@link  android.view.OrientationEventListener} to set
+     * the target rotation. This way, the rotation output will indicate which way is down for a
+     * given video. This is important since display orientation may be locked by device default,
+     * user setting, or app configuration, and some devices may not transition to a
+     * reverse-portrait display orientation. In these cases, use
+     * {@code setTargetRotationDegrees()} to set target rotation dynamically according
+     * to the {@link  android.view.OrientationEventListener}, without re-creating the use case.
+     * The sample code is as below:
+     * <pre>{@code
+     * public class CameraXActivity extends AppCompatActivity {
+     *
+     *     private OrientationEventListener mOrientationEventListener;
+     *
+     *     @Override
+     *     protected void onStart() {
+     *         super.onStart();
+     *         if (mOrientationEventListener == null) {
+     *             mOrientationEventListener = new OrientationEventListener(this) {
+     *                 @Override
+     *                 public void onOrientationChanged(int orientation) {
+     *                     if (orientation == OrientationEventListener.ORIENTATION_UNKNOWN) {
+     *                         return;
+     *                     }
+     *                     mVideoCapture.setTargetRotationDegrees(orientation);
+     *                 }
+     *             };
+     *         }
+     *         mOrientationEventListener.enable();
+     *     }
+     *
+     *     @Override
+     *     protected void onStop() {
+     *         super.onStop();
+     *         mOrientationEventListener.disable();
+     *     }
+     * }
+     * }</pre>
+     *
+     * <p>{@code setTargetRotationDegrees()} cannot rotate the camera image to an arbitrary angle,
+     * instead it maps the angle to one of {@link Surface#ROTATION_0},
+     * {@link Surface#ROTATION_90}, {@link Surface#ROTATION_180} and {@link Surface#ROTATION_270}
+     * as the input of {@link #setTargetRotation(int)}. The rule is as follows:
+     * <p>If the input degrees is not in the range [0..359], it will be converted to the equivalent
+     * degrees in the range [0..359]. And then take the following mapping based on the input
+     * degrees.
+     * <p>degrees >= 315 || degrees < 45 -> {@link Surface#ROTATION_0}
+     * <p>degrees >= 225 && degrees < 315 -> {@link Surface#ROTATION_90}
+     * <p>degrees >= 135 && degrees < 225 -> {@link Surface#ROTATION_180}
+     * <p>degrees >= 45 && degrees < 135 -> {@link Surface#ROTATION_270}
+     * <p>The rotation value can be obtained by {@link #getTargetRotation()}. This means the
+     * rotation previously set by {@link #setTargetRotation(int)} will be overridden by
+     * {@code setTargetRotationDegrees(int)}, and vice versa.
+     *
+     * <p>For a {@link Recorder} output, calling this method has no effect on the ongoing
+     * recording, but will affect recordings started after calling this method. The final
+     * rotation degrees of the video, including the degrees set by this method and the orientation
+     * of the camera sensor, will be reflected by several possibilities, 1) the rotation degrees is
+     * written into the video metadata, 2) the video content is directly rotated, 3) both, i.e.
+     * rotation metadata and rotated video content which combines to the target rotation. CameraX
+     * will choose a strategy according to the use case.
+     *
+     * @param degrees Desired rotation degree of the output video.
+     */
+    public void setTargetRotationDegrees(int degrees) {
+        setTargetRotation(orientationDegreesToSurfaceRotation(degrees));
+    }
+
     // TODO: to public API
     /**
      * Returns the mirror mode.
@@ -278,7 +375,6 @@
      *
      * @return The mirror mode of the intended target.
      *
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @MirrorMode.Mirror
@@ -288,7 +384,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @SuppressWarnings("unchecked")
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -314,7 +409,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @Override
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -325,7 +419,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
@@ -352,7 +445,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
@@ -373,7 +465,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
@@ -388,7 +479,6 @@
 
     /**
      * {@inheritDoc}
-     *
      */
     @NonNull
     @RestrictTo(Scope.LIBRARY_GROUP)
@@ -468,8 +558,7 @@
         VideoEncoderInfo videoEncoderInfo = getVideoEncoderInfo(config.getVideoEncoderInfoFinder(),
                 videoCapabilities, mediaSpec, resolution, targetFpsRange);
         mCropRect = calculateCropRect(resolution, videoEncoderInfo);
-        boolean shouldMirror = camera.getHasTransform() && isMirroringRequired(camera);
-        mNode = createNodeIfNeeded(isCropNeeded(mCropRect, resolution), shouldMirror);
+        mNode = createNodeIfNeeded(camera, mCropRect, resolution);
         // Choose Timebase based on the whether the buffer is copied.
         Timebase timebase;
         if (mNode != null || !camera.getHasTransform()) {
@@ -493,7 +582,7 @@
                     camera.getHasTransform(),
                     mCropRect,
                     getRelativeRotation(camera, isMirroringRequired(camera)),
-                    shouldMirror);
+                    shouldMirror(camera));
             cameraEdge.addOnInvalidatedListener(onSurfaceInvalidated);
             mCameraEdge = cameraEdge;
             SurfaceProcessorNode.OutConfig outConfig =
@@ -527,7 +616,8 @@
         // MediaCodec class instead.
         mDeferrableSurface.setContainerClass(MediaCodec.class);
 
-        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config);
+        SessionConfig.Builder sessionConfigBuilder = SessionConfig.Builder.createFrom(config,
+                streamSpec.getResolution());
         sessionConfigBuilder.setExpectedFrameRateRange(streamSpec.getExpectedFrameRateRange());
         sessionConfigBuilder.addErrorListener(
                 (sessionConfig, error) -> resetPipeline(cameraId, config, streamSpec));
@@ -592,7 +682,6 @@
     }
 
     /**
-     * @hide
      */
     @Nullable
     @RestrictTo(Scope.TESTS)
@@ -605,7 +694,6 @@
      *
      * <p>These values may be overridden by the implementation. They only provide a minimum set of
      * defaults that are implementation independent.
-     *
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     public static final class Defaults implements ConfigProvider<VideoCaptureConfig<?>> {
@@ -724,9 +812,13 @@
     }
 
     @Nullable
-    private SurfaceProcessorNode createNodeIfNeeded(boolean isCropNeeded, boolean mirroring) {
-        if (getEffect() != null || ENABLE_SURFACE_PROCESSING_BY_QUIRK || isCropNeeded
-                || mirroring) {
+    private SurfaceProcessorNode createNodeIfNeeded(@NonNull CameraInternal camera,
+            @NonNull Rect cropRect,
+            @NonNull Size resolution) {
+        if (getEffect() != null
+                || shouldEnableSurfaceProcessingByQuirk(camera)
+                || shouldCrop(cropRect, resolution)
+                || shouldMirror(camera)) {
             Logger.d(TAG, "Surface processing is enabled.");
             return new SurfaceProcessorNode(requireNonNull(getCamera()),
                     getEffect() != null ? getEffect().createSurfaceProcessorInternal() :
@@ -862,11 +954,23 @@
         }
     }
 
-    private static boolean isCropNeeded(@NonNull Rect cropRect, @NonNull Size resolution) {
+    private boolean shouldMirror(@NonNull CameraInternal camera) {
+        // Stream is always mirrored during buffer copy. If there has been a buffer copy, it
+        // means the input stream is already mirrored. Otherwise, mirror it as needed.
+        return camera.getHasTransform() && isMirroringRequired(camera);
+    }
+
+    private static boolean shouldCrop(@NonNull Rect cropRect, @NonNull Size resolution) {
         return resolution.getWidth() != cropRect.width()
                 || resolution.getHeight() != cropRect.height();
     }
 
+    private static boolean shouldEnableSurfaceProcessingByQuirk(@NonNull CameraInternal camera) {
+        // If there has been a buffer copy, it means the surface processing is already enabled on
+        // input stream. Otherwise, enable it as needed.
+        return camera.getHasTransform() && sEnableSurfaceProcessingByQuirk;
+    }
+
     private static int alignDown(int length, int alignment,
             @NonNull Range<Integer> supportedLength) {
         return align(true, length, alignment, supportedLength);
@@ -1128,7 +1232,6 @@
 
     /**
      * @inheritDoc
-     * @hide
      */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
@@ -1145,7 +1248,6 @@
      * @param <T> the type of VideoOutput
      */
     @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-    @RestrictTo(Scope.LIBRARY_GROUP)
     @SuppressWarnings("ObjectToString")
     public static final class Builder<T extends VideoOutput> implements
             UseCaseConfig.Builder<VideoCapture<T>, VideoCaptureConfig<T>, Builder<T>>,
@@ -1153,7 +1255,7 @@
         private final MutableOptionsBundle mMutableConfig;
 
         /** Creates a new Builder object. */
-        Builder(@NonNull T videoOutput) {
+        public Builder(@NonNull T videoOutput) {
             this(createInitialBundle(videoOutput));
         }
 
@@ -1190,6 +1292,7 @@
          * @param configuration An immutable configuration to pre-populate this builder.
          * @return The new Builder.
          */
+        @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
         public static <T extends VideoOutput> Builder<T> fromConfig(
                 @NonNull VideoCaptureConfig<T> configuration) {
@@ -1206,7 +1309,6 @@
 
         /**
          * {@inheritDoc}
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @Override
@@ -1217,7 +1319,6 @@
 
         /**
          * {@inheritDoc}
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -1235,9 +1336,9 @@
         }
 
         /**
-         * Builds an immutable {@link VideoCaptureConfig} from the current state.
+         * Builds a {@link VideoCapture} from the current state.
          *
-         * @return A {@link VideoCaptureConfig} populated with the current state.
+         * @return A {@link VideoCapture} populated with the current state.
          */
         @Override
         @NonNull
@@ -1275,6 +1376,7 @@
          *                   configured.
          * @return the current Builder.
          */
+        @RestrictTo(Scope.LIBRARY_GROUP)
         @Override
         @NonNull
         public Builder<T> setTargetName(@NonNull String targetName) {
@@ -1288,7 +1390,6 @@
          * setTargetAspectRatio is not supported on VideoCapture
          *
          * <p>To set aspect ratio, see {@link Recorder.Builder#setAspectRatio(int)}.
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -1300,16 +1401,29 @@
         /**
          * Sets the rotation of the intended target for images from this configuration.
          *
-         * <p>This is one of four valid values: {@link Surface#ROTATION_0}, {@link
-         * Surface#ROTATION_90}, {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
+         * <p>Valid values include: {@link Surface#ROTATION_0}, {@link Surface#ROTATION_90},
+         * {@link Surface#ROTATION_180}, {@link Surface#ROTATION_270}.
          * Rotation values are relative to the "natural" rotation, {@link Surface#ROTATION_0}.
          *
+         * <p>In general, it is best to additionally set the target rotation dynamically on the
+         * use case. See {@link VideoCapture#setTargetRotationDegrees(int)} for additional
+         * documentation.
+         *
          * <p>If not set, the target rotation will default to the value of
-         * {@link Display#getRotation()} of the default display at the time the use case is
-         * created. The use case is fully created once it has been attached to a camera.
+         * {@link Display#getRotation()} of the default display at the time the use case is bound.
+         *
+         * <p>For a {@link Recorder} output, the final rotation degrees of the video, including
+         * the degrees set by this method and the orientation of the camera sensor, will be
+         * reflected by several possibilities, 1) the rotation degrees is written into the video
+         * metadata, 2) the video content is directly rotated, 3) both, i.e. rotation metadata
+         * and rotated video content which combines to the target rotation. CameraX will choose a
+         * strategy according to the use case.
          *
          * @param rotation The rotation of the intended target.
          * @return The current Builder.
+         * @see VideoCapture#setTargetRotation(int)
+         * @see VideoCapture#setTargetRotationDegrees(int)
+         * @see android.view.OrientationEventListener
          */
         @NonNull
         @Override
@@ -1329,7 +1443,6 @@
          * @param mirrorMode The mirror mode of the intended target.
          * @return The current Builder.
          *
-         * @hide
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
@@ -1343,7 +1456,6 @@
          * setTargetResolution is not supported on VideoCapture
          *
          * <p>To set resolution, see {@link Recorder.Builder#setQualitySelector(QualitySelector)}.
-         *
          */
         @RestrictTo(Scope.LIBRARY_GROUP)
         @NonNull
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/package-info.java b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/package-info.java
index 3cf3391..e507a70 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/package-info.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/internal/audio/package-info.java
@@ -15,7 +15,6 @@
  */
 
 /**
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 package androidx.camera.video.internal.audio;
diff --git a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
index 8c5c619..c03128a 100644
--- a/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
+++ b/camera/camera-video/src/test/java/androidx/camera/video/VideoCaptureTest.kt
@@ -736,6 +736,59 @@
     }
 
     @Test
+    fun setTargetRotationDegrees() {
+        val videoCapture = createVideoCapture()
+        videoCapture.setTargetRotationDegrees(45)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_270)
+        videoCapture.setTargetRotationDegrees(135)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_180)
+        videoCapture.setTargetRotationDegrees(225)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_90)
+        videoCapture.setTargetRotationDegrees(315)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_0)
+        videoCapture.setTargetRotationDegrees(405)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_270)
+        videoCapture.setTargetRotationDegrees(-45)
+        assertThat(videoCapture.targetRotation).isEqualTo(Surface.ROTATION_0)
+    }
+
+    @Test
+    fun hasSurfaceProcessingQuirk_nodeIsNeeded() {
+        // Arrange.
+        VideoCapture.sEnableSurfaceProcessingByQuirk = true
+        setupCamera()
+        createCameraUseCaseAdapter()
+
+        // Act.
+        val videoCapture = createVideoCapture()
+        addAndAttachUseCases(videoCapture)
+
+        // Assert.
+        assertThat(videoCapture.node).isNotNull()
+
+        // Clean-up.
+        VideoCapture.sEnableSurfaceProcessingByQuirk = false
+    }
+
+    @Test
+    fun hasSurfaceProcessingQuirkButNoCameraTransform_nodeIsNotNeeded() {
+        // Arrange.
+        VideoCapture.sEnableSurfaceProcessingByQuirk = true
+        setupCamera(hasTransform = false)
+        createCameraUseCaseAdapter()
+
+        // Act.
+        val videoCapture = createVideoCapture()
+        addAndAttachUseCases(videoCapture)
+
+        // Assert.
+        assertThat(videoCapture.node).isNull()
+
+        // Clean-up.
+        VideoCapture.sEnableSurfaceProcessingByQuirk = false
+    }
+
+    @Test
     fun defaultMirrorModeIsOff() {
         val videoCapture = createVideoCapture()
         assertThat(videoCapture.mirrorMode).isEqualTo(MIRROR_MODE_OFF)
@@ -1327,7 +1380,7 @@
         videoEncoderInfoFinder: Function<VideoEncoderConfig, VideoEncoderInfo> =
             Function { createVideoEncoderInfo() },
     ): VideoCapture<VideoOutput> = VideoCapture.Builder(videoOutput)
-        .setSessionOptionUnpacker { _, _ -> }
+        .setSessionOptionUnpacker { _, _, _ -> }
         .apply {
             targetRotation?.let { setTargetRotation(it) }
             mirrorMode?.let { setMirrorMode(it) }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
index 0288497..0a60ca7 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/Camera2InteropIntegrationTest.kt
@@ -59,11 +59,11 @@
 import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeoutOrNull
 import org.junit.After
 import org.junit.Assert.assertTrue
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -139,9 +139,8 @@
         assertThat(lastState).isEqualTo(DeviceState.Closed)
     }
 
-    @Ignore // b/269523374
     @Test
-    fun cameraSessionListener_receivesClose_afterUnbindAll(): Unit = runBlocking {
+    fun cameraSessionListener_receivesReady_afterBindUseCase(): Unit = runBlocking {
         val imageCaptureBuilder = ImageCapture.Builder()
         val sessionStateFlow = imageCaptureBuilder.createSessionStateFlow()
         withContext(Dispatchers.Main) {
@@ -152,22 +151,21 @@
             )
         }
 
-        var unbindAllCalled = false
-        val lastState = sessionStateFlow.dropWhile { state ->
-            when (state) {
-                // Filter out this state from the downstream flow
-                is SessionState.Unknown -> true
-                is SessionState.Configured -> {
-                    withContext(Dispatchers.Main) { processCameraProvider!!.unbindAll() }
-                    unbindAllCalled = true
-                    true // Filter out this state from the downstream flow
+        val lastState = withTimeoutOrNull(10000) {
+            sessionStateFlow.dropWhile { state ->
+                when (state) {
+                    // Filter out this state from the downstream flow
+                    is SessionState.Unknown -> true
+                    is SessionState.Configured -> {
+                        withContext(Dispatchers.Main) { processCameraProvider!!.unbindAll() }
+                        true // Filter out this state from the downstream flow
+                    }
+
+                    else -> false // Forward to the downstream flow
                 }
+            }.first()
+        } ?: SessionState.Unknown
 
-                else -> false // Forward to the downstream flow
-            }
-        }.first()
-
-        assertThat(unbindAllCalled).isTrue()
         assertThat(lastState).isEqualTo(SessionState.Ready)
     }
 
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
index 1bcfbd0..f96b0b1 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/FocusMeteringDeviceTest.kt
@@ -52,7 +52,6 @@
 import kotlinx.coroutines.runBlocking
 import kotlinx.coroutines.withContext
 import org.hamcrest.CoreMatchers.equalTo
-import org.hamcrest.CoreMatchers.not
 import org.junit.After
 import org.junit.Assert
 import org.junit.Assume
@@ -257,11 +256,6 @@
     @Test
     fun futureCompletes_whenFocusMeteringWithAe() {
         assumeThat(
-            "b/270520932: IllegalArgumentException from Camera2 in CameraPipe",
-            implName, not(CameraPipeConfig::class.simpleName),
-        )
-
-        assumeThat(
             "No AE region available on this device!",
             hasMeteringRegion(cameraSelector, FLAG_AE), equalTo(true)
         )
@@ -275,11 +269,6 @@
     @Test
     fun futureCompletes_whenFocusMeteringWithAwb() {
         assumeThat(
-            "b/270520932: IllegalArgumentException from Camera2 in CameraPipe",
-            implName, not(CameraPipeConfig::class.simpleName),
-        )
-
-        assumeThat(
             "No AWB region available on this device!",
             hasMeteringRegion(cameraSelector, FLAG_AWB), equalTo(true)
         )
@@ -293,11 +282,6 @@
     @Test
     fun futureCompletes_whenFocusMeteringWithAeAwb() {
         assumeThat(
-            "b/270520932: IllegalArgumentException from Camera2 in CameraPipe",
-            implName, not(CameraPipeConfig::class.simpleName),
-        )
-
-        assumeThat(
             "No AE/AWB region available on this device!",
             hasMeteringRegion(cameraSelector, FLAG_AE or FLAG_AWB), equalTo(true)
         )
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
index 857db58..fed1f5b 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/util/StressTestUtil.kt
@@ -74,6 +74,7 @@
             .getLaunchIntentForPackage(CORE_TEST_APP_PACKAGE)!!.apply {
                 putExtra(INTENT_EXTRA_CAMERA_ID, cameraId)
                 putExtra(INTENT_EXTRA_USE_CASE_COMBINATION, useCaseCombination)
+                setClassName(CORE_TEST_APP_PACKAGE, CameraXActivity::class.java.name)
                 flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK
             }
 
diff --git a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
index 6caecd9..c313041 100644
--- a/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
+++ b/camera/integration-tests/coretestapp/src/main/AndroidManifest.xml
@@ -18,6 +18,17 @@
         android:supportsRtl="true"
         android:theme="@style/AppTheme">
         <activity
+            android:name=".ConcurrentCameraActivity"
+            android:screenOrientation="sensorPortrait"
+            android:exported="true"
+            android:label="Concurrent Camera Test App"
+            android:taskAffinity="androidx.camera.integration.core.ConcurrentCameraActivity">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+        </activity>
+        <activity
             android:name=".CameraXActivity"
             android:exported="true"
             android:label="Camera Core Test App"
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
new file mode 100644
index 0000000..c9fb45a
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/ConcurrentCameraActivity.java
@@ -0,0 +1,334 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.integration.core;
+
+import static android.view.View.GONE;
+import static android.view.View.VISIBLE;
+
+import android.content.pm.PackageManager;
+import android.os.Bundle;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.FrameLayout;
+import android.widget.LinearLayout;
+import android.widget.Toast;
+import android.widget.ToggleButton;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appcompat.app.AppCompatActivity;
+import androidx.camera.core.CameraInfo;
+import androidx.camera.core.CameraSelector;
+import androidx.camera.core.Preview;
+import androidx.camera.core.UseCaseGroup;
+import androidx.camera.core.concurrent.ConcurrentCameraConfig;
+import androidx.camera.core.concurrent.SingleCameraConfig;
+import androidx.camera.lifecycle.ProcessCameraProvider;
+import androidx.camera.view.PreviewView;
+import androidx.core.app.ActivityCompat;
+import androidx.core.content.ContextCompat;
+import androidx.lifecycle.LifecycleOwner;
+
+import com.google.common.collect.ImmutableList;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+import java.util.concurrent.ExecutionException;
+/**
+ * Concurrent camera activity.
+ */
+public class ConcurrentCameraActivity extends AppCompatActivity {
+    private static final String TAG = "ConcurrentCameraActivity";
+    private static final int REQUEST_CODE_PERMISSIONS = 1001;
+    private static final String[] REQUIRED_PERMISSIONS = new String[] {
+            "android.permission.CAMERA"
+    };
+
+    @NonNull private PreviewView mSinglePreviewView;
+    @NonNull private PreviewView mFrontPreviewView;
+    @NonNull private PreviewView mBackPreviewView;
+    @NonNull private FrameLayout mFrontPreviewViewForPip;
+    @NonNull private FrameLayout mBackPreviewViewForPip;
+    @NonNull private FrameLayout mFrontPreviewViewForSideBySide;
+    @NonNull private FrameLayout mBackPreviewViewForSideBySide;
+    @NonNull private ToggleButton mModeButton;
+    @NonNull private ToggleButton mLayoutButton;
+    @NonNull private ToggleButton mToggleButton;
+    @NonNull private LinearLayout mSideBySideLayout;
+    @NonNull private FrameLayout mPiPLayout;
+    @Nullable private ProcessCameraProvider mCameraProvider;
+    private boolean mIsConcurrentModeOn = false;
+    private boolean mIsLayoutPiP = true;
+    private boolean mIsFrontPrimary = true;
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+        setContentView(R.layout.activity_concurrent_camera);
+
+        mFrontPreviewViewForPip = findViewById(R.id.camera_front_pip);
+        mBackPreviewViewForPip = findViewById(R.id.camera_back_pip);
+        mBackPreviewViewForSideBySide = findViewById(R.id.camera_back_side_by_side);
+        mFrontPreviewViewForSideBySide = findViewById(R.id.camera_front_side_by_side);
+        mSideBySideLayout = findViewById(R.id.layout_side_by_side);
+        mPiPLayout = findViewById(R.id.layout_pip);
+        mModeButton = findViewById(R.id.mode_button);
+        mLayoutButton = findViewById(R.id.layout_button);
+        mToggleButton = findViewById(R.id.toggle_button);
+
+        boolean isConcurrentCameraSupported =
+                getPackageManager().hasSystemFeature(PackageManager.FEATURE_CAMERA_CONCURRENT);
+        mModeButton.setEnabled(isConcurrentCameraSupported);
+        mLayoutButton.setEnabled(false);
+        if (!isConcurrentCameraSupported) {
+            Toast.makeText(this, getString(R.string.concurrent_not_supported_warning),
+                    Toast.LENGTH_SHORT).show();
+        }
+        mModeButton.setOnClickListener(view -> {
+            if (mCameraProvider == null) {
+                return;
+            }
+            mFrontPreviewView = null;
+            mBackPreviewView = null;
+            // Switch the concurrent mode
+            if (mCameraProvider != null && mIsConcurrentModeOn) {
+                mIsFrontPrimary = true;
+                mIsLayoutPiP = true;
+                bindPreviewForSingle(mCameraProvider);
+                mIsConcurrentModeOn = false;
+            } else {
+                mIsLayoutPiP = true;
+                bindPreviewForPiP(mCameraProvider);
+                mIsConcurrentModeOn = true;
+            }
+            mLayoutButton.setEnabled(mCameraProvider != null && mIsConcurrentModeOn);
+            mToggleButton.setEnabled(mCameraProvider != null && mIsConcurrentModeOn);
+        });
+        mLayoutButton.setOnClickListener(view -> {
+            if (mIsLayoutPiP) {
+                bindPreviewForSideBySide();
+            } else {
+                bindPreviewForPiP(mCameraProvider);
+            }
+            mIsLayoutPiP = !mIsLayoutPiP;
+        });
+        mToggleButton.setOnClickListener(view -> {
+            mIsFrontPrimary = !mIsFrontPrimary;
+            if (mIsConcurrentModeOn) {
+                if (mIsLayoutPiP) {
+                    bindPreviewForPiP(mCameraProvider);
+                } else {
+                    bindPreviewForSideBySide();
+                }
+            } else {
+                bindPreviewForSingle(mCameraProvider);
+            }
+        });
+        if (allPermissionsGranted()) {
+            if (mCameraProvider != null) {
+                mCameraProvider.unbindAll();
+            }
+            startCamera();
+        } else {
+            ActivityCompat.requestPermissions(this, REQUIRED_PERMISSIONS, REQUEST_CODE_PERMISSIONS);
+        }
+    }
+
+    private void startCamera() {
+        final ListenableFuture<ProcessCameraProvider> cameraProviderFuture =
+                ProcessCameraProvider.getInstance(this);
+        cameraProviderFuture.addListener(() -> {
+            try {
+                mCameraProvider = cameraProviderFuture.get();
+                bindPreviewForSingle(mCameraProvider);
+            } catch (ExecutionException | InterruptedException e) {
+                // No errors need to be handled for this Future.
+                // This should never be reached.
+            }
+        }, ContextCompat.getMainExecutor(this));
+    }
+    void bindPreviewForSingle(@NonNull ProcessCameraProvider cameraProvider) {
+        cameraProvider.unbindAll();
+        mSideBySideLayout.setVisibility(GONE);
+        mFrontPreviewViewForPip.setVisibility(VISIBLE);
+        mBackPreviewViewForPip.setVisibility(GONE);
+        mPiPLayout.setVisibility(VISIBLE);
+        // Front
+        mSinglePreviewView = new PreviewView(this);
+        mSinglePreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
+        mFrontPreviewViewForPip.addView(mSinglePreviewView);
+        Preview previewFront = new Preview.Builder()
+                .build();
+        CameraSelector cameraSelectorFront = new CameraSelector.Builder()
+                .requireLensFacing(mIsFrontPrimary
+                        ? CameraSelector.LENS_FACING_FRONT : CameraSelector.LENS_FACING_BACK)
+                .build();
+        previewFront.setSurfaceProvider(mSinglePreviewView.getSurfaceProvider());
+        cameraProvider.bindToLifecycle(
+                this, cameraSelectorFront, previewFront);
+    }
+    void bindPreviewForPiP(@NonNull ProcessCameraProvider cameraProvider) {
+        mSideBySideLayout.setVisibility(GONE);
+        mFrontPreviewViewForPip.setVisibility(VISIBLE);
+        mBackPreviewViewForPip.setVisibility(VISIBLE);
+        mPiPLayout.setVisibility(VISIBLE);
+        if (mFrontPreviewView == null && mBackPreviewView == null) {
+            // Front
+            mFrontPreviewView = new PreviewView(this);
+            mFrontPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
+            mFrontPreviewViewForPip.removeAllViews();
+            mFrontPreviewViewForPip.addView(mFrontPreviewView,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            // Back
+            mBackPreviewView = new PreviewView(this);
+            mBackPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
+            mBackPreviewViewForPip.removeAllViews();
+            mBackPreviewViewForPip.addView(mBackPreviewView,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            cameraProvider.unbindAll();
+            bindToLifecycleForConcurrentCamera(
+                    cameraProvider,
+                    this,
+                    mFrontPreviewView,
+                    mBackPreviewView);
+        } else {
+            updateFrontAndBackView(
+                    mIsFrontPrimary,
+                    mFrontPreviewViewForPip,
+                    mBackPreviewViewForPip,
+                    mFrontPreviewView,
+                    mBackPreviewView);
+        }
+    }
+    void bindPreviewForSideBySide() {
+        mSideBySideLayout.setVisibility(VISIBLE);
+        mPiPLayout.setVisibility(GONE);
+        if (mFrontPreviewView == null && mBackPreviewView == null) {
+            mFrontPreviewView = new PreviewView(this);
+            mFrontPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
+            mBackPreviewView = new PreviewView(this);
+            mBackPreviewView.setImplementationMode(PreviewView.ImplementationMode.COMPATIBLE);
+        }
+        updateFrontAndBackView(
+                mIsFrontPrimary,
+                mFrontPreviewViewForSideBySide,
+                mBackPreviewViewForSideBySide,
+                mFrontPreviewView,
+                mBackPreviewView);
+    }
+    private static void bindToLifecycleForConcurrentCamera(
+            @NonNull ProcessCameraProvider cameraProvider,
+            @NonNull LifecycleOwner lifecycleOwner,
+            @NonNull PreviewView frontPreviewView,
+            @NonNull PreviewView backPreviewView) {
+        Preview previewFront = new Preview.Builder()
+                .build();
+        CameraSelector cameraSelectorPrimary = null;
+        CameraSelector cameraSelectorSecondary = null;
+        for (List<CameraInfo> cameraInfoList : cameraProvider.getAvailableConcurrentCameraInfos()) {
+            for (CameraInfo cameraInfo : cameraInfoList) {
+                if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_FRONT) {
+                    cameraSelectorPrimary = cameraInfo.getCameraSelector();
+                } else if (cameraInfo.getLensFacing() == CameraSelector.LENS_FACING_BACK) {
+                    cameraSelectorSecondary = cameraInfo.getCameraSelector();
+                }
+            }
+        }
+        if (cameraSelectorPrimary == null || cameraSelectorSecondary == null) {
+            return;
+        }
+        previewFront.setSurfaceProvider(frontPreviewView.getSurfaceProvider());
+        SingleCameraConfig primary = new SingleCameraConfig.Builder()
+                .setLifecycleOwner(lifecycleOwner)
+                .setUseCaseGroup(new UseCaseGroup.Builder()
+                        .addUseCase(previewFront)
+                        .build())
+                .setCameraSelector(cameraSelectorPrimary)
+                .build();
+        Preview previewBack = new Preview.Builder()
+                .build();
+        previewBack.setSurfaceProvider(backPreviewView.getSurfaceProvider());
+        SingleCameraConfig secondary = new SingleCameraConfig.Builder()
+                .setLifecycleOwner(lifecycleOwner)
+                .setUseCaseGroup(new UseCaseGroup.Builder()
+                        .addUseCase(previewBack)
+                        .build())
+                .setCameraSelector(cameraSelectorSecondary)
+                .build();
+        ConcurrentCameraConfig concurrentCameraConfig =
+                new ConcurrentCameraConfig.Builder()
+                        .setCameraConfigs(ImmutableList.of(primary, secondary))
+                        .build();
+        cameraProvider.bindToLifecycle(concurrentCameraConfig);
+    }
+    private static void updateFrontAndBackView(
+            boolean isFrontPrimary,
+            @NonNull ViewGroup frontParent,
+            @NonNull ViewGroup backParent,
+            @NonNull View frontChild,
+            @NonNull View backChild) {
+        frontParent.removeAllViews();
+        if (frontChild.getParent() != null) {
+            ((ViewGroup) frontChild.getParent()).removeView(frontChild);
+        }
+        backParent.removeAllViews();
+        if (backChild.getParent() != null) {
+            ((ViewGroup) backChild.getParent()).removeView(backChild);
+        }
+        if (isFrontPrimary) {
+            frontParent.addView(frontChild,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            backParent.addView(backChild,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+        } else {
+            frontParent.addView(backChild,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+            backParent.addView(frontChild,
+                    new FrameLayout.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT,
+                            ViewGroup.LayoutParams.MATCH_PARENT));
+        }
+    }
+    private boolean allPermissionsGranted() {
+        for (String permission : REQUIRED_PERMISSIONS) {
+            if (ContextCompat.checkSelfPermission(this, permission)
+                    != PackageManager.PERMISSION_GRANTED) {
+                return false;
+            }
+        }
+        return true;
+    }
+    @Override
+    public void onRequestPermissionsResult(int requestCode,
+            @NonNull String[] permissions,
+            @NonNull int[] grantResults) {
+        super.onRequestPermissionsResult(requestCode, permissions, grantResults);
+        if (requestCode == REQUEST_CODE_PERMISSIONS) {
+            if (allPermissionsGranted()) {
+                startCamera();
+            } else {
+                Toast.makeText(this, getString(R.string.permission_warning),
+                        Toast.LENGTH_SHORT).show();
+                this.finish();
+            }
+        }
+    }
+}
diff --git a/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml b/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml
new file mode 100644
index 0000000..45e472a
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/res/layout/activity_concurrent_camera.xml
@@ -0,0 +1,128 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+<androidx.constraintlayout.widget.ConstraintLayout
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:app="http://schemas.android.com/apk/res-auto"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    tools:context="androidx.camera.integration.core.ConcurrentCameraActivity">
+
+    <FrameLayout
+        android:id="@+id/layout_root"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent">
+
+        <FrameLayout
+            android:visibility="gone"
+            android:id="@+id/layout_pip"
+            android:orientation="vertical"
+            android:clipToPadding="false"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/camera_front_pip"
+                android:layout_width="match_parent"
+                android:layout_height="match_parent"
+                android:layout_centerInParent="false"
+                android:background="#000"
+                android:contentDescription="@string/preview_front"
+                android:importantForAccessibility="no"/>
+
+            <FrameLayout
+                android:id="@+id/camera_back_pip"
+                android:layout_width="120dp"
+                android:layout_height="200dp"
+                android:layout_centerInParent="false"
+                android:layout_gravity="bottom|right"
+                android:elevation="30dp"
+                android:layout_marginBottom="30dp"
+                android:layout_marginRight="30dp"
+                android:contentDescription="@string/preview_back"
+                android:importantForAccessibility="no"/>
+        </FrameLayout>
+
+
+        <LinearLayout
+            android:visibility="gone"
+            android:id="@+id/layout_side_by_side"
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <FrameLayout
+                android:id="@+id/camera_front_side_by_side"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                app:implementationMode="compatible"
+                android:layout_centerInParent="false"
+                android:background="#000"
+                android:contentDescription="@string/preview_front"
+                android:importantForAccessibility="no"/>
+
+            <FrameLayout
+                android:id="@+id/camera_back_side_by_side"
+                android:layout_width="match_parent"
+                android:layout_height="0dp"
+                android:layout_weight="1"
+                app:implementationMode="compatible"
+                android:layout_centerInParent="false"
+                android:background="#000"
+                android:contentDescription="@string/preview_back"
+                android:importantForAccessibility="no"/>
+        </LinearLayout>
+
+        <LinearLayout
+            android:orientation="vertical"
+            android:layout_width="match_parent"
+            android:layout_height="match_parent">
+
+            <ToggleButton
+                android:id="@+id/mode_button"
+                android:textOn="@string/switch_mode"
+                android:textOff="@string/switch_mode"
+                android:layout_width="100dp"
+                android:layout_height="70dp"
+                android:layout_gravity="top|right"
+                android:layout_marginRight="15dp"
+                android:layout_marginTop="15dp" />
+
+            <ToggleButton
+                android:id="@+id/layout_button"
+                android:textOn="@string/change_layout"
+                android:textOff="@string/change_layout"
+                android:layout_width="100dp"
+                android:layout_height="70dp"
+                android:layout_gravity="top|right"
+                android:layout_marginRight="15dp"
+                android:layout_marginTop="15dp" />
+
+            <ToggleButton
+                android:id="@+id/toggle_button"
+                android:textOn="@string/toggle_camera"
+                android:textOff="@string/toggle_camera"
+                android:layout_width="100dp"
+                android:layout_height="70dp"
+                android:layout_gravity="top|right"
+                android:layout_marginRight="15dp"
+                android:layout_marginTop="15dp" />
+        </LinearLayout>
+    </FrameLayout>
+</androidx.constraintlayout.widget.ConstraintLayout>
+<!--</LinearLayout>-->
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/strings.xml b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
new file mode 100644
index 0000000..d54e0d1
--- /dev/null
+++ b/camera/integration-tests/coretestapp/src/main/res/values/strings.xml
@@ -0,0 +1,28 @@
+<!--
+  Copyright 2023 The Android Open Source Project
+
+  Licensed under the Apache License, Version 2.0 (the "License");
+  you may not use this file except in compliance with the License.
+  You may obtain a copy of the License at
+
+       http://www.apache.org/licenses/LICENSE-2.0
+
+  Unless required by applicable law or agreed to in writing, software
+  distributed under the License is distributed on an "AS IS" BASIS,
+  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+  See the License for the specific language governing permissions and
+  limitations under the License.
+  -->
+
+<resources>
+    <string name="preview_front">Front Camera</string>
+    <string name="preview_back">Back Camera</string>
+    <string name="switch_mode">Switch Mode</string>
+    <string name="change_layout">Change Layout</string>
+    <string name="toggle_camera">Toggle Camera</string>
+    <string name="toggle">Toggle</string>
+    <string name="finish">Finish</string>
+    <string name="is_front_primary">IsFrontPrimary</string>
+    <string name="permission_warning">Permissions not granted by the user.</string>
+    <string name="concurrent_not_supported_warning">Concurrent camera not supported</string>
+</resources>
\ No newline at end of file
diff --git a/camera/integration-tests/coretestapp/src/main/res/values/style.xml b/camera/integration-tests/coretestapp/src/main/res/values/style.xml
index c1d6296f..92cb421 100644
--- a/camera/integration-tests/coretestapp/src/main/res/values/style.xml
+++ b/camera/integration-tests/coretestapp/src/main/res/values/style.xml
@@ -21,4 +21,8 @@
         <item name="android:windowFullscreen">true</item>
         <item name="android:windowContentOverlay">@null</item>
     </style>
+    <style name="AppTheme.NoActionBar">
+        <item name="windowActionBar">false</item>
+        <item name="windowNoTitle">true</item>
+    </style>
 </resources>
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
index 63f11da..26a36f2 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/ImageCaptureExtenderValidationTest.kt
@@ -68,6 +68,8 @@
     @Before
     fun setUp(): Unit = runBlocking {
         assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+        assumeTrue(!ExtensionVersion.isAdvancedExtenderSupported())
+
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
index fb3a2d2..5731a41 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/PreviewExtenderValidationTest.kt
@@ -71,6 +71,8 @@
     @Before
     fun setUp(): Unit = runBlocking {
         assumeTrue(CameraXExtensionsTestUtil.isTargetDeviceAvailableForExtensions())
+        assumeTrue(!ExtensionVersion.isAdvancedExtenderSupported())
+
         cameraProvider = ProcessCameraProvider.getInstance(context)[10000, TimeUnit.MILLISECONDS]
         extensionsManager = ExtensionsManager.getInstanceAsync(
             context,
diff --git a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
index 5a5e216..610e9ae 100644
--- a/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
+++ b/camera/integration-tests/viewtestapp/src/androidTest/java/androidx/camera/integration/view/CameraControllerFragmentTest.kt
@@ -140,7 +140,7 @@
         // Arrange: launch app and verify effect is inactive.
         fragment.assertPreviewIsStreaming()
         val processor =
-            fragment.mToneMappingPreviewEffect.surfaceProcessor as ToneMappingSurfaceProcessor
+            fragment.mToneMappingSurfaceEffect.surfaceProcessor as ToneMappingSurfaceProcessor
         assertThat(processor.isSurfaceRequestedAndProvided()).isFalse()
 
         // Act: turn on effect.
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
index 30610dc..49cb97b 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/CameraControllerFragment.java
@@ -150,7 +150,7 @@
     private ImageAnalysis.Analyzer mWrappedAnalyzer;
 
     @VisibleForTesting
-    ToneMappingPreviewEffect mToneMappingPreviewEffect;
+    ToneMappingSurfaceEffect mToneMappingSurfaceEffect;
     ToneMappingImageEffect mToneMappingImageEffect;
 
     private final ImageAnalysis.Analyzer mAnalyzer = image -> {
@@ -222,7 +222,7 @@
         });
 
         // Set up post-processing effects.
-        mToneMappingPreviewEffect = new ToneMappingPreviewEffect();
+        mToneMappingSurfaceEffect = new ToneMappingSurfaceEffect();
         mToneMappingImageEffect = new ToneMappingImageEffect();
         mEffectToggle = view.findViewById(R.id.effect_toggle);
         mEffectToggle.setOnCheckedChangeListener((compoundButton, isChecked) -> onEffectsToggled());
@@ -370,13 +370,13 @@
             mExecutorService.shutdown();
         }
         mRotationProvider.removeListener(mRotationListener);
-        mToneMappingPreviewEffect.release();
+        mToneMappingSurfaceEffect.release();
     }
 
     private void onEffectsToggled() {
         if (mEffectToggle.isChecked()) {
             mCameraController.setEffects(
-                    new HashSet<>(asList(mToneMappingPreviewEffect, mToneMappingImageEffect)));
+                    new HashSet<>(asList(mToneMappingSurfaceEffect, mToneMappingImageEffect)));
         } else {
             mCameraController.setEffects(null);
         }
diff --git a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
similarity index 81%
rename from camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt
rename to camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
index 25bdc1c..3d18aff 100644
--- a/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingPreviewEffect.kt
+++ b/camera/integration-tests/viewtestapp/src/main/java/androidx/camera/integration/view/ToneMappingSurfaceEffect.kt
@@ -20,10 +20,11 @@
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 
 /**
- * A tone mapping effect for preview UseCase.
+ * A tone mapping effect for Preview/VideoCapture UseCase.
  */
-internal class ToneMappingPreviewEffect :
-    CameraEffect(PREVIEW, mainThreadExecutor(), ToneMappingSurfaceProcessor()) {
+internal class ToneMappingSurfaceEffect : CameraEffect(
+    PREVIEW or VIDEO_CAPTURE, mainThreadExecutor(), ToneMappingSurfaceProcessor()
+) {
 
     fun release() {
         (surfaceProcessor as? ToneMappingSurfaceProcessor)?.release()
diff --git a/car/app/app-automotive/api/public_plus_experimental_current.txt b/car/app/app-automotive/api/public_plus_experimental_current.txt
index f0fc8af..71b454a 100644
--- a/car/app/app-automotive/api/public_plus_experimental_current.txt
+++ b/car/app/app-automotive/api/public_plus_experimental_current.txt
@@ -12,6 +12,10 @@
     ctor public CarAppActivity();
   }
 
+  @androidx.car.app.annotations.ExperimentalCarApi public final class LauncherActivity extends androidx.fragment.app.FragmentActivity implements androidx.lifecycle.LifecycleOwner {
+    ctor public LauncherActivity();
+  }
+
 }
 
 package androidx.car.app.activity.renderer.surface {
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/LauncherActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/LauncherActivity.java
new file mode 100644
index 0000000..942b59f
--- /dev/null
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/LauncherActivity.java
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.activity;
+
+import android.annotation.SuppressLint;
+import android.car.Car;
+import android.car.drivingstate.CarUxRestrictionsManager;
+import android.content.ComponentName;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.os.Bundle;
+
+import androidx.annotation.Nullable;
+import androidx.annotation.VisibleForTesting;
+import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.fragment.app.FragmentActivity;
+
+/**
+ * <p> This class handles providing the right launcher activity when running native
+ * applications and Car App Library applications.
+ *
+ * If distraction optimized is mandated {@link CarAppActivity} will be launched.
+ * otherwise the activity with action {@link Intent#ACTION_MAIN} and category
+ * {@link Intent#CATEGORY_DEFAULT} will be launched.
+ */
+@SuppressLint({"ForbiddenSuperClass"})
+@ExperimentalCarApi
+public final class LauncherActivity extends FragmentActivity {
+
+    @Override
+    protected void onCreate(@Nullable Bundle savedInstanceState) {
+        super.onCreate(savedInstanceState);
+
+        if (isDistractionOptimizedActivityRequired(this)) {
+            startActivity(getCarAppActivityIntent(this));
+        } else {
+            startActivity(getDefaultIntent(this));
+        }
+        finish();
+    }
+
+    @SuppressWarnings("deprecation")
+    @VisibleForTesting
+    static Intent getDefaultIntent(Context context) {
+        Intent intent =
+                new Intent(Intent.ACTION_MAIN).addCategory(
+                        Intent.CATEGORY_DEFAULT);
+        ResolveInfo resolveInfoList = context.getPackageManager().resolveActivity(intent,
+                PackageManager.MATCH_DEFAULT_ONLY);
+
+        if (resolveInfoList == null) {
+            throw new IllegalStateException("Application requires an intent with Action Main and"
+                    + " Category Default");
+        } else {
+            return new Intent().setComponent(new ComponentName(
+                    resolveInfoList.activityInfo.packageName,
+                    resolveInfoList.activityInfo.name));
+
+        }
+    }
+
+    @VisibleForTesting
+    static Intent getCarAppActivityIntent(Context context) {
+        return new Intent(context, CarAppActivity.class);
+    }
+
+    @VisibleForTesting
+    static boolean isDistractionOptimizedActivityRequired(Context context) {
+        if (!context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_AUTOMOTIVE)) {
+            return false;
+        }
+
+        Car car = Car.createCar(context);
+        boolean isDistractionOptimizedRequired = ((CarUxRestrictionsManager) car.getCarManager(
+                Car.CAR_UX_RESTRICTION_SERVICE))
+                .getCurrentCarUxRestrictions().
+                isRequiresDistractionOptimization();
+        car.disconnect();
+        car = null;
+        return isDistractionOptimizedRequired;
+    }
+
+}
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/LauncherActivityTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/LauncherActivityTest.java
new file mode 100644
index 0000000..3a48634
--- /dev/null
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/LauncherActivityTest.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.activity;
+
+import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+import static org.robolectric.Shadows.shadowOf;
+
+import android.content.ComponentName;
+import android.content.Intent;
+import android.content.IntentFilter;
+import android.content.pm.PackageManager;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowPackageManager;
+
+/** Tests for {@link LauncherActivity}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class LauncherActivityTest {
+    private final ComponentName defaultComponentName = new ComponentName(getApplicationContext(),
+            getClass().getName());
+    private final ComponentName launcherComponent = new ComponentName(getApplicationContext(),
+            "androidx.car.app.activity.LauncherActivity");
+    private final ComponentName carAppActivityComponentName = new ComponentName(
+            getApplicationContext(), "androidx.car.app.activity.CarAppActivity");
+
+    private ShadowPackageManager mShadowPackageManager;
+
+    @Before
+    public void setup() {
+        PackageManager packageManager = getApplicationContext().getPackageManager();
+        mShadowPackageManager = shadowOf(packageManager);
+
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
+        intentFilter.addCategory(Intent.CATEGORY_LAUNCHER);
+
+        mShadowPackageManager.addActivityIfNotPresent(launcherComponent);
+        mShadowPackageManager.addIntentFilterForActivity(launcherComponent,
+                intentFilter);
+    }
+
+    @Test
+    public void getCarAppActivityIntent_returnsCarAppActivityIntent() {
+        assertThat(
+                LauncherActivity.getCarAppActivityIntent(
+                        getApplicationContext()).getComponent()).isEqualTo(
+                carAppActivityComponentName);
+    }
+
+    @Test
+    public void getDefaultIntent_throwsIllegalStateException() {
+        assertThrows(IllegalStateException.class,
+                () -> LauncherActivity.getDefaultIntent(
+                        getApplicationContext()));
+    }
+
+    @Test
+    public void getDefaultIntent_returnsDefaultComponentName() {
+        IntentFilter intentFilter = new IntentFilter(Intent.ACTION_MAIN);
+        intentFilter.addCategory(Intent.CATEGORY_DEFAULT);
+
+        mShadowPackageManager.addActivityIfNotPresent(defaultComponentName);
+        mShadowPackageManager.addActivityIfNotPresent(carAppActivityComponentName);
+        mShadowPackageManager.addIntentFilterForActivity(defaultComponentName,
+                intentFilter);
+        Intent defaultIntent = LauncherActivity.getDefaultIntent(
+                getApplicationContext());
+
+        assertThat(defaultIntent.getComponent()).isEqualTo(defaultComponentName);
+    }
+
+    @Test
+    public void isDistractionOptimizedActivityRequired_returnsFalse() {
+        mShadowPackageManager.setSystemFeature(PackageManager.FEATURE_AUTOMOTIVE, false);
+
+        assertThat(LauncherActivity.isDistractionOptimizedActivityRequired(
+                getApplicationContext())).isFalse();
+    }
+
+}
diff --git a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/tabtemplates/TabTemplateDemoScreen.java b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/tabtemplates/TabTemplateDemoScreen.java
index af4b1c3..63300fa 100644
--- a/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/tabtemplates/TabTemplateDemoScreen.java
+++ b/car/app/app-samples/showcase/common/src/main/java/androidx/car/app/sample/showcase/common/screens/templatelayouts/tabtemplates/TabTemplateDemoScreen.java
@@ -26,6 +26,7 @@
 import androidx.car.app.CarToast;
 import androidx.car.app.Screen;
 import androidx.car.app.model.Action;
+import androidx.car.app.model.CarColor;
 import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.GridItem;
 import androidx.car.app.model.GridTemplate;
@@ -142,6 +143,7 @@
         }
         return new ListTemplate.Builder()
                 .setSingleList(listBuilder.build())
+                .addAction(createFabBackAction())
                 .build();
     }
 
@@ -162,6 +164,7 @@
         }
         return new GridTemplate.Builder()
                 .setSingleList(listBuilder.build())
+                .addAction(createFabBackAction())
                 .build();
     }
 
@@ -199,4 +202,13 @@
                 .build();
     }
 
+    private Action createFabBackAction() {
+        Action action = new Action.Builder()
+                .setIcon(CarIcon.BACK)
+                .setBackgroundColor(CarColor.BLUE)
+                .setOnClickListener(() -> getScreenManager().pop())
+                .build();
+        return action;
+    }
+
 }
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
index ce0897a..89d345f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-af/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Die ry kan ook gekies word"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekondêre handeling is gekies"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Primêre handeling vir ry is gekies"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstrasie van verdeelde itemlys"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lys 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lys 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subteks onder elke lys"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstrasies oor diverse template"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Stal demonstrasies uit"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrasie van templaatuitlegte"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
index b5ff8cf..0226e81c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-am/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"እንዲሁም ረድፉ መመረጥ ይችላል"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ሁለተኛ እርምጃ ተመርጧል"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"የረድፍ አንደኛ እርምጃ ተመርጧል"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"የተከፈለ ንጥል ዝርዝር ቅንጭብ ማሳያ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ዝርዝር 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ዝርዝር 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ከእያንዳንዱ ዝርዝር ስር የግርጌ ጽሑፍ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"የተለያዩ ቅንብር ደንቦች ቅንጭብ ማሳያዎች"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"የመሳያ ቅንጭብ ማሳያ"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"የቅንብር ደንብ አቀማመጥ ቅንጭብ ማሳያዎች"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
index 803c3b5..d347a04 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ar/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"يمكن أيضًا اختيار الصف."</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"تم اختيار إجراء ثانوي للصف."</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"تم اختيار إجراء أساسي للصف."</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"عرض تجريبي لقائمة عناصر مُقسَّمة"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"القائمة 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"القائمة 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"نص فرعي تحت كل قائمة"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"إصدارات تجريبية لنموذج ميزات متنوّعة"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"عرض الإصدارات التجريبية"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"عروض توضيحية لتنسيقات النماذج"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
index ea54d28..6014a5f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-as/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"শাৰীটোও বাছনি কৰিব পৰা যায়"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"গৌণ কাৰ্য বাছনি কৰা হৈছে"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"শাৰীৰ প্ৰাথমিক কাৰ্য বাছনি কৰা হৈছে"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"বিভক্ত বস্তুৰ সূচীৰ ডেম’"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"সূচী ১"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"সূচী ২"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"প্ৰতিখন সূচীৰ অন্তৰ্গত উপপাঠ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"সানমিহলি টেম্পলে’টৰ ডেম’"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ডেম’ দেখুৱাওক"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"টেমপ্লে’ট লে’আউটৰ ডেম’"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
index 6724bfb..d02a701 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-az/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Sıra da seçilə bilər"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"İkinci Dərəcəli Əməliyyat seçilib"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Sıra üzrə əsas əməliyyat seçilib"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Bölməli element siyahısı demosu"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Siyahı 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Siyahı 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Hər siyahı üzrə alt mətn"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Digər Şablon Demoları"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Vitrin Demoları"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Şablon Düzən Demoları"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
index 92751ec..63c19f3 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-b+sr+Latn/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Može da se izabere i red"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekundarna radnja je izabrana"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Primarna radnja reda je izabrana"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstracija liste za stavke sa odeljcima"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Podtekst ispod svake liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstracije različitih šablona"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstracije prikazivanja"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstracije izgleda šablona"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
index 349746d..a1cda80 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-be/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Таксама можна выбраць радок"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Выбрана другаснае дзеянне"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Выбрана галоўнае дзеянне для радка"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Дэмаверсія спіса элементаў з раздзеламі"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Спіс 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Спіс 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Тэкст пад кожным спісам"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Дэманстрацыі розных шаблонаў"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Дэманстрацыі выбранага"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Дэманстрацыі макета шаблона"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
index 8be2e94..e4844e6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bg/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Редът може също да бъде избран"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Избрано е алтернативното действие"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Избрано е основното действие за реда"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Демонстрация на списъка с елементи, разделен на секции"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Списък 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Списък 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Подтекст под всеки списък"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Демонстрации на разни шаблони"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Демонстрации на Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Демонстрации на оформления за шаблон"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
index 4066f82..cc25232 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bn/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"সারিও বেছে নেওয়া যেতে পারে"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"সেকেন্ডারি অ্যাকশন বেছে নেওয়া হয়েছে"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"সারির প্রাইমারি অ্যাকশন বেছে নেওয়া হয়েছে"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"বিভাগ অনুযায়ী করা আইটেম তালিকার ডেমো"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"তালিকা ১"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"তালিকা ২"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"প্রতিটি তালিকায় থাকা সাবটেক্সট"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"অন্যান্য টেমপ্লেটের ডেমো"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ডেমো শোকেস করুন"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"টেমপ্লেট লেআউট সংক্রান্ত ডেমো"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
index ae47de2..8f0caf4 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-bs/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Moguće je odabrati i red"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Odabrana je sekundarna radnja"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Odabrana je primarna radnja reda"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo verzija liste stavki u odjeljcima"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1. lista"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2. lista"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Podtekst ispod svake liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demo verzije raznih šablona"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demo verzije predstavljanja"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo verzije rasporeda predloška"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
index a830474..0c95fc9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ca/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"La fila també es pot seleccionar"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"S\'ha seleccionat l\'acció secundària"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"S\'ha seleccionat l\'acció principal de la fila"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demostració d\'una llista d\'elements amb seccions"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Llista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Llista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext a sota de cada llista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demostracions de plantilles diverses"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demostracions de Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demostracions de dissenys de plantilles"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
index 8580af4..3c7db09 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-cs/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Řádek lze také vybrat"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Je vybrána sekundární akce"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Je vybrána primární akce řádku"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Ukázka seznamu položek s oddíly"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Seznam 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Seznam 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Popis pod každým seznamem"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Ukázky různých šablon"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Ukázky Výběru"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Ukázky šablon rozvržení"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
index f35680cb..4881ef8 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-da/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Rækken kan også vælges"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekundær handling er valgt"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Primær handling for rækken er valgt"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo af sektionsinddelt elementliste"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Tekst under hver liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstrationer af diverse skabeloner"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Vis demonstrationer"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demoer for skabelonlayout"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
index 5dca0ea..ea7398b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-de/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Auch diese Zeile kann ausgewählt werden"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekundäre Aktion ist ausgewählt"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Zeile mit der primären Aktion ist ausgewählt"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo für Elementliste mit Abschnitten"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext unter jeder Liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demos der verschiedenen Vorlagen"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demos anzeigen"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo der Layoutvorlagen"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
index a49cb43..6b7cf3d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-el/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Η σειρά μπορεί επίσης να επιλεγεί"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Επιλέχθηκε η δευτερεύουσα ενέργεια σειράς"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Επιλέχθηκε η κύρια ενέργεια σειράς"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Επίδειξη λίστας στοιχείων χωρισμένων σε ενότητες"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Λίστα 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Λίστα 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Δευτερεύον κείμενο κάτω από κάθε λίστα"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Διάφορες επιδείξεις προτύπων"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Προβολή επιδείξεων"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Επιδείξεις διάταξης προτύπου"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
index 9878218..00c576f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rAU/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"The row can also be selected"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary action is selected"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Row primary action is selected"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sectioned item list demo"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"List 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"List 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext under each list"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc templates demos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase demos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Template layout demos"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
index 960c1ff..e74be41 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rCA/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"The row can also be selected"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary Action is selected"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Row primary action is selected"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sectioned Item List Demo"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"List 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"List 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext under each list"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc Templates Demos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase Demos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Template Layout Demos"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
index 9878218..00c576f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rGB/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"The row can also be selected"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary action is selected"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Row primary action is selected"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sectioned item list demo"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"List 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"List 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext under each list"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc templates demos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase demos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Template layout demos"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
index 9878218..00c576f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rIN/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"The row can also be selected"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Secondary action is selected"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Row primary action is selected"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sectioned item list demo"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"List 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"List 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext under each list"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Misc templates demos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase demos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Template layout demos"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
index ba2d288..f2b3cc0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-en-rXC/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‎‏‏‏‎‏‎‎‎‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‎‏‏‏‏‎‏‎‎‏‎‎‏‎‏‏‏‎‎‎‏‏‎‏‎‎‎The row can also be selected‎‏‎‎‏‎"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‎‎‏‏‎‎‎‏‏‏‏‎‎‏‏‏‎‏‎‏‎‎‏‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‏‏‏‎‎‏‏‎‏‎‎‏‎‏‎Secondary Action is selected‎‏‎‎‏‎"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‏‏‏‎‏‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‏‎‎‎‎‎‎‎‏‎‏‎‎‎Row primary action is selected‎‏‎‎‏‎"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‎‎‏‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎‎‎‏‎‏‏‎‎‏‎‏‏‏‎‏‏‏‎‏‏‏‎‎‏‎‎‎‎‎‏‎‏‎‎‏‏‏‎‎‎‏‎Sectioned Item List Demo‎‏‎‎‏‎"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‎‏‎‎‎‏‏‏‏‎‏‎‏‏‎‏‎‎‎‏‎‎‎‏‏‎‎‏‏‎‎‏‏‎‎‎‏‏‎‏‏‎‏‎‏‎‎List 1‎‏‎‎‏‎"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‏‎‏‎‏‏‏‏‏‏‎‎‎‎‎‎‏‎‎‎‎‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‎‎‏‎List 2‎‏‎‎‏‎"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‎‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‏‏‏‎‏‏‎‎‎‎‎‎‎‎‏‎‎‎‏‏‎‏‏‏‏‎Subtext under each list‎‏‎‎‏‎"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‏‎‏‏‎‎‏‏‏‎‎‎‏‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎‎‎‏‎‏‎‎‎‏‎‎‎‏‏‎‏‏‎‎‏‏‎‎‏‎‎Misc Templates Demos‎‏‎‎‏‎"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‎‏‎‏‎‏‏‎‎‏‏‎‏‎‎‏‏‏‎‏‎‎‏‎‎‎‎‏‎‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‏‏‎‎‏‎‎‎‎‎‎‏‏‎‎‎‎Showcase Demos‎‏‎‎‏‎"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‏‎‏‏‏‏‎‎‎‎‎‏‏‎‏‏‎‎‏‎‎‎‏‎‎‎‏‏‎‎‎‎‏‏‏‏‎‎‏‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‏‏‎Template Layout Demos‎‏‎‎‏‎"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
index 9b42026..d4b16a9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-es-rUS/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"También se puede seleccionar la fila"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Se seleccionó una acción secundaria"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Se seleccionó una acción de fila principal"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demostración de la lista de elementos seccionada"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtexto debajo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demostraciones de plantillas varias"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demostraciones de Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demostración de plantilla de diseño"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
index 9c34e1a..35c6bec 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-es/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"También se puede seleccionar la fila"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Se ha seleccionado la acción secundaria"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Se ha seleccionado la acción principal de la fila"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo de una lista de elementos seccionada"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtexto debajo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Otras demos de plantillas"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demos de Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demos de diseño de plantillas"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
index de815b7..7dcd4ca 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-et/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Rea saab samuti valida"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Valitud on teisene toiming"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Valitud on rea peamine toiming"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sanktsioneeritud üksuste loendi demo"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Loend 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Loend 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Iga loendi all olev alltekst"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Mitmesuguste mallide demod"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Esiletõstmise demod"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Malli paigutuse demod"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
index 1b0b4e6..a39db4e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-eu/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Errenkada ere hauta daiteke"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Bigarren mailako ekintza hautatuta dago"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Errenkadari dagokion ekintza nagusia hautatuta dago"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Zerrenden demo-bertsioa duen elementu atalduna"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Zerrenda 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Zerrenda 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Zerrenda bakoitzaren beheko azpitestua"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Bestelako txantiloien demo-bertsioak"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Erakutsi demo-bertsioak"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Txantiloi-diseinuen demo-bertsioak"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
index 64edcd6..6b14091 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fa/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ردیف را هم می‌توان انتخاب کرد"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"کنش فرعی انتخاب شده است"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"کنش اصلی ردیف انتخاب شده است"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"نمونه فهرست موارد بخش‌بندی‌شده"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"فهرست ۱"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"فهرست ۲"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"نوشتار فرعی زیر هر فهرست"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"الگوهای متفرقه نمونه"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"نمایش نمونه‌ها"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"نسخه‌های نمونه طرح‌بندی الگو"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
index 805bb6a..8ad1625 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fi/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Voit valita myös rivin"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Toissijainen toiminto on valittu"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Rivin ensisijainen toiminto on valittu"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Osioihin jaetun listan esittely"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Kunkin listan alla oleva alateksti"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Sekalaisten mallien esittelyt"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase-esittelyt"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Malliasettelujen esittelyt"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
index 9ea0c9e..76b6626 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr-rCA/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Cette ligne peut également être sélectionnée"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"L\'action secondaire est sélectionnée"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"L\'action primaire de la ligne est sélectionnée"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Démo de liste d\'éléments à sections"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Sous-titre de chaque liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Démos de divers modèles"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Présenter les démos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Démos de mise en page du modèle"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
index 64d6333..9e261b2 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-fr/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"La ligne peut également être sélectionnée"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"L\'action secondaire est sélectionnée"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"L\'action principale de la ligne est sélectionnée"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Démo de liste d\'éléments en sections"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Texte sous chaque liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Démos de divers modèles"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Présenter les démos"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Démos de mise en page du modèle"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
index 33d58e3..8086d0a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gl/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Tamén se pode seleccionar a fila"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Seleccionouse a acción secundaria"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Seleccionouse a acción principal da fila"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demostración de lista de elementos por seccións"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Texto secundario debaixo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Outras demostracións de modelos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demostracións de Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demostracións de deseños de modelo"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
index 4f5775e..f56e44e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-gu/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"પંક્તિ પણ પસંદ કરી શકાય છે"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ગૌણ ઍક્શન પસંદ કરી"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"પ્રાથમિક ઍક્શન પંક્તિ પસંદ કરી"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"વિભાગમાં ઉપલબ્ધ આઇટમની સૂચિનો ડેમો"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"સૂચિ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"સૂચિ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"દરેક સૂચિ હેઠળની પેટાટેક્સ્ટ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"વિવિધ નમૂનાઓના ડેમો"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ડેમો બતાવો"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"નમૂનાના લેઆઉટનો ડેમો"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
index ff31971..d98d811 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hi/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"पंक्ति को भी चुना जा सकता है"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"सेकंडरी ऐक्शन चुना गया"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"पंक्ति से जुड़ा प्राइमरी ऐक्शन चुना गया"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"सेक्शन में मौजूद आइटम की सूची का डेमो"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"सूची 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"सूची 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"हर सूची के नीचे सबटेक्स्ट"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"दूसरे टेंप्लेट के डेमो"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"डेमो दिखाएं"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"टेंप्लेट लेआउट के डेमो"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
index e5d91f8..56ea02e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hr/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Može se odabrati i redak"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Odabrana je sekundarna radnja"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Odabran je redak primarne radnje"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Pokazna verzija popisa stavki po odjeljcima"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Popis 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Popis 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Podtekst ispod svakog popisa"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Pokazne verzije raznih predložaka"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Prikaži pokazne verzije"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Pokazne verzije izgleda predloška"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
index b2c75cb..418ef42 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hu/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"A sor is kiválasztható"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Másodlagos művelet kiválasztva"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Sor elsődleges művelete kiválasztva"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Szakaszolt elemlisták bemutatója"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1. lista"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2. lista"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Alszöveg az egyes listák alatt"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Egyéb sablonok – demók"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Kirakat – demók"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Sablonelrendezések bemutatói"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
index 439e94c..2d43574 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-hy/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Շարքը ևս կարելի է ընտրել"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Ընտրված է շարքի երկրորդական գործողությունը"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Ընտրված է շարքի գլխավոր գործողությունը"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Բաժիններով օբյեկտների ցանկի ցուցադրում"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Ցանկ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Ցանկ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Նկարագրություն՝ յուրաքանչյուր ցանկի տակ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Այլ ձևանմուշների դեմոներ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Ցուցադրել դեմոները"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Ձևանմուշի դասավորության դեմոներ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
index 49d1a31..efd56c4 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-in/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Baris juga dapat dipilih"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Tindakan Sekunder dipilih"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Tindakan utama baris dipilih"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo Daftar Item dengan Bagian"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Daftar 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Daftar 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subteks dalam setiap daftar"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demo Template Lain-Lain"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demo Berita Pilihan"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo Tata Letak Template"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
index 345bdac..06c9d74 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-is/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Einnig er hægt að velja línuna"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Aukaaðgerð er valin"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Aðalaðgerð línu er valin"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Sýnishorn af lista yfir skipt atriði"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Listi 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Listi 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Texti undir hverjum lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Sýnishorn ýmissa sniðmáta"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Prufuútgáfur Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Sýnishorn sniðmátsuppsetningar"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
index 828199f..418a52d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-it/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Anche la riga può essere selezionata"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"È stata selezionata l\'azione secondaria"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"È stata selezionata l\'azione principale della riga"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo Elenco di elementi a sezioni"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Elenco 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Elenco 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Sottotesto sotto ogni elenco"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demo modelli vari"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demo Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo Layout modello"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
index ce1ef19..7d6bc35 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-iw/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ניתן לבחור גם את השורה"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"נבחרה פעולה משנית"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"נבחרה פעולה ראשית בשורה"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"הדגמה של רשימת פריטים שמחולקת לקטעים"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"רשימה 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"רשימה 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"טקסט משנה מתחת לכל רשימה"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"הדגמות של תבניות שונות"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"הדגמות תצוגה"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"הדגמות של אפשרויות פריסה של תבנית"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
index 6a71d9e..1f5aa3c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ja/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"行も選択できます"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"セカンダリ アクションを選択しました"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"行のプライマリ アクションを選択しました"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"セクションに区切られたアイテムリストのデモ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"リスト 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"リスト 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"各リストのサブテキスト"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"その他のテンプレートのデモ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"デモを表示"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"テンプレート レイアウトのデモ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
index f662de2..872a4e0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ka/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ასევე შეიძლება მწკრივის არჩევა"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"არჩეულია მეორადი მოქმედება"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"არჩეულია მწკრივის პირველადი მოქმედება"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"ერთეულების სიის დემონსტრაცია სექციებში"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"სია 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"სია 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ქვეტექსტი თითოეული სიის ქვეშ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"სხვადასხვა შაბლონური დემოები"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"დემოების ჩვენება"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"შაბლონის განლაგების დემო"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
index 7d221d2..317dfc35 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kk/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Жолды да таңдауға болады."</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Қосымша әрекет таңдалды."</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Жол үшін негізгі әрекет таңдалды."</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Бөлшектелген элемент тізімінің демо нұсқасы"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1-тізім"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2-тізім"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Әр тізім астындағы түсініктеме мәтін"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"\"Басқалары\" үлгісінің демо нұсқасы"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Презентацияның демо нұсқасы"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Формат үлгісі (демо нұсқалары)"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
index ba092bb..5164aa5 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-km/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ជួរដេកក៏អាចត្រូវបានជ្រើសរើសផងដែរ"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"សកម្មភាពបន្ទាប់បន្សំត្រូវបានជ្រើសរើស"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"សកម្មភាពចម្បងជួរដេកត្រូវបានជ្រើសរើស"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"គំរូបង្ហាញបញ្ជីធាតុតាមផ្នែក"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"បញ្ជីទី 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"បញ្ជីទី 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"អត្ថបទរងនៅក្រោមបញ្ជីនីមួយៗ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"គំរូបង្ហាញនៃ​ទម្រង់គំរូផ្សេងៗ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"គំរូបង្ហាញអំពីការតាំងរំលេច"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"គំរូបង្ហាញនៃប្លង់​ទម្រង់គំរូ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
index 58f3841..153447c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-kn/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ಸಾಲನ್ನು ಸಹ ಆಯ್ಕೆ ಮಾಡಬಹುದು"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ದ್ವಿತೀಯಕ ಕ್ರಿಯೆಯನ್ನು ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"ಸಾಲಿನ ಪ್ರಾಥಮಿಕ ಕ್ರಿಯೆಯನ್ನು ಆಯ್ಕೆಮಾಡಲಾಗಿದೆ"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"ವಿಭಾಗೀಯ ಐಟಂ ಪಟ್ಟಿ ಡೆಮೋ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ಪಟ್ಟಿ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ಪಟ್ಟಿ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ಪ್ರತಿ ಪಟ್ಟಿಯ ಅಡಿಯಲ್ಲಿ ಉಪಪಠ್ಯ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"ಇತರ ಟೆಂಪ್ಲೇಟ್‌ಗಳ ಡೆಮೋಗಳು"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase ಡೆಮೋಗಳು"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"ಟೆಂಪ್ಲೇಟ್ ಲೇಔಟ್ ಡೆಮೋಗಳು"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
index c2de77b..dab016f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ko/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"행도 선택할 수 있습니다."</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"보조 작업 선택됨"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"행 기본 작업 선택됨"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"섹션으로 구분된 항목 목록 데모"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"목록 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"목록 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"각 목록 아래 하위 텍스트"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"기타 템플릿 데모"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"쇼케이스 데모"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"템플릿 레이아웃 데모"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
index 4b51a30..2f22c34 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ky/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Сапты да тандоого болот"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Кошумча аракет тандалды"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Негизги аракет сабы тандалды"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Бөлүмдөргө иреттелген нерселердин тизмесинин демо версиясы"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1-тизме"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2-тизме"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Ар бир тизмеге берилген кошумча текст"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Башка үлгүлөрдүн демолору"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase демолору"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Үлгү калыптарынын демолору"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
index ce02e87..28e6f5e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lo/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ນອກຈາກນັ້ນຍັງເລືອກແຖວໄດ້ນຳ"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ເລືອກຄຳສັ່ງສຳຮອງຂອງແຖວແລ້ວ"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"ເລືອກຄຳສັ່ງຫຼັກຂອງແຖວແລ້ວ"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"ເດໂມລາຍຊື່ລາຍການຕາມພາກສ່ວນ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ລາຍຊື່ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ລາຍຊື່ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ຂໍ້ຄວາມຍ່ອຍພາຍໃຕ້ແຕ່ລະລາຍຊື່"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"ເດໂມແມ່ແບບອື່ນໆ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ເດໂມ Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"ເດໂມໂຄງຮ່າງແມ່ແບບ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
index 6d37a20..8132540 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lt/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Taip pat galima pasirinkti eilutę"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Pasirinktas antrinis veiksmas"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Pasirinktas pagrindinis eilutės veiksmas"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Į skiltis suskirstyto elementų sąrašo demonstracinė versija"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1 sąrašas"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2 sąrašas"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Kiekvieno sąrašo paantraštė"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Įvairių šablonų demonstracinės versijos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Rodyti demonstracines versijas"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Šablonų išdėstymo demonstracinės versijos"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
index ed7b1e4..e7f6ba0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-lv/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Arī rindu var atlasīt"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Atlasīta sekundārā darbība"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Atlasīta rindas primārā darbība"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Vienumu saraksts ar sadaļām: demonstrācija"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1. saraksts"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2. saraksts"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Katra saraksta apakšteksts"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Dažādu veidņu demonstrācijas"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase demonstrācijas"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Veidņu izkārtojumu demonstrācijas"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
index 96a8a17..44e1a78 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mk/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Редот исто така може да се избере"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Избрано е секундарно дејство"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Избрано е примарно дејство на ред"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Демо на поделен список со ставки"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Список 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Список 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Поттекст под секој список"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Разни демоа за шаблони"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Демоа за прикажување"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Демоа за распоред на шаблони"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
index 86266b6..88e1f7e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ml/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"വരിയും തിരഞ്ഞെടുക്കാവുന്നതാണ്"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"സെക്കൻഡറി പ്രവർത്തനം തിരഞ്ഞെടുത്തു"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"വരിയുടെ പ്രാഥമിക പ്രവർത്തനം തിരഞ്ഞെടുത്തു"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"വിഭാഗങ്ങളാക്കിയ ഇന ലിസ്‌റ്റിന്റെ ഡെമോ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ലിസ്റ്റ് 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ലിസ്റ്റ് 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ഓരോ ലിസ്‌റ്റിന് കീഴിലും സബ്‌ടെക്‌സ്‌റ്റ്"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"പലവക ടെംപ്ലേറ്റ് ഡെമോകൾ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ഡെമോകൾ ഷോക്കേസ് ചെയ്യുക"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"ടെംപ്ലേറ്റ് ലേഔട്ട് ഡെമോകൾ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
index 3b2a8d15..f2dc5c0 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mn/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Мөрийг мөн сонгож болно"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Хоёрдогч үйлдлийг сонгосон"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Мөрийн үндсэн үйлдлийг сонгосон"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Хэсэгчилсэн зүйлийн жагсаалтын демо"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1-р жагсаалт"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2-р жагсаалт"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Жагсаалт тус бүрийн доорх дэд текст"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Холимог загварын демо"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase-н демо"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Загварын бүдүүвч бүхий демо"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
index 9a244f1..d5010fe 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-mr/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"पंक्तीदेखील निवडली जाऊ शकते"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"दुय्यम कृती निवडली आहे"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"पंक्तीशी संबंधित प्राथमिक कृती निवडली आहे"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"आयटमच्या सेक्शन्ड सूचीचा डेमो"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"पहिली सूची"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"दुसरी सूची"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"प्रत्येक सूचीच्या खाली सबटेक्स्ट"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"इतर टेंप्लेटचे डेमो"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"डेमो दाखवा"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"टेंप्लेट लेआउट डेमो"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
index 9666b40..6470ccc 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ms/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Baris ini juga boleh dipilih"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Tindakan Sekunder dipilih"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Tindakan utama baris dipilih"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo Senarai Item Berbahagian"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Senarai 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Senarai 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subteks dalam setiap senarai"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Pelbagai Demo Templat"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demo Wadah"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo Reka Letak Templat"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
index fc38283..1fe586c 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-my/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"အတန်းကိုလည်း ရွေးနိုင်သည်"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ဒုတိယဦးစားပေး လုပ်ဆောင်ချက်ကို ရွေးထားသည်"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"အတန်း၏ ပထမဦးစားပေး လုပ်ဆောင်ချက်ကို ရွေးထားသည်"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"အပိုင်းခွဲထားသည့် ဖိုင်စာရင်းသရုပ်ပြချက်"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"စာရင်း ၁"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"စာရင်း ၂"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"စာရင်းတစ်ခုစီ၏အောက်ရှိ ရှင်းလင်းချက်"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"အထွေထွေ ပုံစံသရုပ်ပြချက်များ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase သရုပ်ပြချက်များ"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"နမူနာပုံစံ သရုပ်ပြချက်များ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
index c7bb314..e153193 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-nb/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Raden kan også velges"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekundærhandlingen er valgt"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Hovedhandlingen til raden er valgt"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo av inndelt elementliste"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Undertekst under hver liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demoer av diverse maler"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demoer i fokus"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demoer av mallayout"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
index ec6512e..1b4389e 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ne/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"रो पनि चयन गर्न सकिन्छ"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"सेकेन्डरी एक्सन चयन गरिएको छ"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"रोको प्राइमरी एक्सन चयन गरिएको छ"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"खण्डमा भएको सामग्रीको सूचीको डेमो"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"सूची १"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"सूची २"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"प्रत्येक सूचीको मुनि रहेको सबटेक्स्ट"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"टेम्प्लेटका विविध डेमोहरू"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"सोकेसहरूको डेमो"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"टेम्प्लेट लेआउटका डेमोहरू"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
index bd0c00d..72e7ec8 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-nl/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"De rij kan ook worden geselecteerd"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Secundaire actie is geselecteerd"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Rij voor primaire actie is geselecteerd"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Lijstdemo voor gesegmenteerde items"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lijst 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lijst 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtekst onder elke lijst"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Diverse templatedemo\'s"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demo\'s laten zien"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo\'s voor opmaaktemplate"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
index dc1b85c..e9f4767 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-or/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ଏହି ଧାଡ଼ିଟିକୁ ମଧ୍ୟ ଚୟନ କରାଯାଇପାରିବ"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ଦ୍ୱିତୀୟ କାର୍ଯ୍ୟ ଚୟନ କରାଯାଇଛି"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"ଧାଡ଼ିର ପ୍ରାଥମିକ କାର୍ଯ୍ୟ ଚୟନ କରାଯାଇଛି"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"ବିଭାଗୀକୃତ ଆଇଟମ ତାଲିକା ଡେମୋ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ତାଲିକା 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ତାଲିକା 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ପ୍ରତ୍ୟେକ ତାଲିକା ତଳେ ସବଟେକ୍ସଟ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"ବିବିଧ ଟେମ୍ପଲେଟର ଡେମୋ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Showcase ଡେମୋ"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"ଟେମ୍ପଲେଟ ଲେଆଉଟର ଡେମୋଗୁଡ଼ିକ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
index 4ebb888..66be396 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pa/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"ਕਤਾਰ ਨੂੰ ਵੀ ਚੁਣਿਆ ਜਾ ਸਕਦਾ ਹੈ"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ਸੈਕੰਡਰੀ ਕਾਰਵਾਈ ਚੁਣੀ ਗਈ ਹੈ"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"ਕਤਾਰ ਨਾਲ ਸੰਬੰਧਿਤ ਪ੍ਰਾਇਮਰੀ ਕਾਰਵਾਈ ਚੁਣੀ ਗਈ ਹੈ"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"ਸੈਕਸ਼ਨਬੱਧ ਆਈਟਮ ਦੀ ਸੂਚੀ ਦਾ ਡੈਮੋ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ਸੂਚੀ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ਸੂਚੀ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ਹਰੇਕ ਸੂਚੀ ਦੇ ਹੇਠਾਂ ਸਬਟੈਕਸਟ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"ਫੁਟਕਲ ਟੈਮਪਲੇਟਾਂ ਦੇ ਡੈਮੋ"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ਸ਼ੋਅਕੇਸ ਦੇ ਡੈਮੋ"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"ਟੈਮਪਲੇਟ ਖਾਕੇ ਦੇ ਡੈਮੋ"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
index 9430cbd..c737a55 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pl/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Można wybrać również wiersz"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Wybrano działanie alternatywne"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Wybrano wiersz z działaniem głównym"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Wersja demonstracyjna podzielonej listy elementów"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Tekst pod każdą listą"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Wersje demonstracyjne różnych szablonów"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Wersje demonstracyjne do prezentacji"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Wersje demonstracyjne szablonów układu"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rBR/strings.xml
index 1d12153..cf867f8 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
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"A linha também pode ser selecionada"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"A ação secundária está selecionada"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"A ação principal da linha está selecionada"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstração de lista de itens com seções"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtexto abaixo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstração de modelos diversos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstrações em destaque"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrações de modelos de layout"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
index c45acb4..b79330d 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-pt-rPT/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Também pode selecionar a linha"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Ação secundária selecionada"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Ação principal da linha selecionada"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstração da lista de itens em secções"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtexto abaixo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstrações de modelos diversos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstrações de destaque"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrações do esquema do modelo"</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 1d12153..cf867f8 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
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"A linha também pode ser selecionada"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"A ação secundária está selecionada"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"A ação principal da linha está selecionada"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstração de lista de itens com seções"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtexto abaixo de cada lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstração de modelos diversos"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstrações em destaque"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrações de modelos de layout"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
index 6a3165c..4fe39dc 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ro/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Și rândul poate fi selectat"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Acțiunea secundară a fost selectată"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Acțiunea principală de pe rând a fost selectată"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstrație cu listă de articole cu secțiuni"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtextul de sub fiecare listă"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstrații diverse cu șabloane"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstrații pentru Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrații cu aspecte de șablon"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
index e63a368..e1791af 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ru/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Можно также выбрать строку"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Выбрано второстепенное действие."</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Выбрано главное действие для строки."</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Демонстрация списка объектов с разделами"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Список 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Список 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Описание под каждым списком"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Демонстрации прочих шаблонов"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Демонстрации Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Макет шаблона (режим демонстрации)"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
index 987c118..e2af18a 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-si/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"පේළිය ද තෝරා ගත හැක"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ද්විතියික ක්‍රියාව තෝරා ඇත"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"පේළියේ මූලික ක්‍රියාව තෝරා ඇත"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"කොටස් කරන ලද අයිතම ලැයිස්තු ආදර්ශනය"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"ලැයිස්තුව 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"ලැයිස්තුව 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"එක් එක් ලැයිස්තුව යටතේ උප පෙළ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"විවිධ අච්චු ආදර්ශන"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ප්‍රකාශක තේරූ ආදර්ශන"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"අච්චු පිරිසැලසුම් ආදර්ශන"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
index 9dbed37..14f88e9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sk/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Môžete vybrať aj daný riadok"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Bola vybraná sekundárna akcia"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Bola vybraná primárna akcia riadka"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Ukážka sekcií zoznamu položiek"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"1. zoznam"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"2. zoznam"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Podtext pod každým zoznamom"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Rôzne šablóny – demá"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Výber – demá"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demo rozložení šablóny"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
index 8c4b518..aefe212 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sl/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Izbrati je mogoče tudi vrstico."</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Sekundarno dejanje je izbrano."</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Glavno dejanje vrstice je izbrano."</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Predstavitvena različica seznama elementov po razdelkih"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Seznam 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Seznam 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Podbesedilo pod posameznim seznamom"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Predstavitvene različice različnih predlog"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Predstavitvene različice izpostavljenih stvari"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Predstavitvene različice postavitev predlog"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
index 3d2c569..301e3ec 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sq/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Rreshti mund të zgjidhet gjithashtu"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"U zgjodh veprimi dytësor"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"U zgjodh veprimi kryesor i rreshtit"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demonstrimi i listës me artikuj me seksione"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Nënteksti poshtë çdo liste"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demonstrime shabllonesh të ndryshme"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Demonstrime të prezantimit"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demonstrimet e strukturës së shabllonit"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
index afe1cac..4c63418 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sr/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Може да се изабере и ред"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Секундарна радња је изабрана"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Примарна радња реда је изабрана"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Демонстрација листе за ставке са одељцима"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Листа 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Листа 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Подтекст испод сваке листе"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Демонстрације различитих шаблона"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Демонстрације приказивања"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Демонстрације изгледа шаблона"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
index 97554dc..0ffb8d6 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sv/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Det går även att välja raden"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Den sekundära åtgärden har valts"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Radens primära åtgärd har valts"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo för objektlista indelad i sektioner"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Lista 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Lista 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Undertext under varje lista"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Demor för övriga mallar"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Visa demor"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Demor för mallayouter"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
index bb176a1..2fb1bb3 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-sw/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Safu mlalo pia inaweza kuchaguliwa"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Kitendo cha Upili kimechaguliwa"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Kitendo cha msingi cha safu mlalo kimechaguliwa"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Toleo la Kujaribu Orodha ya Vipengee yenye Vijisehemu"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Orodha ya 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Orodha ya 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Dokezo chini ya kila orodha"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Maonyesho ya Violezo Anuwai"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Maonyesho ya Kuangazia"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Maonyesho ya Kiolezo cha Muundo"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
index f26cd28..cf9a242 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ta/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"வரிசையையும் தேர்ந்தெடுக்க முடியும்"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"இரண்டாம்நிலைச் செயல் தேர்ந்தெடுக்கப்பட்டது"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"வரிசையின் முதன்மைச் செயல் தேர்ந்தெடுக்கப்பட்டது"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"பிரிக்கப்பட்ட உருப்படி பட்டியலின் டெமோ"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"பட்டியல் 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"பட்டியல் 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ஒவ்வொரு பட்டியலுக்கும் கீழுள்ள சுருக்க விவரம்"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"மற்ற டெம்ப்ளேட்டுகளின் டெமோக்கள்"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ஷோகேஸின் டெமோக்கள்"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"டெம்ப்ளேட் தளவமைப்பின் டெமோக்கள்"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
index 44fe516..48c2552 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-te/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"అడ్డు వరుసను కూడా ఎంచుకోవచ్చు"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"రెండవ చర్య ఎంచుకోబడింది"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"అడ్డు వరుస ప్రాథమిక చర్య ఎంచుకోబడింది"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"విభజించిన ఐటెమ్ లిస్ట్ డెమో"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"లిస్ట్ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"లిస్ట్ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ప్రతి లిస్ట్ కింద సబ్‌టెక్స్ట్"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"ఇతర టెంప్లేట్‌ల డెమోలు"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"డెమోలను ప్రదర్శించండి"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"టెంప్లేట్ లేఅవుట్ డెమోలు"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
index e6a22e3..b2d06cb 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-th/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"เลือกแถวได้ด้วย"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"เลือกการดำเนินการสำรองแล้ว"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"เลือกการดำเนินการหลักของแถวแล้ว"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"หน้าจอสาธิตชุดรายการแบบแยกเป็นส่วน"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"รายการที่ 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"รายการที่ 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ข้อความย่อยข้างใต้แต่ละรายการ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"การสาธิตเทมเพลตเบ็ดเตล็ด"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"การสาธิต Showcase"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"การสาธิตเลย์เอาต์เทมเพลต"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
index c7b20e5..d7db66f 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-tl/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Puwede ring piliin ang row"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Napili ang Pangalawahang Pagkilos"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Napili ang pangunahing pagkilos ng row"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Demo ng Nakaseksyong Listahan ng Item"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Listahan 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Listahan 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Subtext sa ilalim ng bawat listahan"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Iba Pang Demo ng Mga Template"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Itampok ang Mga Demo"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Mga Demo ng Layout ng Template"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
index 8a7782e..4c965fb 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-tr/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Satır da seçilebilir"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"İkincil işlem seçildi"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Satırdaki birincil işlem seçildi"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Bölümlere Ayrılmış Öğe Listesi Demosu"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Liste 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Liste 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Her listenin altında alt metin"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Çeşitli Şablon Demoları"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Öne Çıkan Demoları"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Şablon Düzeni Demoları"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
index d1fff04..3e7b486 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-uk/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Рядок також можна вибрати"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Вибрано додаткову дію"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Вибрано основну дію рядка"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Демонстрація списку об’єктів із розділами"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Список 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Список 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Додатковий текст під кожним списком"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Демонстрації інших шаблонів"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Увімкнути демонстрації"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Демонстрації макетів шаблонів"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
index f984a54..438c100 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-ur/strings.xml
@@ -327,14 +327,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"قطار بھی منتخب کی جا سکتی ہے"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"ثانوی کارروائی کا انتخاب کیا گیا ہے"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"قطار کی بنیادی کارروائی کا انتخاب کیا گیا ہے"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"سیکشن کردہ آئٹم کی فہرست کا ڈیمو"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"فہرست 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"فہرست 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"ہر فہرست کے نیچے ذیلی ٹیکسٹ"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"متفرق تمثیلات کے ڈیموز"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"ڈیموز دکھائیں"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"تمثیل کے لے آؤٹ کے ڈیموز"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
index 688a15d..491d966 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-uz/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Qator ham tanlanishi mumkin"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Ikkilamchi amal tanlandi"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Asosiy amal qatori tanlandi"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Boʻlimlarga ajratilgan elementlar roʻyxati namoyishi"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Roʻyxat 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Roʻyxat 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Har bir roʻyxat ostida quyi matn"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Aralash andozalar demolari"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Namoyish demolari"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Andoza dizayni demolari"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
index 2e30bcb..e386507 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-vi/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Bạn cũng có thể chọn hàng"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Đã chọn hành động phụ"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Đã chọn hành động chính cho hàng"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Bản minh hoạ danh sách mục theo phần"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Danh sách 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Danh sách 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Nội dung phụ dưới mỗi danh sách"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Bản demo biểu mẫu Misc"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Bản demo nổi bật"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Bản minh hoạ bố cục mẫu"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
index fc75a82..a5ecad9 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rCN/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"也可以选择行"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"次要操作处于选中状态"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"行主要操作处于选中状态"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"分节项目列表演示"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"列表 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"列表 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"每个列表下的辅助文本"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"其他模板演示"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"展示演示"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"模板布局演示"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
index ccef93f..0a10a88 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rHK/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"亦可選取列"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"已選取次要動作"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"已選取列的主要動作"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"分類項目清單示範"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"清單 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"清單 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"每個清單的說明文字"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"其他範本示範"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"展示示範"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"「範本版面配置」示範"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
index 5640ae1..a91421b 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zh-rTW/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"也可以選取列"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"已選取次要動作"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"已選取列的主要動作"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"分節項目清單示範"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"清單 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"清單 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"每個清單底下的子文字"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"其他範本示範"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"「展示」示範"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"「範本版面配置」示範"</string>
diff --git a/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml b/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
index 656ccb5a..b0147da 100644
--- a/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
+++ b/car/app/app-samples/showcase/common/src/main/res/values-zu/strings.xml
@@ -323,14 +323,10 @@
     <string name="secondary_actions_decoration_test_subtitle" msgid="155884606592724532">"Umugqa ungabuye ukhethwe"</string>
     <string name="secondary_action_toast" msgid="5076434693504006565">"Isenzo Sesibili Sikhethiwe"</string>
     <string name="row_primary_action_toast" msgid="756516694751965204">"Isenzo esiyinhloko somugqa sikhethiwe"</string>
-    <!-- no translation found for sectioned_item_list_demo_title (1236735461225007729) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_one_title (6594204350307263338) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_two_title (4941545381743022833) -->
-    <skip />
-    <!-- no translation found for sectioned_item_list_subtext (2963927693547537519) -->
-    <skip />
+    <string name="sectioned_item_list_demo_title" msgid="1236735461225007729">"Idemo yento ekuhlu efakwe esigabeni"</string>
+    <string name="sectioned_item_list_one_title" msgid="6594204350307263338">"Uhlu 1"</string>
+    <string name="sectioned_item_list_two_title" msgid="4941545381743022833">"Uhlu 2"</string>
+    <string name="sectioned_item_list_subtext" msgid="2963927693547537519">"Umbhalo ongaphansi kohlu ngalunye"</string>
     <string name="misc_templates_demos_title" msgid="6077169010255928114">"Amademo Ezifanekiso Ezixubile"</string>
     <string name="showcase_demos_title" msgid="1542092687878113304">"Bonisa Amademo"</string>
     <string name="template_layouts_demo_title" msgid="788249269446087847">"Ama-demo Esakhiwo Sesifanekiso"</string>
diff --git a/cardview/OWNERS b/cardview/OWNERS
index bc07458..83166ee 100644
--- a/cardview/OWNERS
+++ b/cardview/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 461354
-alanv@google.com
\ No newline at end of file
+alanv@google.com
+aelias@google.com
+ryanmentley@google.com
diff --git a/collection/OWNERS b/collection/OWNERS
index 1c323f5..1ffdba3 100644
--- a/collection/OWNERS
+++ b/collection/OWNERS
@@ -2,3 +2,5 @@
 lukhnos@google.com
 dustinlam@google.com
 yboyar@google.com
+aelias@google.com
+ryanmentley@google.com
diff --git a/compose/animation/animation/api/current.txt b/compose/animation/animation/api/current.txt
index 9b1560f..e836634 100644
--- a/compose/animation/animation/api/current.txt
+++ b/compose/animation/animation/api/current.txt
@@ -5,6 +5,38 @@
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> defaultDecayAnimationSpec();
   }
 
+  public final class AnimatedContentKt {
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method public static androidx.compose.animation.SizeTransform SizeTransform(optional boolean clip, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.IntSize,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize>> sizeAnimationSpec);
+    method public static infix androidx.compose.animation.ContentTransform with(androidx.compose.animation.EnterTransition, androidx.compose.animation.ExitTransition exit);
+  }
+
+  public sealed interface AnimatedContentTransitionScope<S> extends androidx.compose.animation.core.Transition.Segment<S> {
+    method public androidx.compose.animation.EnterTransition slideIntoContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialOffset);
+    method public androidx.compose.animation.ExitTransition slideOutOfContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetOffset);
+    method public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public static final value class AnimatedContentTransitionScope.SlideDirection {
+    field public static final androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion Companion;
+  }
+
+  public static final class AnimatedContentTransitionScope.SlideDirection.Companion {
+    method public int getDown();
+    method public int getEnd();
+    method public int getLeft();
+    method public int getRight();
+    method public int getStart();
+    method public int getUp();
+    property public final int Down;
+    property public final int End;
+    property public final int Left;
+    property public final int Right;
+    property public final int Start;
+    property public final int Up;
+  }
+
   public final class AnimatedVisibilityKt {
     method @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
@@ -25,6 +57,19 @@
     method public static kotlin.jvm.functions.Function1<androidx.compose.ui.graphics.colorspace.ColorSpace,androidx.compose.animation.core.TwoWayConverter<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D>> getVectorConverter(androidx.compose.ui.graphics.Color.Companion);
   }
 
+  public final class ContentTransform {
+    ctor public ContentTransform(androidx.compose.animation.EnterTransition targetContentEnter, androidx.compose.animation.ExitTransition initialContentExit, optional float targetContentZIndex, optional androidx.compose.animation.SizeTransform? sizeTransform);
+    method public androidx.compose.animation.ExitTransition getInitialContentExit();
+    method public androidx.compose.animation.SizeTransform? getSizeTransform();
+    method public androidx.compose.animation.EnterTransition getTargetContentEnter();
+    method public float getTargetContentZIndex();
+    method public void setTargetContentZIndex(float);
+    property public final androidx.compose.animation.ExitTransition initialContentExit;
+    property public final androidx.compose.animation.SizeTransform? sizeTransform;
+    property public final androidx.compose.animation.EnterTransition targetContentEnter;
+    property public final float targetContentZIndex;
+  }
+
   public final class CrossfadeKt {
     method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional String label, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit> content);
@@ -36,6 +81,8 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical expandFrom, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialHeight);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition fadeIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialAlpha);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition fadeOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetAlpha);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition scaleIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialScale, optional long transformOrigin);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition scaleOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetScale, optional long transformOrigin);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkHorizontally(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Horizontal shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetWidth);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkOut(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> targetSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetHeight);
@@ -73,6 +120,12 @@
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color> animateColorAsState(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,? extends kotlin.Unit>? finishedListener);
   }
 
+  public interface SizeTransform {
+    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> createAnimationSpec(long initialSize, long targetSize);
+    method public boolean getClip();
+    property public abstract boolean clip;
+  }
+
   public final class SplineBasedDecayKt {
     method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> splineBasedDecay(androidx.compose.ui.unit.Density density);
   }
diff --git a/compose/animation/animation/api/public_plus_experimental_current.txt b/compose/animation/animation/api/public_plus_experimental_current.txt
index f1c9ff2..b192211 100644
--- a/compose/animation/animation/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation/api/public_plus_experimental_current.txt
@@ -6,28 +6,23 @@
   }
 
   public final class AnimatedContentKt {
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
-    method @Deprecated @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<S>,? extends androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,? extends kotlin.Unit> content);
-    method @androidx.compose.animation.ExperimentalAnimationApi public static androidx.compose.animation.SizeTransform SizeTransform(optional boolean clip, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.IntSize,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize>> sizeAnimationSpec);
-    method @androidx.compose.animation.ExperimentalAnimationApi public static infix androidx.compose.animation.ContentTransform with(androidx.compose.animation.EnterTransition, androidx.compose.animation.ExitTransition exit);
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method public static androidx.compose.animation.SizeTransform SizeTransform(optional boolean clip, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.IntSize,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize>> sizeAnimationSpec);
+    method public static infix androidx.compose.animation.ContentTransform with(androidx.compose.animation.EnterTransition, androidx.compose.animation.ExitTransition exit);
   }
 
-  @androidx.compose.animation.ExperimentalAnimationApi public final class AnimatedContentScope<S> implements androidx.compose.animation.core.Transition.Segment<S> {
-    method public S! getInitialState();
-    method public S! getTargetState();
+  public sealed interface AnimatedContentTransitionScope<S> extends androidx.compose.animation.core.Transition.Segment<S> {
     method public androidx.compose.animation.EnterTransition slideIntoContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialOffset);
     method public androidx.compose.animation.ExitTransition slideOutOfContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetOffset);
-    method @androidx.compose.animation.ExperimentalAnimationApi public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
-    property public S! initialState;
-    property public S! targetState;
+    method public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
   }
 
-  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public static final value class AnimatedContentScope.SlideDirection {
-    field public static final androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion Companion;
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public static final value class AnimatedContentTransitionScope.SlideDirection {
+    field public static final androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion Companion;
   }
 
-  public static final class AnimatedContentScope.SlideDirection.Companion {
+  public static final class AnimatedContentTransitionScope.SlideDirection.Companion {
     method public int getDown();
     method public int getEnd();
     method public int getLeft();
@@ -67,7 +62,7 @@
     method public static kotlin.jvm.functions.Function1<androidx.compose.ui.graphics.colorspace.ColorSpace,androidx.compose.animation.core.TwoWayConverter<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D>> getVectorConverter(androidx.compose.ui.graphics.Color.Companion);
   }
 
-  @androidx.compose.animation.ExperimentalAnimationApi public final class ContentTransform {
+  public final class ContentTransform {
     ctor public ContentTransform(androidx.compose.animation.EnterTransition targetContentEnter, androidx.compose.animation.ExitTransition initialContentExit, optional float targetContentZIndex, optional androidx.compose.animation.SizeTransform? sizeTransform);
     method public androidx.compose.animation.ExitTransition getInitialContentExit();
     method public androidx.compose.animation.SizeTransform? getSizeTransform();
@@ -100,8 +95,8 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical expandFrom, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialHeight);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition fadeIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialAlpha);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition fadeOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetAlpha);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition scaleIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialScale, optional long transformOrigin);
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition scaleOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetScale, optional long transformOrigin);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition scaleIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialScale, optional long transformOrigin);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition scaleOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetScale, optional long transformOrigin);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkHorizontally(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Horizontal shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetWidth);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkOut(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> targetSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetHeight);
@@ -142,7 +137,7 @@
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color> animateColorAsState(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,? extends kotlin.Unit>? finishedListener);
   }
 
-  @androidx.compose.animation.ExperimentalAnimationApi public interface SizeTransform {
+  public interface SizeTransform {
     method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> createAnimationSpec(long initialSize, long targetSize);
     method public boolean getClip();
     property public abstract boolean clip;
diff --git a/compose/animation/animation/api/restricted_current.txt b/compose/animation/animation/api/restricted_current.txt
index 9b1560f..e836634 100644
--- a/compose/animation/animation/api/restricted_current.txt
+++ b/compose/animation/animation/api/restricted_current.txt
@@ -5,6 +5,38 @@
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.animation.core.DecayAnimationSpec<java.lang.Float> defaultDecayAnimationSpec();
   }
 
+  public final class AnimatedContentKt {
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(S? targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional String label, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public static <S> void AnimatedContent(androidx.compose.animation.core.Transition<S>, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<S>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, optional kotlin.jvm.functions.Function1<? super S,?> contentKey, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super S,kotlin.Unit> content);
+    method public static androidx.compose.animation.SizeTransform SizeTransform(optional boolean clip, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.IntSize,? super androidx.compose.ui.unit.IntSize,? extends androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize>> sizeAnimationSpec);
+    method public static infix androidx.compose.animation.ContentTransform with(androidx.compose.animation.EnterTransition, androidx.compose.animation.ExitTransition exit);
+  }
+
+  public sealed interface AnimatedContentTransitionScope<S> extends androidx.compose.animation.core.Transition.Segment<S> {
+    method public androidx.compose.animation.EnterTransition slideIntoContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialOffset);
+    method public androidx.compose.animation.ExitTransition slideOutOfContainer(int towards, optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntOffset> animationSpec, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetOffset);
+    method public infix androidx.compose.animation.ContentTransform using(androidx.compose.animation.ContentTransform, androidx.compose.animation.SizeTransform? sizeTransform);
+  }
+
+  @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public static final value class AnimatedContentTransitionScope.SlideDirection {
+    field public static final androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion Companion;
+  }
+
+  public static final class AnimatedContentTransitionScope.SlideDirection.Companion {
+    method public int getDown();
+    method public int getEnd();
+    method public int getLeft();
+    method public int getRight();
+    method public int getStart();
+    method public int getUp();
+    property public final int Down;
+    property public final int End;
+    property public final int Left;
+    property public final int Right;
+    property public final int Start;
+    property public final int Up;
+  }
+
   public final class AnimatedVisibilityKt {
     method @androidx.compose.runtime.Composable public static void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public static void AnimatedVisibility(androidx.compose.foundation.layout.RowScope, boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
@@ -25,6 +57,19 @@
     method public static kotlin.jvm.functions.Function1<androidx.compose.ui.graphics.colorspace.ColorSpace,androidx.compose.animation.core.TwoWayConverter<androidx.compose.ui.graphics.Color,androidx.compose.animation.core.AnimationVector4D>> getVectorConverter(androidx.compose.ui.graphics.Color.Companion);
   }
 
+  public final class ContentTransform {
+    ctor public ContentTransform(androidx.compose.animation.EnterTransition targetContentEnter, androidx.compose.animation.ExitTransition initialContentExit, optional float targetContentZIndex, optional androidx.compose.animation.SizeTransform? sizeTransform);
+    method public androidx.compose.animation.ExitTransition getInitialContentExit();
+    method public androidx.compose.animation.SizeTransform? getSizeTransform();
+    method public androidx.compose.animation.EnterTransition getTargetContentEnter();
+    method public float getTargetContentZIndex();
+    method public void setTargetContentZIndex(float);
+    property public final androidx.compose.animation.ExitTransition initialContentExit;
+    property public final androidx.compose.animation.SizeTransform? sizeTransform;
+    property public final androidx.compose.animation.EnterTransition targetContentEnter;
+    property public final float targetContentZIndex;
+  }
+
   public final class CrossfadeKt {
     method @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional String label, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> content);
     method @Deprecated @androidx.compose.runtime.Composable public static <T> void Crossfade(T? targetState, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit> content);
@@ -36,6 +81,8 @@
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition expandVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical expandFrom, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> initialHeight);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition fadeIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialAlpha);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition fadeOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetAlpha);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.EnterTransition scaleIn(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float initialScale, optional long transformOrigin);
+    method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition scaleOut(optional androidx.compose.animation.core.FiniteAnimationSpec<java.lang.Float> animationSpec, optional float targetScale, optional long transformOrigin);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkHorizontally(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Horizontal shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetWidth);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkOut(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,androidx.compose.ui.unit.IntSize> targetSize);
     method @androidx.compose.runtime.Stable public static androidx.compose.animation.ExitTransition shrinkVertically(optional androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> animationSpec, optional androidx.compose.ui.Alignment.Vertical shrinkTowards, optional boolean clip, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,java.lang.Integer> targetHeight);
@@ -73,6 +120,12 @@
     method @Deprecated @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color> animateColorAsState(long targetValue, optional androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color> animationSpec, optional kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,? extends kotlin.Unit>? finishedListener);
   }
 
+  public interface SizeTransform {
+    method public androidx.compose.animation.core.FiniteAnimationSpec<androidx.compose.ui.unit.IntSize> createAnimationSpec(long initialSize, long targetSize);
+    method public boolean getClip();
+    property public abstract boolean clip;
+  }
+
   public final class SplineBasedDecayKt {
     method public static <T> androidx.compose.animation.core.DecayAnimationSpec<T> splineBasedDecay(androidx.compose.ui.unit.Density density);
   }
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/NestedMenuDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/NestedMenuDemo.kt
index f36690d..8317633 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/NestedMenuDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/NestedMenuDemo.kt
@@ -17,7 +17,7 @@
 package androidx.compose.animation.demos.layoutanimation
 
 import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope.SlideDirection
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.with
 import androidx.compose.foundation.background
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ScreenTransitionDemo.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ScreenTransitionDemo.kt
index 2be5c38..0454eb6 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ScreenTransitionDemo.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/layoutanimation/ScreenTransitionDemo.kt
@@ -13,13 +13,11 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-@file:OptIn(ExperimentalAnimationApi::class)
 
 package androidx.compose.animation.demos.layoutanimation
 
 import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope.SlideDirection
-import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.expandHorizontally
@@ -53,7 +51,6 @@
 import androidx.compose.ui.unit.dp
 
 @Preview
-@OptIn(ExperimentalAnimationApi::class)
 @Composable
 fun ScreenTransitionDemo() {
     Column {
@@ -85,7 +82,7 @@
                 Text("Next screen")
             }
         }
-        val transition = updateTransition(targetScreen)
+        val transition = updateTransition(targetScreen, "screen transition")
         transition.AnimatedContent(
             transitionSpec = {
                 if (TestScreens.Screen1 isTransitioningTo TestScreens.Screen2 ||
diff --git a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/visualinspection/SlideInContentVariedSizes.kt b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/visualinspection/SlideInContentVariedSizes.kt
index 82d6078..e947060 100644
--- a/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/visualinspection/SlideInContentVariedSizes.kt
+++ b/compose/animation/animation/integration-tests/animation-demos/src/main/java/androidx/compose/animation/demos/visualinspection/SlideInContentVariedSizes.kt
@@ -17,10 +17,10 @@
 package androidx.compose.animation.demos.visualinspection
 
 import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Down
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Left
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Right
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Up
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Down
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Left
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Right
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Up
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.SizeTransform
 import androidx.compose.animation.with
diff --git a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
index eb72098..2947e60 100644
--- a/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
+++ b/compose/animation/animation/samples/src/main/java/androidx/compose/animation/samples/AnimatedContentSamples.kt
@@ -18,10 +18,9 @@
 
 import androidx.annotation.Sampled
 import androidx.compose.animation.AnimatedContent
-import androidx.compose.animation.AnimatedContentScope
-import androidx.compose.animation.AnimatedContentScope.SlideDirection
+import androidx.compose.animation.AnimatedContentTransitionScope
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection
 import androidx.compose.animation.ContentTransform
-import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.SizeTransform
 import androidx.compose.animation.core.animateDp
 import androidx.compose.animation.core.keyframes
@@ -58,7 +57,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 
-@OptIn(ExperimentalAnimationApi::class)
 @Composable
 @Sampled
 fun AnimateIncrementDecrementSample() {
@@ -106,7 +104,6 @@
     }
 }
 
-@OptIn(ExperimentalAnimationApi::class)
 @Composable
 @Sampled
 fun SimpleAnimatedContentSample() {
@@ -139,12 +136,11 @@
 
 private enum class ContentState { Foo, Bar, Baz }
 
-@OptIn(ExperimentalAnimationApi::class)
 @Suppress("UNUSED_VARIABLE")
 @Sampled
 fun AnimatedContentTransitionSpecSample() {
     // enum class CartState { Expanded, Collapsed }
-    val transitionSpec: AnimatedContentScope<CartState>.() -> ContentTransform =
+    val transitionSpec: AnimatedContentTransitionScope<CartState>.() -> ContentTransform =
         {
             // Fade in with a delay so that it starts after fade out
             fadeIn(animationSpec = tween(150, delayMillis = 150))
@@ -177,7 +173,6 @@
 
 @Sampled
 @Composable
-@OptIn(ExperimentalAnimationApi::class)
 fun TransitionExtensionAnimatedContentSample() {
     @Composable
     fun CollapsedCart() { /* Some content here */
@@ -264,7 +259,6 @@
 
 @Suppress("UNUSED_VARIABLE")
 @Sampled
-@OptIn(ExperimentalAnimationApi::class)
 @Composable
 fun SlideIntoContainerSample() {
     // enum class NestedMenuState { Level1, Level2, Level3 }
@@ -272,7 +266,7 @@
     // is to 1) establish a z-order for different levels of the menu, and 2) imply a spatial
     // order between the menus via the different slide direction when navigating to child menu vs
     // parent menu. See the demos directory of the source code for a full demo.
-    val transitionSpec: AnimatedContentScope<NestedMenuState>.() -> ContentTransform = {
+    val transitionSpec: AnimatedContentTransitionScope<NestedMenuState>.() -> ContentTransform = {
         if (initialState < targetState) {
             // Going from parent menu to child menu, slide towards left
             slideIntoContainer(towards = SlideDirection.Left) with
diff --git a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
index 79cbf93..9fb6011 100644
--- a/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
+++ b/compose/animation/animation/src/androidAndroidTest/kotlin/androidx/compose/animation/AnimatedContentTest.kt
@@ -45,7 +45,6 @@
 import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
-import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.unit.Density
@@ -67,13 +66,12 @@
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
-@OptIn(ExperimentalTestApi::class)
 class AnimatedContentTest {
 
     @get:Rule
     val rule = createComposeRule()
 
-    @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
+    @OptIn(InternalAnimationApi::class)
     @Test
     fun AnimatedContentSizeTransformTest() {
         val size1 = 40
@@ -153,7 +151,7 @@
         }
     }
 
-    @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
+    @OptIn(InternalAnimationApi::class)
     @Test
     fun AnimatedContentSizeTransformEmptyComposableTest() {
         val size1 = 160
@@ -214,7 +212,7 @@
         }
     }
 
-    @OptIn(ExperimentalAnimationApi::class, InternalAnimationApi::class)
+    @OptIn(InternalAnimationApi::class)
     @Test
     fun AnimatedContentContentAlignmentTest() {
         val size1 = IntSize(80, 80)
@@ -360,17 +358,17 @@
                     transitionSpec = {
                         if (true isTransitioningTo false) {
                             slideIntoContainer(
-                                towards = AnimatedContentScope.SlideDirection.Start, animSpec
+                                AnimatedContentTransitionScope.SlideDirection.Start, animSpec
                             ) with
                                 slideOutOfContainer(
-                                    towards = AnimatedContentScope.SlideDirection.Start, animSpec
+                                    AnimatedContentTransitionScope.SlideDirection.Start, animSpec
                                 )
                         } else {
                             slideIntoContainer(
-                                towards = AnimatedContentScope.SlideDirection.End, animSpec
+                                AnimatedContentTransitionScope.SlideDirection.End, animSpec
                             ) with
                                 slideOutOfContainer(
-                                    towards = AnimatedContentScope.SlideDirection.End,
+                                    towards = AnimatedContentTransitionScope.SlideDirection.End,
                                     animSpec
                                 )
                         }
@@ -437,7 +435,6 @@
         rule.onNodeWithTag("false").assertDoesNotExist()
     }
 
-    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun AnimatedContentWithKeysTest() {
         var targetState by mutableStateOf(1)
@@ -496,19 +493,25 @@
             AnimatedContent(targetState = flag,
                 modifier = Modifier.onGloballyPositioned { rootCoords = it },
                 transitionSpec = {
-                if (targetState) {
-                    fadeIn(tween(2000)) with slideOut(
-                        tween(2000)) { fullSize ->
-                        IntOffset(0, fullSize.height / 2) } + fadeOut(
-                        tween(2000))
-                } else {
-                    fadeIn(tween(2000)) with fadeOut(tween(2000))
-                }
-            }) { state ->
+                    if (targetState) {
+                        fadeIn(tween(2000)) with slideOut(
+                            tween(2000)
+                        ) { fullSize ->
+                            IntOffset(0, fullSize.height / 2)
+                        } + fadeOut(
+                            tween(2000)
+                        )
+                    } else {
+                        fadeIn(tween(2000)) with fadeOut(tween(2000))
+                    }
+                }) { state ->
                 if (state) {
                     Box(modifier = Modifier
                         .onGloballyPositioned {
-                            assertEquals(Offset.Zero, rootCoords!!.localPositionOf(it, Offset.Zero))
+                            assertEquals(
+                                Offset.Zero,
+                                rootCoords!!.localPositionOf(it, Offset.Zero)
+                            )
                         }
                         .fillMaxSize()
                         .background(Color.Green)
@@ -523,7 +526,10 @@
                     }
                     Box(modifier = Modifier
                         .onGloballyPositioned {
-                            assertEquals(Offset.Zero, rootCoords!!.localPositionOf(it, Offset.Zero))
+                            assertEquals(
+                                Offset.Zero,
+                                rootCoords!!.localPositionOf(it, Offset.Zero)
+                            )
                         }
                         .fillMaxSize()
                         .background(Color.Red)
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
index f4ce0bdf..9a51fc4 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/AnimatedContent.kt
@@ -18,12 +18,12 @@
 
 package androidx.compose.animation
 
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Down
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.End
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Left
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Right
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Start
-import androidx.compose.animation.AnimatedContentScope.SlideDirection.Companion.Up
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Down
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.End
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Left
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Right
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Start
+import androidx.compose.animation.AnimatedContentTransitionScope.SlideDirection.Companion.Up
 import androidx.compose.animation.core.AnimationVector2D
 import androidx.compose.animation.core.FiniteAnimationSpec
 import androidx.compose.animation.core.InternalAnimationApi
@@ -116,12 +116,11 @@
  * @see ContentTransform
  * @see AnimatedVisibilityScope
  */
-@ExperimentalAnimationApi
 @Composable
 fun <S> AnimatedContent(
     targetState: S,
     modifier: Modifier = Modifier,
-    transitionSpec: AnimatedContentScope<S>.() -> ContentTransform = {
+    transitionSpec: AnimatedContentTransitionScope<S>.() -> ContentTransform = {
         fadeIn(animationSpec = tween(220, delayMillis = 90)) +
             scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with
             fadeOut(animationSpec = tween(90))
@@ -139,33 +138,13 @@
     )
 }
 
+// TODO: Remove this before M7 stable. This is only intended for helping devs quick fix the
+// name change.
 @Deprecated(
-    "AnimatedContent API now has a new label parameter added.",
-    level = DeprecationLevel.HIDDEN
+    "AnimatedContentScope has been renamed to AnimatedContentTransitionScope",
+    replaceWith = ReplaceWith("AnimatedContentTransitionScope<S>")
 )
-
-@ExperimentalAnimationApi
-@Composable
-fun <S> AnimatedContent(
-    targetState: S,
-    modifier: Modifier = Modifier,
-    transitionSpec: AnimatedContentScope<S>.() -> ContentTransform = {
-        fadeIn(animationSpec = tween(220, delayMillis = 90)) +
-            scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with
-            fadeOut(animationSpec = tween(90))
-    },
-    contentAlignment: Alignment = Alignment.TopStart,
-    content: @Composable() AnimatedVisibilityScope.(targetState: S) -> Unit
-) {
-    AnimatedContent(
-        targetState,
-        modifier,
-        transitionSpec,
-        contentAlignment,
-        "AnimatedContent",
-        content
-    )
-}
+typealias AnimatedContentScope<S> = AnimatedContentTransitionScope<S>
 
 /**
  * [ContentTransform] defines how the target content (i.e. content associated with target state)
@@ -173,11 +152,12 @@
  *
  * [targetContentEnter] defines the enter transition for the content associated with the new
  * target state. It can be a combination of [fadeIn], [slideIn]/[slideInHorizontally]
- * /[slideInVertically]/[AnimatedContentScope.slideIntoContainer], and expand. Similarly,
+ * /[slideInVertically]/[AnimatedContentTransitionScope.slideIntoContainer], and expand. Similarly,
  * [initialContentExit] supports a combination of [ExitTransition] for animating out the initial
  * content (i.e. outgoing content). If the initial content and target content are of different
  * size, the [sizeTransform] will be triggered unless it's explicitly set to `null`.
- * [AnimatedContentScope.slideIntoContainer] and [AnimatedContentScope.slideOutOfContainer] can
+ * [AnimatedContentTransitionScope.slideIntoContainer] and
+ * [AnimatedContentTransitionScope.slideOutOfContainer] can
  * provide container-size-aware sliding in from the edge of the container, or sliding out to the
  * edge of the container.
  *
@@ -204,7 +184,6 @@
  * @see ExitTransition
  * @see AnimatedContent
  */
-@ExperimentalAnimationApi
 class ContentTransform(
     val targetContentEnter: EnterTransition,
     val initialContentExit: ExitTransition,
@@ -236,7 +215,6 @@
  *
  * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
  */
-@ExperimentalAnimationApi
 fun SizeTransform(
     clip: Boolean = true,
     sizeAnimationSpec: (initialSize: IntSize, targetSize: IntSize) -> FiniteAnimationSpec<IntSize> =
@@ -251,7 +229,6 @@
  *
  * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
  */
-@ExperimentalAnimationApi
 interface SizeTransform {
     /**
      * Whether the content should be clipped using the animated size.
@@ -268,7 +245,6 @@
 /**
  * Private implementation of SizeTransform interface.
  */
-@ExperimentalAnimationApi
 private class SizeTransformImpl(
     override val clip: Boolean = true,
     val sizeAnimationSpec:
@@ -287,43 +263,21 @@
  *
  * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
  */
-@ExperimentalAnimationApi
 infix fun EnterTransition.with(exit: ExitTransition) = ContentTransform(this, exit)
 
 /**
- * [AnimatedContentScope] provides functions that are convenient and only applicable in the
+ * [AnimatedContentTransitionScope] provides functions that are convenient and only applicable in the
  * context of [AnimatedContent], such as [slideIntoContainer] and [slideOutOfContainer].
  */
-@ExperimentalAnimationApi
-// TODO: Consider making AnimatedContentScope an interface before graduating it from experimental
-class AnimatedContentScope<S> internal constructor(
-    internal val transition: Transition<S>,
-    internal var contentAlignment: Alignment,
-    internal var layoutDirection: LayoutDirection
-) : Transition.Segment<S> {
-    /**
-     * Initial state of a Transition Segment. This is the state that transition starts from.
-     */
-    override val initialState: S
-        @Suppress("UnknownNullness")
-        get() = transition.segment.initialState
 
-    /**
-     * Target state of a Transition Segment. This is the state that transition will end on.
-     */
-    override val targetState: S
-        @Suppress("UnknownNullness")
-        get() = transition.segment.targetState
+sealed interface AnimatedContentTransitionScope<S> : Transition.Segment<S> {
 
     /**
      * Customizes the [SizeTransform] of a given [ContentTransform]. For example:
      *
      * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
      */
-    @ExperimentalAnimationApi
-    infix fun ContentTransform.using(sizeTransform: SizeTransform?) = this.apply {
-        this.sizeTransform = sizeTransform
-    }
+    infix fun ContentTransform.using(sizeTransform: SizeTransform?): ContentTransform
 
     /**
      * [SlideDirection] defines the direction of the slide in/out for [slideIntoContainer] and
@@ -382,42 +336,7 @@
             visibilityThreshold = IntOffset.VisibilityThreshold
         ),
         initialOffset: (offsetForFullSlide: Int) -> Int = { it }
-    ): EnterTransition =
-        when {
-            towards.isLeft -> slideInHorizontally(animationSpec) {
-                initialOffset.invoke(
-                    currentSize.width - calculateOffset(IntSize(it, it), currentSize).x
-                )
-            }
-            towards.isRight -> slideInHorizontally(animationSpec) {
-                initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).x - it)
-            }
-            towards == Up -> slideInVertically(animationSpec) {
-                initialOffset.invoke(
-                    currentSize.height - calculateOffset(IntSize(it, it), currentSize).y
-                )
-            }
-            towards == Down -> slideInVertically(animationSpec) {
-                initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).y - it)
-            }
-            else -> EnterTransition.None
-        }
-
-    private val SlideDirection.isLeft: Boolean
-        get() {
-            return this == Left || this == Start && layoutDirection == LayoutDirection.Ltr ||
-                this == End && layoutDirection == LayoutDirection.Rtl
-        }
-
-    private val SlideDirection.isRight: Boolean
-        get() {
-            return this == Right || this == Start && layoutDirection == LayoutDirection.Rtl ||
-                this == End && layoutDirection == LayoutDirection.Ltr
-        }
-
-    private fun calculateOffset(fullSize: IntSize, currentSize: IntSize): IntOffset {
-        return contentAlignment.align(fullSize, currentSize, LayoutDirection.Ltr)
-    }
+    ): EnterTransition
 
     /**
      * This defines a horizontal/vertical exit transition to completely slide out of the
@@ -446,6 +365,135 @@
             visibilityThreshold = IntOffset.VisibilityThreshold
         ),
         targetOffset: (offsetForFullSlide: Int) -> Int = { it }
+    ): ExitTransition
+}
+
+internal class AnimatedContentTransitionScopeImpl<S> internal constructor(
+    internal val transition: Transition<S>,
+    internal var contentAlignment: Alignment,
+    internal var layoutDirection: LayoutDirection
+) : AnimatedContentTransitionScope<S> {
+    /**
+     * Initial state of a Transition Segment. This is the state that transition starts from.
+     */
+    override val initialState: S
+        @Suppress("UnknownNullness")
+        get() = transition.segment.initialState
+
+    /**
+     * Target state of a Transition Segment. This is the state that transition will end on.
+     */
+    override val targetState: S
+        @Suppress("UnknownNullness")
+        get() = transition.segment.targetState
+
+    /**
+     * Customizes the [SizeTransform] of a given [ContentTransform]. For example:
+     *
+     * @sample androidx.compose.animation.samples.AnimatedContentTransitionSpecSample
+     */
+    override infix fun ContentTransform.using(sizeTransform: SizeTransform?) = this.apply {
+        this.sizeTransform = sizeTransform
+    }
+
+    /**
+     * This defines a horizontal/vertical slide-in that is specific to [AnimatedContent] from the
+     * edge of the container. The offset amount is dynamically calculated based on the current
+     * size of the [AnimatedContent] and its content alignment. This offset (may be positive or
+     * negative based on the direction of the slide) is then passed to [initialOffset]. By default,
+     * [initialOffset] will be using the offset calculated from the system to slide the content in.
+     * [slideIntoContainer] is a convenient alternative to [slideInHorizontally] and
+     * [slideInVertically] when the incoming and outgoing content
+     * differ in size. Otherwise, it would be equivalent to [slideInHorizontally] and
+     * [slideInVertically] with an offset of the full width/height.
+     *
+     * [towards] specifies the slide direction. Content can be slided into the container towards
+     * [AnimatedContentTransitionScope.SlideDirection.Left],
+     * [AnimatedContentTransitionScope.SlideDirection.Right],
+     * [AnimatedContentTransitionScope.SlideDirection.Up]
+     * and [AnimatedContentTransitionScope.SlideDirection.Down].
+     *
+     * [animationSpec] defines the animation that will be used to animate the slide-in.
+     *
+     * @sample androidx.compose.animation.samples.SlideIntoContainerSample
+     *
+     * @see AnimatedContent
+     * @see slideInHorizontally
+     * @see slideInVertically
+     */
+    override fun slideIntoContainer(
+        towards: AnimatedContentTransitionScope.SlideDirection,
+        animationSpec: FiniteAnimationSpec<IntOffset>,
+        initialOffset: (offsetForFullSlide: Int) -> Int
+    ): EnterTransition =
+        when {
+            towards.isLeft -> slideInHorizontally(animationSpec) {
+                initialOffset.invoke(
+                    currentSize.width - calculateOffset(IntSize(it, it), currentSize).x
+                )
+            }
+
+            towards.isRight -> slideInHorizontally(animationSpec) {
+                initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).x - it)
+            }
+
+            towards == Up -> slideInVertically(animationSpec) {
+                initialOffset.invoke(
+                    currentSize.height - calculateOffset(IntSize(it, it), currentSize).y
+                )
+            }
+
+            towards == Down -> slideInVertically(animationSpec) {
+                initialOffset.invoke(-calculateOffset(IntSize(it, it), currentSize).y - it)
+            }
+
+            else -> EnterTransition.None
+        }
+
+    private val AnimatedContentTransitionScope.SlideDirection.isLeft: Boolean
+        get() {
+            return this == Left || this == Start && layoutDirection == LayoutDirection.Ltr ||
+                this == End && layoutDirection == LayoutDirection.Rtl
+        }
+
+    private val AnimatedContentTransitionScope.SlideDirection.isRight: Boolean
+        get() {
+            return this == Right || this == Start && layoutDirection == LayoutDirection.Rtl ||
+                this == End && layoutDirection == LayoutDirection.Ltr
+        }
+
+    private fun calculateOffset(fullSize: IntSize, currentSize: IntSize): IntOffset {
+        return contentAlignment.align(fullSize, currentSize, LayoutDirection.Ltr)
+    }
+
+    /**
+     * This defines a horizontal/vertical exit transition to completely slide out of the
+     * [AnimatedContent] container. The offset amount is dynamically calculated based on the current
+     * size of the [AnimatedContent] and the new target size. This offset gets passed
+     * to [targetOffset] lambda. By default, [targetOffset] uses this offset as is, but it can be
+     * customized to slide a distance based on the offset. [slideOutOfContainer] is a
+     * convenient alternative to [slideOutHorizontally] and [slideOutVertically] when the incoming
+     * and outgoing content differ in size. Otherwise, it would be equivalent to
+     * [slideOutHorizontally] and [slideOutVertically] with an offset of the full width/height.
+     *
+     * [towards] specifies the slide direction. Content can be slided out of the container towards
+     * [AnimatedContentTransitionScope.SlideDirection.Left],
+     * [AnimatedContentTransitionScope.SlideDirection.Right],
+     * [AnimatedContentTransitionScope.SlideDirection.Up]
+     * and [AnimatedContentTransitionScope.SlideDirection.Down].
+     *
+     * [animationSpec] defines the animation that will be used to animate the slide-out.
+     *
+     * @sample androidx.compose.animation.samples.SlideIntoContainerSample
+     *
+     * @see AnimatedContent
+     * @see slideOutHorizontally
+     * @see slideOutVertically
+     */
+    override fun slideOutOfContainer(
+        towards: AnimatedContentTransitionScope.SlideDirection,
+        animationSpec: FiniteAnimationSpec<IntOffset>,
+        targetOffset: (offsetForFullSlide: Int) -> Int
     ): ExitTransition {
         return when {
             // Note: targetSize could be 0 for empty composables
@@ -453,6 +501,7 @@
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).x - it)
             }
+
             towards.isRight -> slideOutHorizontally(animationSpec) {
 
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
@@ -460,17 +509,20 @@
                     -calculateOffset(IntSize(it, it), targetSize).x + targetSize.width
                 )
             }
+
             towards == Up -> slideOutVertically(animationSpec) {
 
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(-calculateOffset(IntSize(it, it), targetSize).y - it)
             }
+
             towards == Down -> slideOutVertically(animationSpec) {
                 val targetSize = targetSizeMap[transition.targetState]?.value ?: IntSize.Zero
                 targetOffset.invoke(
                     -calculateOffset(IntSize(it, it), targetSize).y + targetSize.height
                 )
             }
+
             else -> ExitTransition.None
         }
     }
@@ -484,7 +536,6 @@
     private val currentSize: IntSize
         get() = animatedSize?.value ?: measuredSize
 
-    @OptIn(ExperimentalAnimationApi::class)
     @Suppress("ComposableModifierFactory", "ModifierFactoryExtensionFunction")
     @Composable
     internal fun createSizeAnimationModifier(
@@ -520,7 +571,6 @@
         }
     }
 
-    @ExperimentalAnimationApi
     private inner class SizeModifier(
         val sizeAnimation: Transition<S>.DeferredAnimation<IntSize, AnimationVector2D>,
         val sizeTransform: State<SizeTransform?>,
@@ -601,11 +651,11 @@
  * @see ContentTransform
  * @see AnimatedVisibilityScope
  */
-@ExperimentalAnimationApi
+@OptIn(ExperimentalAnimationApi::class)
 @Composable
 fun <S> Transition<S>.AnimatedContent(
     modifier: Modifier = Modifier,
-    transitionSpec: AnimatedContentScope<S>.() -> ContentTransform = {
+    transitionSpec: AnimatedContentTransitionScope<S>.() -> ContentTransform = {
         fadeIn(animationSpec = tween(220, delayMillis = 90)) +
             scaleIn(initialScale = 0.92f, animationSpec = tween(220, delayMillis = 90)) with
             fadeOut(animationSpec = tween(90))
@@ -616,7 +666,7 @@
 ) {
     val layoutDirection = LocalLayoutDirection.current
     val rootScope = remember(this) {
-        AnimatedContentScope(this, contentAlignment, layoutDirection)
+        AnimatedContentTransitionScopeImpl(this, contentAlignment, layoutDirection)
     }
 
     // TODO: remove screen as soon as they are animated out
@@ -674,7 +724,7 @@
                         }
                     }
                 val childData = remember {
-                    AnimatedContentScope.ChildData(stateForContent == targetState)
+                    AnimatedContentTransitionScopeImpl.ChildData(stateForContent == targetState)
                 }
                 // TODO: Will need a custom impl of this to: 1) get the signal for when
                 // the animation is finished, 2) get the target size properly
@@ -682,12 +732,14 @@
                     { it == stateForContent },
                     enter = specOnEnter.targetContentEnter,
                     exit = exit,
-                    modifier = Modifier.layout { measurable, constraints ->
-                        val placeable = measurable.measure(constraints)
-                        layout(placeable.width, placeable.height) {
-                            placeable.place(0, 0, zIndex = specOnEnter.targetContentZIndex)
+                    modifier = Modifier
+                        .layout { measurable, constraints ->
+                            val placeable = measurable.measure(constraints)
+                            layout(placeable.width, placeable.height) {
+                                placeable.place(0, 0, zIndex = specOnEnter.targetContentZIndex)
+                            }
                         }
-                    }.then(childData.apply { isTarget = stateForContent == targetState })
+                        .then(childData.apply { isTarget = stateForContent == targetState })
                 ) {
                     // TODO: Should Transition.AnimatedVisibility have an end listener?
                     DisposableEffect(this) {
@@ -719,8 +771,8 @@
     )
 }
 
-@OptIn(ExperimentalAnimationApi::class)
-private class AnimatedContentMeasurePolicy(val rootScope: AnimatedContentScope<*>) : MeasurePolicy {
+private class AnimatedContentMeasurePolicy(val rootScope: AnimatedContentTransitionScopeImpl<*>) :
+    MeasurePolicy {
     override fun MeasureScope.measure(
         measurables: List<Measurable>,
         constraints: Constraints
@@ -728,7 +780,9 @@
         val placeables = arrayOfNulls<Placeable>(measurables.size)
         // Measure the target composable first (but place it on top unless zIndex is specified)
         measurables.fastForEachIndexed { index, measurable ->
-            if ((measurable.parentData as? AnimatedContentScope.ChildData)?.isTarget == true) {
+            if ((measurable.parentData as? AnimatedContentTransitionScopeImpl.ChildData)
+                    ?.isTarget == true
+            ) {
                 placeables[index] = measurable.measure(constraints)
             }
         }
diff --git a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
index ba5bb31..93c840e 100644
--- a/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
+++ b/compose/animation/animation/src/commonMain/kotlin/androidx/compose/animation/EnterExitTransition.kt
@@ -354,7 +354,6 @@
  *                        [TransformOrigin.Center].
  */
 @Stable
-@ExperimentalAnimationApi
 fun scaleIn(
     animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
     initialScale: Float = 0f,
@@ -385,7 +384,6 @@
  *                        [TransformOrigin.Center].
  */
 @Stable
-@ExperimentalAnimationApi
 fun scaleOut(
     animationSpec: FiniteAnimationSpec<Float> = spring(stiffness = Spring.StiffnessMediumLow),
     targetScale: Float = 0f,
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
index dd33cd1..3829597 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/ControlFlowTransformTests.kt
@@ -6206,4 +6206,85 @@
             }
         """
     )
+
+    fun testNothingBody() = verifyComposeIrTransform(
+        source = """
+        import androidx.compose.runtime.*
+
+        val test1: @Composable () -> Unit = TODO()
+
+        @Composable
+        fun Test2(): Unit = TODO()
+
+        @Composable
+        fun Test3() {
+            Wrapper {
+                TODO()
+            }
+        }
+        """,
+        extra = """
+        import androidx.compose.runtime.*
+
+        @Composable
+        fun Wrapper(content: @Composable () -> Unit) = content()
+        """,
+        expectedTransformed = """
+        val test1: Function2<Composer, Int, Unit> = TODO()
+        @Composable
+        fun Test2(%composer: Composer?, %changed: Int) {
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(Test2):Test.kt")
+          if (%changed !== 0 || !%composer.skipping) {
+            if (isTraceInProgress()) {
+              traceEventStart(<>, %changed, -1, <>)
+            }
+            TODO()
+            if (isTraceInProgress()) {
+              traceEventEnd()
+            }
+          } else {
+            %composer.skipToGroupEnd()
+          }
+          %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+            Test2(%composer, updateChangedFlags(%changed or 0b0001))
+          }
+        }
+        @Composable
+        fun Test3(%composer: Composer?, %changed: Int) {
+          %composer = %composer.startRestartGroup(<>)
+          sourceInformation(%composer, "C(Test3)<Wrappe...>:Test.kt")
+          if (%changed !== 0 || !%composer.skipping) {
+            if (isTraceInProgress()) {
+              traceEventStart(<>, %changed, -1, <>)
+            }
+            Wrapper(ComposableSingletons%TestKt.lambda-1, %composer, 0b0110)
+            if (isTraceInProgress()) {
+              traceEventEnd()
+            }
+          } else {
+            %composer.skipToGroupEnd()
+          }
+          %composer.endRestartGroup()?.updateScope { %composer: Composer?, %force: Int ->
+            Test3(%composer, updateChangedFlags(%changed or 0b0001))
+          }
+        }
+        internal object ComposableSingletons%TestKt {
+          val lambda-1: Function2<Composer, Int, Unit> = composableLambdaInstance(<>, false) { %composer: Composer?, %changed: Int ->
+            sourceInformation(%composer, "C:Test.kt")
+            if (%changed and 0b1011 !== 0b0010 || !%composer.skipping) {
+              if (isTraceInProgress()) {
+                traceEventStart(<>, %changed, -1, <>)
+              }
+              TODO()
+              if (isTraceInProgress()) {
+                traceEventEnd()
+              }
+            } else {
+              %composer.skipToGroupEnd()
+            }
+          }
+        }
+        """
+    )
 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
index 1cdc4f6..5dcbf04 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/analysis/ComposableCheckerTests.kt
@@ -1298,4 +1298,24 @@
             }
     """
     )
+
+    @Test
+    fun testNothingAsAValidComposableFunctionBody() = check("""
+        import androidx.compose.runtime.*
+
+        val test1: @Composable () -> Unit = TODO()
+
+        @Composable
+        fun Test2(): Unit = TODO()
+
+        @Composable
+        fun Wrapper(content: @Composable () -> Unit) = content()
+
+        @Composable
+        fun Test3() {
+            Wrapper {
+                TODO()
+            }
+        }
+    """)
 }
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
index 0b888d9..4b18ff3 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposableCallChecker.kt
@@ -65,6 +65,7 @@
 import org.jetbrains.kotlin.types.lowerIfFlexible
 import org.jetbrains.kotlin.types.typeUtil.builtIns
 import org.jetbrains.kotlin.types.typeUtil.isAnyOrNullableAny
+import org.jetbrains.kotlin.types.typeUtil.isNothing
 import org.jetbrains.kotlin.types.upperIfFlexible
 import org.jetbrains.kotlin.util.OperatorNameConventions
 
@@ -319,6 +320,7 @@
         c: ResolutionContext<*>
     ) {
         val bindingContext = c.trace.bindingContext
+        if (expressionType.isNothing()) return
         val expectedType = c.expectedType
         if (expectedType === TypeUtils.NO_EXPECTED_TYPE) return
         if (expectedType === TypeUtils.UNIT_EXPECTED_TYPE) return
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
index 3c67d9f..9f48540 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/VersionChecker.kt
@@ -108,6 +108,7 @@
             9500 to "1.4.0-beta01",
             9600 to "1.4.0-beta02",
             9700 to "1.4.0-rc01",
+            9701 to "1.4.0-rc02",
             9701 to "1.5.0-alpha01",
         )
 
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 d14e5f6..0d625d7 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
@@ -312,12 +312,29 @@
 /**
  * Box [Constraints], but which abstract away width and height in favor of main axis and cross axis.
  */
-internal data class OrientationIndependentConstraints(
-    val mainAxisMin: Int,
-    val mainAxisMax: Int,
-    val crossAxisMin: Int,
-    val crossAxisMax: Int
+@JvmInline
+internal value class OrientationIndependentConstraints private constructor(
+    private val value: Constraints
 ) {
+    inline val mainAxisMin: Int get() = value.minWidth
+    inline val mainAxisMax: Int get() = value.maxWidth
+    inline val crossAxisMin: Int get() = value.minHeight
+    inline val crossAxisMax: Int get() = value.maxHeight
+
+    constructor(
+        mainAxisMin: Int,
+        mainAxisMax: Int,
+        crossAxisMin: Int,
+        crossAxisMax: Int
+    ) : this(
+        Constraints(
+            minWidth = mainAxisMin,
+            maxWidth = mainAxisMax,
+            minHeight = crossAxisMin,
+            maxHeight = crossAxisMax
+        )
+    )
+
     constructor(c: Constraints, orientation: LayoutOrientation) : this(
         if (orientation === LayoutOrientation.Horizontal) c.minWidth else c.minHeight,
         if (orientation === LayoutOrientation.Horizontal) c.maxWidth else c.maxHeight,
@@ -356,6 +373,19 @@
         } else {
             mainAxisMax
         }
+
+    fun copy(
+        mainAxisMin: Int = this.mainAxisMin,
+        mainAxisMax: Int = this.mainAxisMax,
+        crossAxisMin: Int = this.crossAxisMin,
+        crossAxisMax: Int = this.crossAxisMax
+    ): OrientationIndependentConstraints =
+        OrientationIndependentConstraints(
+            mainAxisMin,
+            mainAxisMax,
+            crossAxisMin,
+            crossAxisMax
+        )
 }
 
 internal val IntrinsicMeasurable.rowColumnParentData: RowColumnParentData?
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 6d60e98..a57c9d4 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -1276,3 +1276,27 @@
 
 }
 
+package androidx.compose.foundation.text2 {
+
+  public final class BasicTextField2Kt {
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void BasicTextField2(androidx.compose.foundation.text2.TextFieldState state, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional androidx.compose.foundation.interaction.MutableInteractionSource? interactionSource, optional androidx.compose.ui.graphics.Brush cursorBrush, optional int minLines, optional int maxLines, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional kotlin.jvm.functions.Function2<? super androidx.compose.ui.unit.Density,? super androidx.compose.ui.text.TextLayoutResult,kotlin.Unit> onTextLayout);
+  }
+
+  @androidx.compose.foundation.ExperimentalFoundationApi public fun interface TextEditFilter {
+    method public androidx.compose.ui.text.input.TextFieldValue filter(androidx.compose.ui.text.input.TextFieldValue oldValue, androidx.compose.ui.text.input.TextFieldValue newValue);
+    field public static final androidx.compose.foundation.text2.TextEditFilter.Companion Companion;
+  }
+
+  public static final class TextEditFilter.Companion {
+    method public androidx.compose.foundation.text2.TextEditFilter getDefault();
+    property public final androidx.compose.foundation.text2.TextEditFilter Default;
+  }
+
+  @androidx.compose.foundation.ExperimentalFoundationApi public final class TextFieldState {
+    ctor public TextFieldState(optional androidx.compose.ui.text.input.TextFieldValue initialValue, optional androidx.compose.foundation.text2.TextEditFilter filter);
+    method public androidx.compose.ui.text.input.TextFieldValue getValue();
+    property public final androidx.compose.ui.text.input.TextFieldValue value;
+  }
+
+}
+
diff --git a/compose/foundation/foundation/build.gradle b/compose/foundation/foundation/build.gradle
index 3c6a103..6950856 100644
--- a/compose/foundation/foundation/build.gradle
+++ b/compose/foundation/foundation/build.gradle
@@ -42,6 +42,7 @@
         implementation(libs.kotlinStdlibCommon)
         implementation(project(":compose:foundation:foundation-layout"))
         implementation(project(':emoji2:emoji2'))
+        implementation("androidx.core:core:1.9.0")
         implementation("androidx.compose.ui:ui-graphics:1.2.1")
         implementation("androidx.compose.ui:ui-text:1.2.1")
         implementation("androidx.compose.ui:ui-util:1.2.1")
@@ -66,7 +67,6 @@
         androidTestImplementation(libs.testRunner)
         androidTestImplementation(libs.testMonitor)
         androidTestImplementation "androidx.activity:activity-compose:1.3.1"
-        androidTestImplementation("androidx.core:core:1.9.0")
         androidTestImplementation(libs.espressoCore)
         androidTestImplementation(libs.junit)
         androidTestImplementation(libs.kotlinTest)
@@ -106,6 +106,7 @@
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation(project(':emoji2:emoji2'))
+                implementation("androidx.core:core:1.9.0")
             }
 
             desktopMain.dependencies {
@@ -137,7 +138,6 @@
                 implementation(project(":test:screenshot:screenshot"))
                 implementation(project(":internal-testutils-runtime"))
                 implementation("androidx.activity:activity-compose:1.3.1")
-                implementation("androidx.core:core:1.9.0")
 
                 implementation(libs.testUiautomator)
                 implementation(libs.testRules)
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/build.gradle b/compose/foundation/foundation/integration-tests/foundation-demos/build.gradle
index 2692883..4382106 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/build.gradle
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/build.gradle
@@ -40,24 +40,6 @@
     implementation(project(":compose:ui:ui-tooling-preview"))
     debugImplementation(project(":compose:ui:ui-tooling"))
     implementation(project(":internal-testutils-fonts"))
-
-    testImplementation(project(":compose:test-utils"))
-    testImplementation(libs.testRules)
-    testImplementation(libs.testRunner)
-    testImplementation(libs.junit)
-    testImplementation(libs.truth)
-    testImplementation(libs.kotlinTest)
-    testImplementation(libs.mockitoCore4)
-    testImplementation(libs.kotlinReflect)
-    testImplementation(libs.mockitoKotlin4)
-
-    androidTestImplementation(project(":compose:test-utils"))
-    androidTestImplementation(libs.testRules)
-    androidTestImplementation(libs.testRunner)
-    androidTestImplementation(libs.junit)
-    androidTestImplementation(libs.kotlinTest)
-    androidTestImplementation(libs.truth)
-    androidTestImplementation(libs.espressoCore)
 }
 
 android {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt
deleted file mode 100644
index 1d195c9..0000000
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt
+++ /dev/null
@@ -1,123 +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.foundation.text2.service
-
-import android.text.InputType
-import android.view.inputmethod.EditorInfo
-import androidx.compose.foundation.text2.TextFieldState
-import androidx.compose.ui.platform.LocalPlatformTextInputPluginRegistry
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.input.TextFieldValue
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import com.google.common.truth.Truth.assertThat
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class AndroidTextInputAdapterTest {
-
-    @get:Rule
-    val rule = createComposeRule()
-
-    private lateinit var adapter: AndroidTextInputAdapter
-
-    @Before
-    @OptIn(ExperimentalTextApi::class)
-    fun setup() {
-        rule.setContent {
-            val adapterProvider = LocalPlatformTextInputPluginRegistry.current
-            adapter = adapterProvider.rememberAdapter(AndroidTextInputPlugin)
-        }
-    }
-
-    @Test
-    fun startInputSession_returnsOpenSession() {
-        val state = TextFieldState()
-        rule.runOnUiThread {
-            val session = adapter.startInputSession(state)
-            assertThat(session.isOpen).isTrue()
-        }
-    }
-
-    @Test
-    fun disposedSession_returnsClosed() {
-        val state = TextFieldState()
-        rule.runOnUiThread {
-            val session = adapter.startInputSession(state)
-            session.dispose()
-            assertThat(session.isOpen).isFalse()
-        }
-    }
-
-    @Test(expected = IllegalStateException::class)
-    fun startingInputSessionOnNonMainThread_throwsIllegalStateException() {
-        adapter.startInputSession(TextFieldState())
-    }
-
-    @Test
-    fun creatingSecondInputSession_closesFirstOne() {
-        val state = TextFieldState()
-        rule.runOnUiThread {
-            val session1 = adapter.startInputSession(state)
-            val session2 = adapter.startInputSession(state)
-
-            assertThat(session1.isOpen).isFalse()
-            assertThat(session2.isOpen).isTrue()
-        }
-    }
-
-    // TODO: split into multiple tests when ImeOptions are added
-    @Test
-    fun createInputConnection_modifiesEditorInfo() {
-        val state = TextFieldState(TextFieldValue("hello", selection = TextRange(0, 5)))
-        rule.runOnUiThread {
-            adapter.startInputSession(state)
-            val editorInfo = EditorInfo()
-            adapter.createInputConnection(editorInfo)
-
-            assertThat(editorInfo.initialSelStart).isEqualTo(0)
-            assertThat(editorInfo.initialSelEnd).isEqualTo(5)
-            assertThat(editorInfo.inputType).isEqualTo(InputType.TYPE_CLASS_TEXT)
-            assertThat(editorInfo.imeOptions).isEqualTo(
-                EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
-            )
-        }
-    }
-
-    @Test
-    fun inputConnection_sendsUpdates_toActiveSession() {
-        val state1 = TextFieldState()
-        val state2 = TextFieldState()
-        rule.runOnUiThread {
-            adapter.startInputSession(state1)
-            adapter.startInputSession(state2)
-
-            val connection = adapter.createInputConnection(EditorInfo())
-
-            connection.commitText("Hello", 0)
-
-            assertThat(state1.value.text).isEqualTo("")
-            assertThat(state2.value.text).isEqualTo("Hello")
-        }
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
index 95eb56d..72d075c 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text/TextDemos.kt
@@ -17,6 +17,7 @@
 package androidx.compose.foundation.demos.text
 
 import androidx.compose.foundation.demos.text2.BasicTextField2Demos
+import androidx.compose.foundation.demos.text2.KeyboardOptionsDemos
 import androidx.compose.integration.demos.common.ComposableDemo
 import androidx.compose.integration.demos.common.DemoCategory
 
@@ -120,6 +121,9 @@
             listOf(
                 ComposableDemo("Basic text input") {
                     BasicTextField2Demos()
+                },
+                ComposableDemo("Keyboard Options") {
+                    KeyboardOptionsDemos()
                 }
             )
         ),
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
index 44d5663..aaeb580 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/BasicTextField2Demos.kt
@@ -14,12 +14,14 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package androidx.compose.foundation.demos.text2
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.demos.text.TagLine
 import androidx.compose.foundation.layout.Column
-import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.text2.BasicTextField2
 import androidx.compose.foundation.text2.TextFieldState
 import androidx.compose.material.LocalTextStyle
@@ -49,7 +51,7 @@
 @Composable
 fun PlainBasicTextField2() {
     val state = remember { TextFieldState() }
-    BasicTextField2(state, Modifier.fillMaxWidth(), textStyle = LocalTextStyle.current)
+    BasicTextField2(state, Modifier, textStyle = LocalTextStyle.current)
 }
 
 @Composable
@@ -62,7 +64,7 @@
         counter %= 2
     })
 
-    BasicTextField2(state, Modifier.fillMaxWidth(), textStyle = LocalTextStyle.current)
+    BasicTextField2(state, Modifier, textStyle = LocalTextStyle.current)
 }
 
 @Composable
@@ -72,5 +74,5 @@
             if (new.text.isDigitsOnly()) new else old
         }
     }
-    BasicTextField2(state, Modifier.fillMaxWidth(), textStyle = LocalTextStyle.current)
+    BasicTextField2(state, Modifier, textStyle = LocalTextStyle.current)
 }
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
new file mode 100644
index 0000000..577711fa
--- /dev/null
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/text2/KeyboardOptionsDemos.kt
@@ -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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.demos.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.demos.text.TagLine
+import androidx.compose.foundation.demos.text.fontSize8
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text2.BasicTextField2
+import androidx.compose.foundation.text2.TextFieldState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+
+@Preview
+@Composable
+fun KeyboardOptionsDemos() {
+    LazyColumn {
+        item { Item(KeyboardType.Text) }
+        item { Item(KeyboardType.Ascii) }
+        item { Item(KeyboardType.Number) }
+        item { Item(KeyboardType.Phone) }
+        item { Item(KeyboardType.Uri) }
+        item { Item(KeyboardType.Email) }
+        item { Item(KeyboardType.Password) }
+        item { Item(KeyboardType.NumberPassword) }
+    }
+}
+
+@Composable
+private fun Item(keyboardType: KeyboardType) {
+    TagLine(tag = "Keyboard Type: $keyboardType")
+    EditLine(keyboardType = keyboardType)
+}
+
+@Composable
+private fun EditLine(
+    keyboardType: KeyboardType = KeyboardType.Text,
+    imeAction: ImeAction = ImeAction.Default,
+    text: String = ""
+) {
+    val state = remember { TextFieldState(TextFieldValue(text)) }
+    BasicTextField2(
+        modifier = demoTextFieldModifiers,
+        state = state,
+        keyboardOptions = KeyboardOptions(
+            keyboardType = keyboardType,
+            imeAction = imeAction
+        ),
+        textStyle = TextStyle(fontSize = fontSize8),
+    )
+}
+
+val demoTextFieldModifiers = Modifier
+    .fillMaxWidth()
+    .padding(6.dp)
+    .border(1.dp, Color.LightGray, RoundedCornerShape(6.dp))
+    .padding(6.dp)
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/BasicTextField2.kt
deleted file mode 100644
index c93b842..0000000
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/BasicTextField2.kt
+++ /dev/null
@@ -1,359 +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.foundation.text2
-
-import androidx.compose.animation.core.animateFloatAsState
-import androidx.compose.foundation.ExperimentalFoundationApi
-import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
-import androidx.compose.foundation.focusable
-import androidx.compose.foundation.interaction.MutableInteractionSource
-import androidx.compose.foundation.relocation.BringIntoViewRequester
-import androidx.compose.foundation.text.selection.LocalTextSelectionColors
-import androidx.compose.foundation.text2.service.AndroidTextInputPlugin
-import androidx.compose.foundation.text2.service.TextInputSession
-import androidx.compose.runtime.Composable
-import androidx.compose.runtime.DisposableEffect
-import androidx.compose.runtime.getValue
-import androidx.compose.runtime.mutableStateOf
-import androidx.compose.runtime.remember
-import androidx.compose.runtime.rememberCoroutineScope
-import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.Snapshot
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.drawBehind
-import androidx.compose.ui.focus.FocusRequester
-import androidx.compose.ui.focus.focusRequester
-import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.geometry.Rect
-import androidx.compose.ui.layout.FirstBaseline
-import androidx.compose.ui.layout.LastBaseline
-import androidx.compose.ui.layout.Layout
-import androidx.compose.ui.layout.MeasureScope
-import androidx.compose.ui.platform.LocalDensity
-import androidx.compose.ui.platform.LocalFontFamilyResolver
-import androidx.compose.ui.platform.LocalPlatformTextInputPluginRegistry
-import androidx.compose.ui.text.AnnotatedString
-import androidx.compose.ui.text.ExperimentalTextApi
-import androidx.compose.ui.text.Paragraph
-import androidx.compose.ui.text.Placeholder
-import androidx.compose.ui.text.TextLayoutResult
-import androidx.compose.ui.text.TextMeasurer
-import androidx.compose.ui.text.TextStyle
-import androidx.compose.ui.text.drawText
-import androidx.compose.ui.text.font.FontFamily
-import androidx.compose.ui.text.rememberTextMeasurer
-import androidx.compose.ui.unit.Constraints
-import androidx.compose.ui.unit.Density
-import androidx.compose.ui.unit.IntSize
-import androidx.compose.ui.unit.dp
-import kotlin.math.ceil
-import kotlin.math.roundToInt
-import kotlinx.coroutines.launch
-
-/**
- * @param enabled controls the enabled state of the text field. When `false`, the text
- * field will be neither editable nor focusable, the input of the text field will not be selectable
- */
-@OptIn(
-    ExperimentalTextApi::class,
-    ExperimentalFoundationApi::class
-)
-@Composable
-fun BasicTextField2(
-    state: TextFieldState,
-    modifier: Modifier = Modifier,
-    textStyle: TextStyle = TextStyle.Default,
-    enabled: Boolean = true,
-    interactionSource: MutableInteractionSource? = null,
-    onTextLayout: (TextLayoutResult) -> Unit = {}
-) {
-    val textInputAdapter = LocalPlatformTextInputPluginRegistry.current
-        .rememberAdapter(AndroidTextInputPlugin)
-    val focusRequester = remember { FocusRequester() }
-
-    // Caching is not useful for dynamic content. We keep cache for a single layout when the
-    // content is not edited but layout phase still occurs.
-    // b/261581753
-    val textMeasurer = rememberTextMeasurer(cacheSize = 1)
-    val scope = rememberCoroutineScope()
-    val fontFamilyResolver = LocalFontFamilyResolver.current
-    val density = LocalDensity.current
-    val bringIntoViewRequester = remember { BringIntoViewRequester() }
-
-    val textLayoutState = remember {
-        TextLayoutState(
-            TextMeasureParams(
-                text = state.value.annotatedString,
-                style = textStyle,
-                density = density,
-                fontFamilyResolver = fontFamilyResolver,
-                softWrap = true,
-                placeholders = emptyList()
-            )
-        )
-    }
-
-    textLayoutState.updateParams(
-        visualText = state.value.annotatedString,
-        textStyle = textStyle,
-        softWrap = true,
-        density = density,
-        fontFamilyResolver = fontFamilyResolver
-    )
-
-    var isFocused by remember { mutableStateOf(false) }
-    val borderWidth by animateFloatAsState(targetValue = if (isFocused) 2f else 0f)
-
-    val textInputSessionState = remember { mutableStateOf<TextInputSession?>(null) }
-
-    DisposableEffect(state) {
-        // restart input if it's active
-        val textInputSession = textInputSessionState.value
-        if (textInputSession != null) {
-            textInputSession.dispose()
-            if (isFocused) {
-                textInputSessionState.value = textInputAdapter.startInputSession(state)
-            }
-        }
-        onDispose { }
-    }
-
-    val drawModifier = Modifier.drawBehind {
-        textLayoutState.layoutResult?.let { layoutResult ->
-            // TODO: draw selection
-            drawText(layoutResult)
-        }
-    }
-
-    val focusModifier = Modifier
-        .focusRequester(focusRequester)
-        .onFocusChanged {
-            if (isFocused == it.isFocused) {
-                return@onFocusChanged
-            }
-            isFocused = it.isFocused
-
-            if (it.isFocused) {
-                textInputSessionState.value = textInputAdapter.startInputSession(state)
-            }
-
-            // The focusable modifier itself will request the entire focusable be brought into view
-            // when it gains focus – in this case, that's the decoration box. However, since text
-            // fields may have their own internal scrolling, and the decoration box can do anything,
-            // we also need to specifically request that the cursor itself be brought into view.
-            // TODO(b/216790855) If this request happens after the focusable's request, the field
-            //  will only be scrolled far enough to show the cursor, _not_ the entire decoration
-            //  box.
-            if (it.isFocused) {
-                textLayoutState.layoutResult?.let { layoutResult ->
-                    scope.launch {
-                        val selectionEnd = state.value.selection.max
-                        val selectionEndBounds = when {
-                            selectionEnd < layoutResult.layoutInput.text.length -> {
-                                layoutResult.getBoundingBox(selectionEnd)
-                            }
-
-                            selectionEnd != 0 -> {
-                                layoutResult.getBoundingBox(selectionEnd - 1)
-                            }
-
-                            else -> { // empty text.
-                                val defaultSize = computeSizeForDefaultText(
-                                    textLayoutState.textMeasureParams.style,
-                                    textLayoutState.textMeasureParams.density,
-                                    textLayoutState.textMeasureParams.fontFamilyResolver
-                                )
-                                Rect(0f, 0f, 1.0f, defaultSize.height.toFloat())
-                            }
-                        }
-                        bringIntoViewRequester.bringIntoView(selectionEndBounds)
-                    }
-                }
-            } else {
-                state.deselect()
-            }
-        }
-        .focusable(interactionSource = interactionSource, enabled = enabled)
-
-    Layout(
-        content = {},
-        modifier = modifier
-            .then(focusModifier)
-            .then(drawModifier)
-            .clickable { focusRequester.requestFocus() }
-            .border(
-                width = borderWidth.dp,
-                color = LocalTextSelectionColors.current.backgroundColor
-            )
-    ) { _, constraints ->
-        val result = with(textLayoutState) { layout(textMeasurer, constraints, onTextLayout) }
-
-        // TODO: min height
-
-        layout(
-            width = result.size.width,
-            height = result.size.height,
-            alignmentLines = mapOf(
-                FirstBaseline to result.firstBaseline.roundToInt(),
-                LastBaseline to result.lastBaseline.roundToInt()
-            )
-        ) {}
-    }
-}
-
-@OptIn(ExperimentalTextApi::class)
-internal class TextLayoutState(
-    initialTextMeasureParams: TextMeasureParams
-) {
-    /**
-     * Set of parameters to compute text layout.
-     */
-    var textMeasureParams: TextMeasureParams by mutableStateOf(initialTextMeasureParams)
-        private set
-
-    /**
-     * TextFieldState holds both TextDelegate and layout result. However, these two values are not
-     * updated at the same time. TextDelegate is updated during composition according to new
-     * arguments while layoutResult is updated during layout phase. Therefore, [layoutResult] might
-     * not indicate the layout calculated from [textMeasureParams] at a given time during
-     * composition. This variable tells whether layout result is in sync with the latest
-     * [TextMeasureParams].
-     */
-    private var isLayoutResultStale: Boolean = true
-
-    var layoutResult: TextLayoutResult? by mutableStateOf(null)
-        private set
-
-    fun updateParams(
-        visualText: AnnotatedString,
-        textStyle: TextStyle,
-        softWrap: Boolean,
-        density: Density,
-        fontFamilyResolver: FontFamily.Resolver
-    ) {
-        if (!textMeasureParams.layoutCompatible(
-                text = visualText,
-                style = textStyle,
-                softWrap = softWrap,
-                density = density,
-                fontFamilyResolver = fontFamilyResolver,
-                placeholders = emptyList()
-        )) {
-            val newTextMeasureParams = TextMeasureParams(
-                text = visualText,
-                style = textStyle,
-                softWrap = softWrap,
-                density = density,
-                fontFamilyResolver = fontFamilyResolver,
-                placeholders = emptyList(),
-            )
-            isLayoutResultStale = true
-            textMeasureParams = newTextMeasureParams
-        }
-    }
-
-    fun MeasureScope.layout(
-        textMeasurer: TextMeasurer,
-        constraints: Constraints,
-        onTextLayout: (TextLayoutResult) -> Unit
-    ): TextLayoutResult {
-        val prevResult = Snapshot.withoutReadObservation { layoutResult }
-        val result = if (isLayoutResultStale || prevResult == null) {
-            textMeasurer.measure(
-                text = textMeasureParams.text,
-                style = textMeasureParams.style,
-                softWrap = textMeasureParams.softWrap,
-                density = this,
-                fontFamilyResolver = textMeasureParams.fontFamilyResolver,
-                layoutDirection = layoutDirection,
-                constraints = constraints
-            ).also {
-                layoutResult = it
-                isLayoutResultStale = false
-                onTextLayout(it)
-            }
-        } else {
-            prevResult
-        }
-        return result
-    }
-}
-
-internal data class TextMeasureParams(
-    val text: AnnotatedString,
-    val style: TextStyle,
-    val softWrap: Boolean,
-    val density: Density,
-    val fontFamilyResolver: FontFamily.Resolver,
-    val placeholders: List<Placeholder>
-) {
-    /**
-     * Returns whether given parameters and values in this [TextMeasureParams] would create the same
-     * layout or they are layout incompatible.
-     */
-    fun layoutCompatible(
-        text: AnnotatedString,
-        style: TextStyle,
-        softWrap: Boolean,
-        density: Density,
-        fontFamilyResolver: FontFamily.Resolver,
-        placeholders: List<Placeholder>
-    ): Boolean {
-        return this.text == text &&
-            this.style.hasSameLayoutAffectingAttributes(style) &&
-            this.softWrap == softWrap &&
-            this.density == density &&
-            this.fontFamilyResolver == fontFamilyResolver &&
-            this.placeholders == placeholders
-    }
-}
-
-/**
- * Computed the default width and height for TextField.
- *
- * The bounding box or x-advance of the empty text is empty, i.e. 0x0 box or 0px advance. However
- * this is not useful for TextField since text field want to reserve some amount of height for
- * accepting touch for starting text input. In Android, uses FontMetrics of the first font in the
- * fallback chain to compute this height, this is because custom font may have different
- * ascender/descender from the default font in Android.
- *
- * Until we have font metrics APIs, use the height of reference text as a workaround.
- */
-internal fun computeSizeForDefaultText(
-    style: TextStyle,
-    density: Density,
-    fontFamilyResolver: FontFamily.Resolver,
-    text: String = EmptyTextReplacement,
-    maxLines: Int = 1
-): IntSize {
-    val paragraph = Paragraph(
-        text = text,
-        style = style,
-        spanStyles = listOf(),
-        maxLines = maxLines,
-        ellipsis = false,
-        density = density,
-        fontFamilyResolver = fontFamilyResolver,
-        constraints = Constraints()
-    )
-    return IntSize(paragraph.minIntrinsicWidth.ceilToIntPx(), paragraph.height.ceilToIntPx())
-}
-
-internal fun Float.ceilToIntPx(): Int = ceil(this).roundToInt()
-internal const val DefaultWidthCharCount = 10 // min width for TextField is 10 chars long
-internal val EmptyTextReplacement = "H".repeat(DefaultWidthCharCount) // just a reference character.
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker b/compose/foundation/foundation/integration-tests/foundation-demos/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
deleted file mode 100644
index 1f0955d..0000000
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/resources/mockito-extensions/org.mockito.plugins.MockMaker
+++ /dev/null
@@ -1 +0,0 @@
-mock-maker-inline
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/DragGestureDetectorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/DragGestureDetectorTest.kt
new file mode 100644
index 0000000..edd29e1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/gesture/DragGestureDetectorTest.kt
@@ -0,0 +1,688 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.gesture
+
+import androidx.compose.foundation.gestures.awaitDragOrCancellation
+import androidx.compose.foundation.gestures.awaitEachGesture
+import androidx.compose.foundation.gestures.awaitFirstDown
+import androidx.compose.foundation.gestures.awaitHorizontalDragOrCancellation
+import androidx.compose.foundation.gestures.awaitHorizontalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.awaitTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.awaitVerticalDragOrCancellation
+import androidx.compose.foundation.gestures.awaitVerticalTouchSlopOrCancellation
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectHorizontalDragGestures
+import androidx.compose.foundation.gestures.detectVerticalDragGestures
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.TestViewConfiguration
+import androidx.compose.ui.AbsoluteAlignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.pointer.PointerEventPass
+import androidx.compose.ui.input.pointer.PointerInputChange
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.input.pointer.positionChange
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.TouchInjectionScope
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.DpSize
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+private const val TargetTag = "TargetLayout"
+
+@RunWith(Parameterized::class)
+class DragGestureDetectorTest(dragType: GestureType) {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    enum class GestureType {
+        VerticalDrag,
+        HorizontalDrag,
+        AwaitVerticalDragOrCancel,
+        AwaitHorizontalDragOrCancel,
+        AwaitDragOrCancel,
+        DragWithVertical,
+        DragWithHorizontal,
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters
+        fun parameters() = GestureType.values()
+    }
+
+    private var dragDistance = 0f
+    private var dragged = false
+    private var gestureEnded = false
+    private var gestureStarted = false
+    private var gestureCanceled = false
+    private var consumePositiveOnly = false
+    private var sloppyDetector = false
+    private var startOrder = -1
+    private var endOrder = -1
+    private var cancelOrder = -1
+    private var dragOrder = -1
+
+    private val DragTouchSlopUtil = layoutWithGestureDetector {
+        var count = 0
+        detectDragGestures(
+            onDragStart = {
+                gestureStarted = true
+                startOrder = count++
+            },
+            onDragEnd = {
+                gestureEnded = true
+                endOrder = count++
+            },
+            onDragCancel = {
+                gestureCanceled = true
+                cancelOrder = count++
+            }
+        ) { change, dragAmount ->
+            val positionChange = change.positionChange()
+            dragOrder = count++
+            if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
+                change.consume()
+                dragged = true
+                dragDistance += dragAmount.getDistance()
+            }
+        }
+    }
+
+    private val VerticalTouchSlopUtil = layoutWithGestureDetector {
+        var count = 0
+        detectVerticalDragGestures(
+            onDragStart = {
+                gestureStarted = true
+                startOrder = count++
+            },
+            onDragEnd = {
+                gestureEnded = true
+                endOrder = count++
+            },
+            onDragCancel = {
+                gestureCanceled = true
+                cancelOrder = count++
+            }
+        ) { change, dragAmount ->
+            dragOrder = count++
+            if (change.positionChange().y > 0f || !consumePositiveOnly) {
+                dragged = true
+                dragDistance += dragAmount
+            }
+        }
+    }
+
+    private val HorizontalTouchSlopUtil = layoutWithGestureDetector {
+        var count = 0
+        detectHorizontalDragGestures(
+            onDragStart = {
+                gestureStarted = true
+                startOrder = count++
+            },
+            onDragEnd = {
+                gestureEnded = true
+                endOrder = count++
+            },
+            onDragCancel = {
+                gestureCanceled = true
+                cancelOrder = count++
+            }
+        ) { change, dragAmount ->
+            dragOrder = count++
+            if (change.positionChange().x > 0f || !consumePositiveOnly) {
+                dragged = true
+                dragDistance += dragAmount
+            }
+        }
+    }
+
+    private val AwaitVerticalDragUtil = layoutWithGestureDetector {
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange = awaitVerticalTouchSlopOrCancellation(down.id) { change, overSlop ->
+                if (change.positionChange().y > 0f || !consumePositiveOnly) {
+                    dragged = true
+                    dragDistance = overSlop
+                    change.consume()
+                }
+            }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitVerticalDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().y
+                        change.consume()
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
+                        }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
+            }
+        }
+    }
+
+    private val AwaitHorizontalDragUtil = layoutWithGestureDetector {
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange =
+                awaitHorizontalTouchSlopOrCancellation(down.id) { change, overSlop ->
+                    if (change.positionChange().x > 0f || !consumePositiveOnly) {
+                        dragged = true
+                        dragDistance = overSlop
+                        change.consume()
+                    }
+                }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitHorizontalDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().x
+                        change.consume()
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
+                        }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
+            }
+        }
+    }
+
+    private val AwaitDragUtil = layoutWithGestureDetector {
+        awaitEachGesture {
+            val down = awaitFirstDown()
+            val slopChange = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
+                val positionChange = change.positionChange()
+                if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
+                    dragged = true
+                    dragDistance = overSlop.getDistance()
+                    change.consume()
+                }
+            }
+            if (slopChange != null || sloppyDetector) {
+                gestureStarted = true
+                var pointer = if (sloppyDetector) down.id else slopChange!!.id
+                do {
+                    val change = awaitDragOrCancellation(pointer)
+                    if (change == null) {
+                        gestureCanceled = true
+                    } else {
+                        dragDistance += change.positionChange().getDistance()
+                        change.consume()
+                        if (change.changedToUpIgnoreConsumed()) {
+                            gestureEnded = true
+                        }
+                        pointer = change.id
+                    }
+                } while (!gestureEnded && !gestureCanceled)
+            }
+        }
+    }
+
+    private val content = when (dragType) {
+        GestureType.VerticalDrag -> VerticalTouchSlopUtil
+        GestureType.HorizontalDrag -> HorizontalTouchSlopUtil
+        GestureType.AwaitVerticalDragOrCancel -> AwaitVerticalDragUtil
+        GestureType.AwaitHorizontalDragOrCancel -> AwaitHorizontalDragUtil
+        GestureType.AwaitDragOrCancel -> AwaitDragUtil
+        GestureType.DragWithVertical -> DragTouchSlopUtil
+        GestureType.DragWithHorizontal -> DragTouchSlopUtil
+    }
+
+    private val dragMotion = when (dragType) {
+        GestureType.VerticalDrag,
+        GestureType.AwaitVerticalDragOrCancel,
+        GestureType.DragWithVertical -> Offset(0f, 18f)
+        else -> Offset(18f, 0f)
+    }
+
+    private val crossDragMotion = when (dragType) {
+        GestureType.VerticalDrag,
+        GestureType.AwaitVerticalDragOrCancel,
+        GestureType.DragWithVertical -> Offset(18f, 0f)
+        else -> Offset(0f, 18f)
+    }
+
+    private val twoAxisDrag = when (dragType) {
+        GestureType.DragWithVertical,
+        GestureType.DragWithHorizontal,
+        GestureType.AwaitDragOrCancel -> true
+        else -> false
+    }
+
+    private val supportsSloppyGesture = when (dragType) {
+        GestureType.AwaitVerticalDragOrCancel,
+        GestureType.AwaitHorizontalDragOrCancel,
+        GestureType.AwaitDragOrCancel -> true
+        else -> false
+    }
+
+    private val nothingHandler: PointerInputChange.() -> Unit = {}
+
+    private var initialPass: PointerInputChange.() -> Unit = nothingHandler
+    private var finalPass: PointerInputChange.() -> Unit = nothingHandler
+
+    @Before
+    fun setup() {
+        dragDistance = 0f
+        dragged = false
+        gestureEnded = false
+
+        rule.setContent(content)
+    }
+
+    private fun layoutWithGestureDetector(
+        gestureDetector: suspend PointerInputScope.() -> Unit,
+    ): @Composable () -> Unit = {
+        CompositionLocalProvider(
+            LocalDensity provides Density(1f),
+            LocalViewConfiguration provides TestViewConfiguration(
+                minimumTouchTargetSize = DpSize.Zero
+            )
+        ) {
+            with(LocalDensity.current) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .pointerInput(Unit) {
+                            // Some tests execute a lambda before the initial and final passes
+                            // so they are called here, higher up the chain, so that the
+                            // calls happen prior to the gestureDetector below. The lambdas
+                            // do things like consume events on the initial pass or validate
+                            // consumption on the final pass.
+                            awaitPointerEventScope {
+                                while (true) {
+                                    val event = awaitPointerEvent(PointerEventPass.Initial)
+                                    event.changes.forEach {
+                                        initialPass(it)
+                                    }
+                                    awaitPointerEvent(PointerEventPass.Final)
+                                    event.changes.forEach {
+                                        finalPass(it)
+                                    }
+                                }
+                            }
+                        }
+                        .wrapContentSize(AbsoluteAlignment.TopLeft)
+                        .size(100.toDp())
+                        .pointerInput(key1 = gestureDetector, block = gestureDetector)
+                        .testTag(TargetTag)
+                )
+            }
+        }
+    }
+
+    /**
+     * Executes [block] on the [TargetTag] layout. The optional [initialPass] is executed
+     * prior to the [PointerEventPass.Initial] and [finalPass] is executed before
+     * [PointerEventPass.Final] of the gesture detector.
+     */
+    private fun performTouch(
+        initialPass: PointerInputChange.() -> Unit = nothingHandler,
+        finalPass: PointerInputChange.() -> Unit = nothingHandler,
+        block: TouchInjectionScope.() -> Unit
+    ) {
+        this.initialPass = initialPass
+        this.finalPass = finalPass
+        rule.onNodeWithTag(TargetTag).performTouchInput(block)
+        rule.waitForIdle()
+        this.initialPass = nothingHandler
+        this.finalPass = nothingHandler
+    }
+
+    /**
+     * A normal drag, just to ensure that the drag worked.
+     */
+    @Test
+    fun normalDrag() {
+        performTouch {
+            down(Offset.Zero)
+            moveBy(dragMotion)
+        }
+        assertTrue(gestureStarted)
+        assertTrue(dragged)
+        assertEquals(0f, dragDistance)
+        performTouch {
+            moveBy(dragMotion)
+        }
+        assertEquals(18f, dragDistance)
+        assertFalse(gestureEnded)
+        performTouch { up() }
+        assertTrue(gestureEnded)
+    }
+
+    /**
+     * A drag in the opposite direction doesn't cause a drag event.
+     */
+    @Test
+    fun crossDrag() {
+        if (!twoAxisDrag) {
+            performTouch {
+                down(Offset.Zero)
+                moveBy(crossDragMotion)
+                up()
+            }
+            assertFalse(gestureStarted)
+            assertFalse(dragged)
+            assertFalse(gestureEnded)
+
+            // now try a normal drag to ensure that it is still working.
+            performTouch {
+                down(Offset.Zero)
+                moveBy(dragMotion)
+                up()
+            }
+            assertTrue(gestureStarted)
+            assertTrue(dragged)
+            assertEquals(0f, dragDistance)
+            assertTrue(gestureEnded)
+        }
+    }
+
+    /**
+     * Use two fingers and lift the finger before the touch slop is reached.
+     */
+    @Test
+    fun twoFingerDrag_upBeforeSlop() {
+        performTouch {
+            down(0, Offset.Zero)
+            down(1, Offset.Zero)
+        }
+
+        // second finger shouldn't cause a drag. It should follow finger1
+        performTouch {
+            moveBy(1, dragMotion)
+        }
+
+        assertFalse(gestureStarted)
+        assertFalse(dragged)
+
+        // now it should move to finger 2
+        performTouch {
+            up(0)
+        }
+
+        performTouch {
+            moveBy(1, dragMotion)
+            moveBy(1, dragMotion)
+            up(1)
+        }
+
+        assertTrue(dragged)
+        assertEquals(18f, dragDistance)
+        assertTrue(gestureEnded)
+    }
+
+    /**
+     * Use two fingers and lift the finger after the touch slop is reached.
+     */
+    @Test
+    fun twoFingerDrag_upAfterSlop() {
+        performTouch {
+            down(0, Offset.Zero)
+            down(1, Offset.Zero)
+            moveBy(0, dragMotion)
+            up(0)
+        }
+
+        assertTrue(gestureStarted)
+        assertTrue(dragged)
+        assertEquals(0f, dragDistance)
+        assertFalse(gestureEnded)
+
+        performTouch {
+            moveBy(1, dragMotion)
+            up(1)
+        }
+
+        assertEquals(18f, dragDistance)
+        assertTrue(gestureEnded)
+    }
+
+    /**
+     * Cancel drag during touch slop
+     */
+    @Test
+    fun cancelDragDuringSlop() {
+        performTouch {
+            down(Offset.Zero)
+        }
+        performTouch(initialPass = { consume() }) {
+            moveBy(dragMotion)
+        }
+        performTouch {
+            up()
+        }
+        assertFalse(gestureStarted)
+        assertFalse(dragged)
+        assertFalse(gestureEnded)
+        assertFalse(gestureCanceled) // only canceled if the touch slop was crossed first
+    }
+
+    /**
+     * Cancel drag after touch slop
+     */
+    @Test
+    fun cancelDragAfterSlop() {
+        performTouch {
+            down(Offset.Zero)
+            moveBy(dragMotion)
+        }
+        performTouch(initialPass = { consume() }) {
+            moveBy(dragMotion)
+        }
+        performTouch {
+            up()
+        }
+        assertTrue(gestureStarted)
+        assertTrue(dragged)
+        assertFalse(gestureEnded)
+        assertTrue(gestureCanceled)
+        assertEquals(0f, dragDistance)
+    }
+
+    /**
+     * When this drag direction is more than the other drag direction, it should have priority
+     * in locking the orientation.
+     */
+    @Test
+    fun dragLockedWithPriority() {
+        if (!twoAxisDrag) {
+            performTouch {
+                down(Offset.Zero)
+            }
+            // This should have priority because it has moved more than the other direction.
+            performTouch(finalPass = { assertTrue(isConsumed) }) {
+                moveBy((dragMotion * 2f) + crossDragMotion)
+            }
+            performTouch {
+                up()
+            }
+            assertTrue(gestureStarted)
+            assertTrue(dragged)
+            assertTrue(gestureEnded)
+            assertFalse(gestureCanceled)
+            assertEquals(18f, dragDistance)
+        }
+    }
+
+    /**
+     * When a drag is not consumed, it should lead to the touch slop being reset. This is
+     * important when you drag your finger to
+     */
+    @Test
+    fun dragBackAndForth() {
+        if (supportsSloppyGesture) {
+            try {
+                consumePositiveOnly = true
+
+                performTouch {
+                    down(Offset.Zero)
+                    moveBy(-dragMotion)
+                }
+
+                assertFalse(gestureStarted)
+                assertFalse(dragged)
+                performTouch {
+                    moveBy(dragMotion)
+                    up()
+                }
+
+                assertTrue(gestureStarted)
+                assertTrue(dragged)
+            } finally {
+                consumePositiveOnly = false
+            }
+        }
+    }
+
+    /**
+     * When gesture detectors use the wrong pointer for the drag, it should just not
+     * detect the touch.
+     */
+    @Test
+    fun pointerUpTooQuickly() {
+        if (supportsSloppyGesture) {
+            try {
+                sloppyDetector = true
+
+                performTouch {
+                    down(0, Offset.Zero)
+                    down(1, Offset.Zero)
+                    up(0)
+                    moveBy(1, dragMotion)
+                    up(1)
+                }
+
+                // The sloppy detector doesn't know to look at finger2
+                assertTrue(gestureCanceled)
+            } finally {
+                sloppyDetector = false
+            }
+        }
+    }
+
+    @Test
+    fun dragGestureCallbackOrder_normalFinish() {
+        if (!supportsSloppyGesture) {
+            performTouch {
+                down(Offset.Zero)
+                moveBy(Offset(50f, 50f))
+            }
+            assertTrue(startOrder < dragOrder)
+            performTouch {
+                up()
+            }
+            assertTrue(startOrder < dragOrder)
+            assertTrue(dragOrder < endOrder)
+            assertTrue(cancelOrder == -1)
+        }
+    }
+
+    @Test
+    fun dragGestureCallbackOrder_cancel() {
+        if (!supportsSloppyGesture) {
+            performTouch {
+                down(Offset.Zero)
+                moveBy(dragMotion)
+            }
+            performTouch(initialPass = { consume() }) {
+                moveBy(dragMotion)
+            }
+            assertTrue(startOrder < dragOrder)
+            assertTrue(dragOrder < cancelOrder)
+            assertTrue(endOrder == -1)
+        }
+    }
+
+    // An error in the pointer input stream should stop the gesture without error.
+    @Test
+    fun interruptedBeforeDrag() {
+        performTouch {
+            down(Offset.Zero)
+            cancel()
+        }
+        // The next stream doesn't have the existing pointer, so we lose the dragging
+        performTouch {
+            down(Offset.Zero)
+            up()
+        }
+        assertFalse(gestureStarted)
+    }
+
+    // An error in the pointer input stream should stop the gesture without error.
+    @Test
+    fun interruptedBeforeTouchSlop() {
+        performTouch {
+            down(Offset.Zero)
+            moveBy(dragMotion / 2f)
+            cancel()
+        }
+        // The next stream doesn't have the existing pointer, so we lose the dragging
+        performTouch {
+            down(Offset.Zero)
+            up()
+        }
+        assertFalse(gestureStarted)
+    }
+
+    // An error in the pointer input stream should end in a drag cancellation.
+    @Test
+    fun interruptedAfterTouchSlop() {
+        performTouch {
+            down(Offset.Zero)
+            moveBy(dragMotion * 2f)
+            cancel()
+        }
+        // The next stream doesn't have the existing pointer, so we lose the dragging
+        performTouch {
+            down(Offset.Zero)
+            up()
+        }
+        assertTrue(gestureCanceled)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt
new file mode 100644
index 0000000..d7cf9d2
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/BasicTextField2Test.kt
@@ -0,0 +1,310 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.text.KeyboardHelper
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties.TextSelectionRange
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.assertIsNotEnabled
+import androidx.compose.ui.test.assertIsNotFocused
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextInput
+import androidx.compose.ui.test.performTextReplacement
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.sp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class BasicTextField2Test {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val Tag = "BasicTextField2"
+
+    @Test
+    fun textField_rendersEmptyContent() {
+        var textLayoutResult: TextLayoutResult? = null
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier.fillMaxSize(),
+                onTextLayout = { textLayoutResult = it }
+            )
+        }
+
+        rule.runOnIdle {
+            assertThat(textLayoutResult).isNotNull()
+            assertThat(textLayoutResult?.layoutInput?.text).isEqualTo(AnnotatedString(""))
+        }
+    }
+
+    @Test
+    fun textField_contentChange_updatesState() {
+        val state = TextFieldState(TextFieldValue("Hello ", TextRange(Int.MAX_VALUE)))
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.runOnIdle {
+            assertThat(state.value.text).isEqualTo("Hello World!")
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("Hello World!")
+        val selection = rule.onNodeWithTag(Tag).fetchSemanticsNode()
+            .config.getOrNull(TextSelectionRange)
+        assertThat(selection).isEqualTo(TextRange("Hello World!".length))
+    }
+
+    /**
+     * This is a goal that we set for ourselves. Only updating the editing buffer should not cause
+     * BasicTextField to recompose.
+     */
+    @Test
+    fun textField_imeUpdatesDontCauseRecomposition() {
+        val state = TextFieldState()
+        var compositionCount = 0
+        var textLayoutResultCount = 0
+        rule.setContent {
+            compositionCount++
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                onTextLayout = { textLayoutResultCount++ }
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextInput("hello")
+        }
+
+        rule.runOnIdle {
+            assertThat(compositionCount).isEqualTo(1)
+            assertThat(textLayoutResultCount).isEqualTo(2)
+        }
+    }
+
+    @Test
+    fun textField_textStyleFontSizeChange_relayouts() {
+        val state = TextFieldState(TextFieldValue("Hello ", TextRange(Int.MAX_VALUE)))
+        var style by mutableStateOf(TextStyle(fontSize = 20.sp))
+        val textLayoutResults = mutableListOf<TextLayoutResult>()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                textStyle = style,
+                onTextLayout = { textLayoutResults += it }
+            )
+        }
+
+        style = TextStyle(fontSize = 30.sp)
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.size).isEqualTo(2)
+            assertThat(textLayoutResults.map { it.layoutInput.style.fontSize })
+                .isEqualTo(listOf(20.sp, 30.sp))
+        }
+    }
+
+    @Test
+    fun textField_textStyleColorChange_doesNotRelayout() {
+        val state = TextFieldState(TextFieldValue("Hello"))
+        var style by mutableStateOf(TextStyle(color = Color.Red))
+        val textLayoutResults = mutableListOf<TextLayoutResult>()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                textStyle = style,
+                onTextLayout = { textLayoutResults += it }
+            )
+        }
+
+        style = TextStyle(color = Color.Blue)
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.size).isEqualTo(2)
+            assertThat(textLayoutResults[0].multiParagraph)
+                .isSameInstanceAs(textLayoutResults[1].multiParagraph)
+        }
+    }
+
+    @Test
+    fun textField_contentChange_relayouts() {
+        val state = TextFieldState(TextFieldValue("Hello ", TextRange(Int.MAX_VALUE)))
+        val textLayoutResults = mutableListOf<TextLayoutResult>()
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag),
+                onTextLayout = { textLayoutResults += it }
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performTextInput("World!")
+
+        rule.runOnIdle {
+            assertThat(textLayoutResults.size).isEqualTo(2)
+            assertThat(textLayoutResults.map { it.layoutInput.text.text })
+                .isEqualTo(listOf("Hello ", "Hello World!"))
+        }
+    }
+
+    @Test
+    fun textField_focus_showsSoftwareKeyboard() {
+        val state = TextFieldState()
+        val keyboardHelper = KeyboardHelper(rule)
+        rule.setContent {
+            keyboardHelper.initialize()
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).performClick()
+        rule.onNodeWithTag(Tag).assertIsFocused()
+
+        keyboardHelper.waitForKeyboardVisibility(true)
+
+        rule.runOnIdle {
+            assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+        }
+    }
+
+    @Ignore // b/273412941
+    @Test
+    fun textField_focus_doesNotShowSoftwareKeyboard_ifDisabled() {
+        val state = TextFieldState()
+        val keyboardHelper = KeyboardHelper(rule)
+        rule.setContent {
+            keyboardHelper.initialize()
+            BasicTextField2(
+                state = state,
+                enabled = false,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertIsNotEnabled()
+        rule.onNodeWithTag(Tag).performClick()
+        rule.onNodeWithTag(Tag).assertIsNotFocused()
+
+        keyboardHelper.waitForKeyboardVisibility(false)
+
+        rule.runOnIdle {
+            assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+        }
+    }
+
+    @Test
+    fun textField_whenStateObjectChanges_newTextIsRendered() {
+        val state1 = TextFieldState(TextFieldValue("Hello"))
+        val state2 = TextFieldState(TextFieldValue("World"))
+        var toggleState by mutableStateOf(true)
+        val state by derivedStateOf { if (toggleState) state1 else state2 }
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                enabled = false,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("Hello")
+        toggleState = !toggleState
+        rule.onNodeWithTag(Tag).assertTextEquals("World")
+    }
+
+    @Test
+    fun textField_whenStateObjectChanges_restartsInput() {
+        val state1 = TextFieldState(TextFieldValue("Hello"))
+        val state2 = TextFieldState(TextFieldValue("World"))
+        var toggleState by mutableStateOf(true)
+        val state by derivedStateOf { if (toggleState) state1 else state2 }
+        rule.setContent {
+            BasicTextField2(
+                state = state,
+                enabled = false,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .testTag(Tag)
+            )
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            performTextReplacement("Compose")
+            assertTextEquals("Compose")
+        }
+        toggleState = !toggleState
+        with(rule.onNodeWithTag(Tag)) {
+            performTextReplacement("Compose2")
+            assertTextEquals("Compose2")
+        }
+        assertThat(state1.value.text).isEqualTo("Compose")
+        assertThat(state2.value.text).isEqualTo("Compose2")
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldContentSemanticsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldContentSemanticsTest.kt
new file mode 100644
index 0000000..adcd8d6
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldContentSemanticsTest.kt
@@ -0,0 +1,121 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.getOrNull
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.assertTextEquals
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldContentSemanticsTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val Tag = "TextField"
+
+    @Test
+    fun contentSemanticsAreSet_inTheFirstComposition() {
+        val state = TextFieldState(TextFieldValue("hello"))
+        rule.setContent {
+            Box(modifier = Modifier.testTag(Tag).then(TextFieldContentSemanticsElement(state)))
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+    }
+
+    @Test
+    fun contentSemanticsAreSet_afterRecomposition() {
+        val state = TextFieldState(TextFieldValue("hello"))
+        rule.setContent {
+            Box(modifier = Modifier.testTag(Tag).then(TextFieldContentSemanticsElement(state)))
+        }
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello")
+
+        state.editProcessor.reset(TextFieldValue("hello2"))
+
+        rule.onNodeWithTag(Tag).assertTextEquals("hello2")
+    }
+
+    @Test
+    fun selectionSemanticsAreSet_inTheFirstComposition() {
+        val state = TextFieldState(TextFieldValue("hello", selection = TextRange(2)))
+        rule.setContent {
+            Box(modifier = Modifier.testTag(Tag).then(TextFieldContentSemanticsElement(state)))
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    @Test
+    fun selectionSemanticsAreSet_afterRecomposition() {
+        val state = TextFieldState(TextFieldValue("hello"))
+        rule.setContent {
+            Box(modifier = Modifier.testTag(Tag).then(TextFieldContentSemanticsElement(state)))
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange.Zero)
+        }
+
+        state.editProcessor.reset(TextFieldValue("hello", selection = TextRange(2)))
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    @Test
+    fun semanticsAreSet_afterStateObjectChanges() {
+        val state1 = TextFieldState(TextFieldValue("hello"))
+        val state2 = TextFieldState(TextFieldValue("world", TextRange(2)))
+        var chosenState by mutableStateOf(true)
+        rule.setContent {
+            Box(modifier = Modifier.testTag(Tag).then(TextFieldContentSemanticsElement(
+                if (chosenState) state1 else state2
+            )))
+        }
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("hello")
+            assertSelection(TextRange.Zero)
+        }
+
+        chosenState = false
+
+        with(rule.onNodeWithTag(Tag)) {
+            assertTextEquals("world")
+            assertSelection(TextRange(2))
+        }
+    }
+
+    private fun SemanticsNodeInteraction.assertSelection(expected: TextRange) {
+        val selection = fetchSemanticsNode().config
+            .getOrNull(SemanticsProperties.TextSelectionRange)
+        assertThat(selection).isEqualTo(expected)
+    }
+}
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCursorTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCursorTest.kt
new file mode 100644
index 0000000..48e9db1
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldCursorTest.kt
@@ -0,0 +1,476 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text2
+
+import android.os.Build
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertPixelColor
+import androidx.compose.testutils.assertPixels
+import androidx.compose.testutils.assertShape
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.toPixelMap
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.hasSetTextAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTextReplacement
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.compose.ui.unit.toOffset
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import com.google.common.truth.Truth.assertThat
+import kotlin.math.ceil
+import kotlin.math.floor
+import org.junit.Rule
+import org.junit.Test
+
+@LargeTest
+class TextFieldCursorTest {
+
+    private val motionDurationScale = object : MotionDurationScale {
+        override var scaleFactor: Float by mutableStateOf(1f)
+    }
+
+    @OptIn(ExperimentalTestApi::class)
+    @get:Rule
+    val rule = createComposeRule(effectContext = motionDurationScale).also {
+        it.mainClock.autoAdvance = false
+    }
+
+    private val boxPadding = 10.dp
+    private val cursorColor = Color.Red
+    private val textStyle = TextStyle(
+        color = Color.White,
+        background = Color.White,
+        fontSize = 10.sp
+    )
+
+    private val textFieldWidth = 10.dp
+    private val textFieldHeight = 20.dp
+    private val textFieldBgColor = Color.White
+    private var isFocused = false
+    private var cursorRect = Rect.Zero
+
+    private val bgModifier = Modifier.background(textFieldBgColor)
+    private val focusModifier = Modifier.onFocusChanged { if (it.isFocused) isFocused = true }
+    private val sizeModifier = Modifier.size(textFieldWidth, textFieldHeight)
+
+    // default TextFieldModifier
+    private val textFieldModifier = sizeModifier
+        .then(bgModifier)
+        .then(focusModifier)
+
+    // default onTextLayout to capture cursor boundaries.
+    private val onTextLayout: Density.(TextLayoutResult) -> Unit = {
+        cursorRect = it.getCursorRect(0)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorRendered() = with(rule.density) {
+        val state = TextFieldState()
+        rule.setContent {
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun textFieldFocused_cursorWithBrush() = with(rule.density) {
+        val state = TextFieldState()
+        rule.setContent {
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle.copy(fontSize = textStyle.fontSize * 2),
+                    modifier = Modifier
+                        .size(textFieldWidth, textFieldHeight * 2)
+                        .then(bgModifier)
+                        .then(focusModifier),
+                    cursorBrush = Brush.verticalGradient(
+                        // make a brush double/triple color at the beginning and end so we have stable
+                        // colors at the ends.
+                        // Without triple bottom, the bottom color never hits to the provided color.
+                        listOf(
+                            Color.Blue,
+                            Color.Blue,
+                            Color.Green,
+                            Color.Green,
+                            Color.Green
+                        )
+                    ),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(100)
+
+        val bitmap = rule.onNode(hasSetTextAction())
+            .captureToImage().toPixelMap()
+
+        val cursorLeft = ceil(cursorRect.left).toInt() + 1
+        val cursorTop = ceil(cursorRect.top).toInt() + 1
+        val cursorBottom = floor(cursorRect.bottom).toInt() - 1
+        bitmap.assertPixelColor(Color.Blue, x = cursorLeft, y = cursorTop)
+        bitmap.assertPixelColor(Color.Green, x = cursorLeft, y = cursorBottom)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorBlinkingAnimation() = with(rule.density) {
+        val state = TextFieldState()
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(100)
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+
+        // cursor invisible during next 500 ms
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Suppress("UnnecessaryOptInAnnotation")
+    @OptIn(ExperimentalTestApi::class)
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorBlinkingAnimation_whenSystemDisablesAnimations() = with(rule.density) {
+        motionDurationScale.scaleFactor = 0f
+        val state = TextFieldState()
+
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(100)
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+
+        // cursor invisible during next 500 ms
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorUnsetColor_noCursor() = with(rule.density) {
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField(
+                    value = "",
+                    onValueChange = {},
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(Color.Unspecified)
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // no cursor when usually shown
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+
+        // no cursor when should be no cursor
+        rule.mainClock.advanceTimeBy(700)
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun cursorNotBlinking_whileTyping() = with(rule.density) {
+        val state = TextFieldState(TextFieldValue("test"))
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // cursor visible first 500 ms
+        rule.mainClock.advanceTimeBy(500)
+        // TODO(b/170298051) check here that cursor is visible when we have a way to control
+        //  cursor position when sending a text
+
+        // change text field value
+        rule.onNode(hasSetTextAction())
+            .performTextReplacement("")
+
+        // cursor would have been invisible during next 500 ms if cursor blinks while typing.
+        // To prevent blinking while typing we restart animation when new symbol is typed.
+        rule.mainClock.advanceTimeBy(400)
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun selectionChanges_cursorNotBlinking() = with(rule.density) {
+        rule.mainClock.autoAdvance = false
+        val state = TextFieldState(TextFieldValue("test", selection = TextRange(2)))
+        rule.setContent {
+            // The padding helps if the test is run accidentally in landscape. Landscape makes
+            // the cursor to be next to the navigation bar which affects the red color to be a bit
+            // different - possibly anti-aliasing.
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = SolidColor(cursorColor),
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        // hide the cursor
+        rule.mainClock.advanceTimeBy(500)
+        rule.mainClock.advanceTimeByFrame()
+
+        // TODO(b/170298051) check here that cursor is visible when we have a way to control
+        //  cursor position when sending a text
+
+        rule.runOnIdle {
+            state.editProcessor.reset(state.value.copy(selection = TextRange(0)))
+        }
+
+        // necessary for animation to start (shows cursor again)
+        rule.mainClock.advanceTimeByFrame()
+
+        with(rule.density) {
+            rule.onNode(hasSetTextAction())
+                .captureToImage()
+                .assertCursor(2.dp, this, cursorRect)
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    fun brushChanged_doesntResetTimer() {
+        var cursorBrush by mutableStateOf(SolidColor(cursorColor))
+        val state = TextFieldState()
+        rule.setContent {
+            Box(Modifier.padding(boxPadding)) {
+                BasicTextField2(
+                    state = state,
+                    textStyle = textStyle,
+                    modifier = textFieldModifier,
+                    cursorBrush = cursorBrush,
+                    onTextLayout = onTextLayout
+                )
+            }
+        }
+
+        focusAndWait()
+
+        rule.mainClock.advanceTimeBy(800)
+        cursorBrush = SolidColor(Color.Green)
+        rule.mainClock.advanceTimeByFrame()
+
+        rule.onNode(hasSetTextAction())
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                shape = RectangleShape,
+                shapeColor = Color.White,
+                backgroundColor = Color.White,
+                shapeOverlapPixelCount = 0.0f
+            )
+    }
+
+    private fun focusAndWait() {
+        rule.onNode(hasSetTextAction()).performClick()
+        rule.mainClock.advanceTimeUntil { isFocused }
+    }
+
+    private fun ImageBitmap.assertCursor(cursorWidth: Dp, density: Density, cursorRect: Rect) {
+        assertThat(cursorRect.height).isNotEqualTo(0f)
+        assertThat(cursorRect).isNotEqualTo(Rect.Zero)
+        val cursorWidthPx = (with(density) { cursorWidth.roundToPx() })
+
+        // assert cursor width is greater than 2 since we will shrink the check area by 1 on each
+        // side
+        assertThat(cursorWidthPx).isGreaterThan(2)
+
+        // shrink the check are by 1px for left, top, right, bottom
+        val checkRect = Rect(
+            ceil(cursorRect.left) + 1,
+            ceil(cursorRect.top) + 1,
+            floor(cursorRect.right) + cursorWidthPx - 1,
+            floor(cursorRect.bottom) - 1
+        )
+
+        // skip an expanded rectangle that is 1px larger than cursor rectangle due to antialiasing
+        val skipRect = Rect(
+            floor(cursorRect.left) - 1,
+            floor(cursorRect.top) - 1,
+            ceil(cursorRect.right) + cursorWidthPx + 1,
+            ceil(cursorRect.bottom) + 1
+        )
+
+        val width = width
+        val height = height
+        this.assertPixels(
+            IntSize(width, height)
+        ) { position ->
+            if (checkRect.contains(position.toOffset())) {
+                // cursor
+                cursorColor
+            } else if (skipRect.contains(position.toOffset())) {
+                // skip some pixels around cursor
+                null
+            } else {
+                // text field background
+                textFieldBgColor
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldFocusTest.kt
new file mode 100644
index 0000000..1bc6713
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/TextFieldFocusTest.kt
@@ -0,0 +1,493 @@
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text2
+
+import android.os.SystemClock
+import android.view.InputDevice
+import android.view.KeyEvent
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.requiredWidth
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.foundation.text.BasicTextField
+import androidx.compose.foundation.text.KeyboardHelper
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.NativeKeyEvent
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performKeyPress
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Ignore
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class TextFieldFocusTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val keyboardHelper = KeyboardHelper(rule)
+
+    @Composable
+    private fun TextFieldApp(dataList: List<FocusTestData>) {
+        for (data in dataList) {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                modifier = Modifier
+                    .focusRequester(data.focusRequester)
+                    .onFocusChanged { data.focused = it.isFocused }
+                    .requiredWidth(10.dp)
+            )
+        }
+    }
+
+    data class FocusTestData(val focusRequester: FocusRequester, var focused: Boolean = false)
+
+    @Test
+    fun requestFocus() {
+        lateinit var testDataList: List<FocusTestData>
+
+        rule.setContent {
+            testDataList = listOf(
+                FocusTestData(FocusRequester()),
+                FocusTestData(FocusRequester()),
+                FocusTestData(FocusRequester())
+            )
+
+            TextFieldApp(testDataList)
+        }
+
+        rule.runOnIdle { testDataList[0].focusRequester.requestFocus() }
+
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isTrue()
+            assertThat(testDataList[1].focused).isFalse()
+            assertThat(testDataList[2].focused).isFalse()
+        }
+
+        rule.runOnIdle { testDataList[1].focusRequester.requestFocus() }
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isFalse()
+            assertThat(testDataList[1].focused).isTrue()
+            assertThat(testDataList[2].focused).isFalse()
+        }
+
+        rule.runOnIdle { testDataList[2].focusRequester.requestFocus() }
+        rule.runOnIdle {
+            assertThat(testDataList[0].focused).isFalse()
+            assertThat(testDataList[1].focused).isFalse()
+            assertThat(testDataList[2].focused).isTrue()
+        }
+    }
+
+    @Test
+    fun noCrashWhenSwitchingBetweenEnabledFocusedAndDisabledTextField() {
+        val enabled = mutableStateOf(true)
+        var focused = false
+        val tag = "textField"
+        rule.setContent {
+            val state = remember { TextFieldState() }
+            BasicTextField2(
+                state = state,
+                enabled = enabled.value,
+                modifier = Modifier
+                    .testTag(tag)
+                    .onFocusChanged {
+                        focused = it.isFocused
+                    }
+                    .requiredWidth(10.dp)
+            )
+        }
+        // bring enabled text field into focus
+        rule.onNodeWithTag(tag).performClick()
+        rule.runOnIdle {
+            assertThat(focused).isTrue()
+        }
+
+        // make text field disabled
+        enabled.value = false
+        rule.runOnIdle {
+            assertThat(focused).isFalse()
+        }
+
+        // make text field enabled again, it must not crash
+        enabled.value = true
+        rule.runOnIdle {
+            assertThat(focused).isFalse()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun keyboardIsShown_forFieldInActivity_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            }
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    fun keyboardIsShown_forFieldInActivity_whenFocusRequestedImmediately_fromDisposableEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun keyboardIsShown_forFieldInDialog_whenFocusRequestedImmediately_fromLaunchedEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                LaunchedEffect(Unit) {
+                    it()
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    // TODO(b/229378542) We can't accurately detect IME visibility from dialogs before API 30 so
+    //  this test can't assert.
+    @SdkSuppress(minSdkVersion = 30)
+    @Test
+    fun keyboardIsShown_forFieldInDialog_whenFocusRequestedImmediately_fromDisposableEffect() {
+        keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+            runEffect = {
+                DisposableEffect(Unit) {
+                    it()
+                    onDispose {}
+                }
+            },
+            wrapContent = {
+                Dialog(onDismissRequest = {}, content = it)
+            }
+        )
+    }
+
+    private fun keyboardIsShown_whenFocusRequestedImmediately_fromEffect(
+        runEffect: @Composable (body: () -> Unit) -> Unit,
+        wrapContent: @Composable (@Composable () -> Unit) -> Unit = { it() }
+    ) {
+        val focusRequester = FocusRequester()
+        val keyboardHelper = KeyboardHelper(rule)
+
+        rule.setContent {
+            wrapContent {
+                keyboardHelper.initialize()
+
+                runEffect {
+                    assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+                    focusRequester.requestFocus()
+                }
+
+                BasicTextField(
+                    value = "",
+                    onValueChange = {},
+                    modifier = Modifier.focusRequester(focusRequester)
+                )
+            }
+        }
+
+        keyboardHelper.waitForKeyboardVisibility(visible = true)
+
+        // Ensure the keyboard doesn't leak in to the next test. Can't do this at the start of the
+        // test since the KeyboardHelper won't be initialized until composition runs, and this test
+        // is checking behavior that all happens on the first frame.
+        keyboardHelper.hideKeyboard()
+        keyboardHelper.waitForKeyboardVisibility(visible = false)
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    @Ignore // TODO(halilibo): reenable when dpad focus modifier does not use TextFieldState
+    fun basicTextField_checkFocusNavigation_onDPadLeft() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on left
+        keyPressOnPhysicalKeyboard(rule, NativeKeyEvent.KEYCODE_DPAD_LEFT)
+
+        // Check if the element to the left of text field gains focus
+        rule.onNodeWithTag("test-button-left").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    @Ignore // TODO(halilibo): reenable when dpad focus modifier does not use TextFieldState
+    fun basicTextField_checkFocusNavigation_onDPadRight() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on right
+        keyPressOnPhysicalKeyboard(rule, NativeKeyEvent.KEYCODE_DPAD_RIGHT)
+
+        // Check if the element to the right of text field gains focus
+        rule.onNodeWithTag("test-button-right").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    @Ignore // TODO(halilibo): reenable when dpad focus modifier does not use TextFieldState
+    fun basicTextField_checkFocusNavigation_onDPadUp() {
+        setupAndEnableBasicTextField()
+        inputMultilineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on top
+        keyPressOnPhysicalKeyboard(rule, NativeKeyEvent.KEYCODE_DPAD_UP)
+
+        // Check if the element on the top of text field gains focus
+        rule.onNodeWithTag("test-button-top").assertIsFocused()
+    }
+
+    @SdkSuppress(minSdkVersion = 22) // b/266742195
+    @Test
+    @Ignore // TODO(halilibo): re-enable when dpad focus modifier does not use TextFieldState
+    fun basicTextField_checkFocusNavigation_onDPadDown() {
+        setupAndEnableBasicTextField()
+        inputMultilineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Move focus to the focusable element on bottom
+        keyPressOnPhysicalKeyboard(rule, NativeKeyEvent.KEYCODE_DPAD_DOWN)
+
+        // Check if the element to the bottom of text field gains focus
+        rule.onNodeWithTag("test-button-bottom").assertIsFocused()
+    }
+
+    @Ignore // b/264919150
+    @Test
+    fun basicTextField_checkKeyboardShown_onDPadCenter() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // Dismiss keyboard on back press
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_BACK)
+        keyboardHelper.waitForKeyboardVisibility(false)
+        rule.runOnIdle {
+            assertThat(keyboardHelper.isSoftwareKeyboardShown()).isFalse()
+        }
+
+        // Check if keyboard is enabled on Dpad center key press
+        keyPressOnPhysicalKeyboard(rule, NativeKeyEvent.KEYCODE_DPAD_CENTER)
+        keyboardHelper.waitForKeyboardVisibility(true)
+        rule.runOnIdle {
+            assertThat(keyboardHelper.isSoftwareKeyboardShown()).isTrue()
+        }
+    }
+
+    @Test
+    fun basicTextField_handlesInvalidDevice() {
+        setupAndEnableBasicTextField()
+        inputSingleLineTextInBasicTextField()
+
+        // -2 shouldn't be a valid device – we verify this below by asserting the device in the
+        // event is actually null.
+        val invalidDeviceId = -2
+        val keyCode = NativeKeyEvent.KEYCODE_DPAD_CENTER
+        val keyEventDown = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_DOWN, keyCode, 0, 0, invalidDeviceId, 0
+        )
+        assertThat(keyEventDown.device).isNull()
+        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
+        rule.waitForIdle()
+        val keyEventUp = KeyEvent(
+            SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+            KeyEvent.ACTION_UP, keyCode, 0, 0, invalidDeviceId, 0
+        )
+        rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
+        rule.waitForIdle()
+    }
+
+    private fun setupAndEnableBasicTextField() {
+        setupContent()
+
+        rule.onNodeWithTag("test-text-field-1").assertIsFocused()
+    }
+
+    private fun inputSingleLineTextInBasicTextField() {
+        // Input "abc"
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
+        rule.waitForIdle()
+    }
+
+    private fun inputMultilineTextInBasicTextField() {
+        // Input "a\nb\nc"
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_A)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_B)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_ENTER)
+        rule.waitForIdle()
+        keyPressOnVirtualKeyboard(NativeKeyEvent.KEYCODE_C)
+        rule.waitForIdle()
+    }
+
+    private fun setupContent() {
+        rule.setContent {
+            keyboardHelper.initialize()
+            Column() {
+                Row(horizontalArrangement = Arrangement.Center) {
+                    TestFocusableElement(id = "top")
+                }
+                Row() {
+                    TestFocusableElement(id = "left")
+                    TestBasicTextField(id = "1", requestFocus = true)
+                    TestFocusableElement(id = "right")
+                }
+                Row(horizontalArrangement = Arrangement.Center) {
+                    TestFocusableElement(id = "bottom")
+                }
+            }
+        }
+        rule.waitForIdle()
+    }
+
+    @Composable
+    private fun TestFocusableElement(id: String) {
+        var isFocused by remember {
+            mutableStateOf(false)
+        }
+        BasicText(
+            text = "test-button-$id",
+            modifier = Modifier
+                .testTag("test-button-$id")
+                .padding(10.dp)
+                .onFocusChanged {
+                    isFocused = it.hasFocus
+                }
+                .focusable()
+                .border(2.dp, if (isFocused) Color.Green else Color.Cyan)
+        )
+    }
+
+    @Composable
+    private fun TestBasicTextField(
+        id: String,
+        requestFocus: Boolean = false
+    ) {
+        var textInput by remember {
+            mutableStateOf("")
+        }
+        var isFocused by remember {
+            mutableStateOf(false)
+        }
+        val focusRequester = remember {
+            FocusRequester()
+        }
+        val modifier = if (requestFocus) Modifier.focusRequester(focusRequester) else Modifier
+
+        BasicTextField(
+            value = textInput,
+            onValueChange = {
+                textInput = it
+            },
+            modifier = modifier
+                .testTag("test-text-field-$id")
+                .padding(10.dp)
+                .onFocusChanged {
+                    isFocused = it.isFocused || it.hasFocus
+                }
+                .border(2.dp, if (isFocused) Color.Red else Color.Transparent)
+        )
+
+        LaunchedEffect(requestFocus, focusRequester) {
+            if (requestFocus) focusRequester.requestFocus()
+        }
+    }
+
+    // Triggers a key press on the root node from a non-virtual device
+    private fun keyPressOnPhysicalKeyboard(
+        rule: ComposeContentTestRule,
+        keyCode: Int,
+        count: Int = 1
+    ) {
+        repeat(count) {
+            val deviceId = InputDevice.getDeviceIds().first { id ->
+                InputDevice.getDevice(id)?.isVirtual?.not() ?: false
+            }
+            val keyEventDown = KeyEvent(
+                SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+                KeyEvent.ACTION_DOWN, keyCode, 0, 0, deviceId, 0
+            )
+            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventDown))
+            rule.waitForIdle()
+            val keyEventUp = KeyEvent(
+                SystemClock.uptimeMillis(), SystemClock.uptimeMillis(),
+                KeyEvent.ACTION_UP, keyCode, 0, 0, deviceId, 0
+            )
+            rule.onRoot().performKeyPress(androidx.compose.ui.input.key.KeyEvent(keyEventUp))
+        }
+    }
+
+    // Triggers a key press on the virtual keyboard
+    private fun keyPressOnVirtualKeyboard(keyCode: Int, count: Int = 1) {
+        repeat(count) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
+        }
+    }
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/input/BackspaceCommandTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/BackspaceCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/input/BackspaceCommandTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/BackspaceCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/input/MoveCursorCommandTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/MoveCursorCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/input/MoveCursorCommandTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/input/MoveCursorCommandTest.kt
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt
new file mode 100644
index 0000000..72bae6d
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapterTest.kt
@@ -0,0 +1,221 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.foundation.text2.service
+
+import android.text.InputType
+import android.view.inputmethod.EditorInfo
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.text2.TextFieldState
+import androidx.compose.ui.platform.LocalPlatformTextInputPluginRegistry
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+class AndroidTextInputAdapterTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var adapter: AndroidTextInputAdapter
+
+    @Before
+    @OptIn(ExperimentalTextApi::class)
+    fun setup() {
+        rule.setContent {
+            val adapterProvider = LocalPlatformTextInputPluginRegistry.current
+            adapter = adapterProvider.rememberAdapter(AndroidTextInputPlugin)
+        }
+    }
+
+    @Test
+    fun startInputSession_returnsOpenSession() {
+        val state = TextFieldState()
+        rule.runOnUiThread {
+            val session = adapter.startInputSession(state, ImeOptions.Default)
+            assertThat(session.isOpen).isTrue()
+        }
+    }
+
+    @Test
+    fun disposedSession_returnsClosed() {
+        val state = TextFieldState()
+        rule.runOnUiThread {
+            val session = adapter.startInputSession(state, ImeOptions.Default)
+            session.dispose()
+            assertThat(session.isOpen).isFalse()
+        }
+    }
+
+    @Test(expected = IllegalStateException::class)
+    fun startingInputSessionOnNonMainThread_throwsIllegalStateException() {
+        adapter.startInputSession(TextFieldState(), ImeOptions.Default)
+    }
+
+    @Test
+    fun creatingSecondInputSession_closesFirstOne() {
+        val state = TextFieldState()
+        rule.runOnUiThread {
+            val session1 = adapter.startInputSession(state, ImeOptions.Default)
+            val session2 = adapter.startInputSession(state, ImeOptions.Default)
+
+            assertThat(session1.isOpen).isFalse()
+            assertThat(session2.isOpen).isTrue()
+        }
+    }
+
+    @Test
+    fun createInputConnection_modifiesEditorInfo() {
+        val state = TextFieldState(TextFieldValue("hello", selection = TextRange(0, 5)))
+        rule.runOnUiThread {
+            adapter.startInputSession(state, ImeOptions.Default)
+            val editorInfo = EditorInfo()
+            adapter.createInputConnection(editorInfo)
+
+            assertThat(editorInfo.initialSelStart).isEqualTo(0)
+            assertThat(editorInfo.initialSelEnd).isEqualTo(5)
+            assertThat(editorInfo.inputType).isEqualTo(
+                InputType.TYPE_CLASS_TEXT or
+                    InputType.TYPE_TEXT_FLAG_MULTI_LINE or
+                    InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+            )
+            assertThat(editorInfo.imeOptions).isEqualTo(
+                    EditorInfo.IME_FLAG_NO_FULLSCREEN or
+                    EditorInfo.IME_FLAG_NO_ENTER_ACTION
+            )
+        }
+    }
+
+    @Test
+    fun inputConnection_sendsUpdates_toActiveSession() {
+        val state1 = TextFieldState()
+        val state2 = TextFieldState()
+        rule.runOnUiThread {
+            adapter.startInputSession(state1, ImeOptions.Default)
+            adapter.startInputSession(state2, ImeOptions.Default)
+
+            val connection = adapter.createInputConnection(EditorInfo())
+
+            connection.commitText("Hello", 0)
+
+            assertThat(state1.value.text).isEqualTo("")
+            assertThat(state2.value.text).isEqualTo("Hello")
+        }
+    }
+
+    @Test
+    fun createInputConnection_updatesEditorInfo() {
+        val editorInfo = EditorInfo()
+        rule.runOnUiThread {
+            adapter.startInputSession(
+                TextFieldState(),
+                ImeOptions(
+                    singleLine = true,
+                    keyboardType = KeyboardType.Email,
+                    autoCorrect = false,
+                    imeAction = ImeAction.Search,
+                    capitalization = KeyboardCapitalization.Words
+                )
+            )
+
+            adapter.createInputConnection(editorInfo)
+
+            assertThat(editorInfo.inputType).isEqualTo(
+                InputType.TYPE_CLASS_TEXT or
+                    InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or
+                    InputType.TYPE_TEXT_FLAG_CAP_WORDS
+            )
+            assertThat(editorInfo.imeOptions).isEqualTo(
+                EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN
+            )
+        }
+    }
+
+    @Test
+    fun createInputConnection_updatesEditorInfo_withTheLatestSession() {
+        val editorInfo = EditorInfo()
+        rule.runOnUiThread {
+            adapter.startInputSession(
+                TextFieldState(),
+                ImeOptions(
+                    keyboardType = KeyboardType.Number
+                )
+            )
+            adapter.startInputSession(
+                TextFieldState(),
+                ImeOptions(
+                    singleLine = true,
+                    keyboardType = KeyboardType.Email,
+                    autoCorrect = false,
+                    imeAction = ImeAction.Search,
+                    capitalization = KeyboardCapitalization.Words
+                )
+            )
+
+            adapter.createInputConnection(editorInfo)
+
+            assertThat(editorInfo.inputType).isEqualTo(
+                InputType.TYPE_CLASS_TEXT or
+                    InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS or
+                    InputType.TYPE_TEXT_FLAG_CAP_WORDS
+            )
+            assertThat(editorInfo.imeOptions).isEqualTo(
+                EditorInfo.IME_ACTION_SEARCH or EditorInfo.IME_FLAG_NO_FULLSCREEN
+            )
+        }
+    }
+
+    @Test
+    fun createInputConnection_updatesEditorInfo_noActiveSession() {
+        val noActiveSessionEI = EditorInfo()
+        val activeSessionEI = EditorInfo()
+        val disposedSessionEI = EditorInfo()
+        rule.runOnUiThread {
+            adapter.createInputConnection(noActiveSessionEI)
+            val session = adapter.startInputSession(
+                TextFieldState(),
+                ImeOptions(
+                    keyboardType = KeyboardType.Number
+                )
+            )
+            adapter.createInputConnection(activeSessionEI)
+            session.dispose()
+            adapter.createInputConnection(disposedSessionEI)
+
+            assertThat(noActiveSessionEI.inputType).isNotEqualTo(activeSessionEI.inputType)
+            assertThat(noActiveSessionEI.imeOptions).isNotEqualTo(activeSessionEI.imeOptions)
+
+            assertThat(noActiveSessionEI.inputType).isEqualTo(disposedSessionEI.inputType)
+            assertThat(noActiveSessionEI.imeOptions).isEqualTo(disposedSessionEI.imeOptions)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/EditorInfoTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/EditorInfoTest.kt
new file mode 100644
index 0000000..3055b66
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/EditorInfoTest.kt
@@ -0,0 +1,542 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.service
+
+import android.text.InputType
+import android.view.inputmethod.EditorInfo
+import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.text.input.TextFieldValue
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class EditorInfoTest {
+
+    @Test
+    fun test_fill_editor_info_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_ascii() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_number() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_phone() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Phone,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_PHONE and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_uri() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Uri,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_URI and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_email() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Email,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_EMAIL_ADDRESS and info.inputType) != 0)
+            .isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_password() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Password,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_number_password() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.NumberPassword,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_NUMBER_VARIATION_PASSWORD and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_decimal_number() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Decimal,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_NUMBER and info.inputType) != 0).isTrue()
+        assertThat((InputType.TYPE_NUMBER_FLAG_DECIMAL and info.inputType) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_UNSPECIFIED
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_none() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.None
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_NONE
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_go() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Go
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_GO
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_next() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Next
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_NEXT
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_previous() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Previous
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_PREVIOUS
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_search() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Search,
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_SEARCH
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_send() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Send
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_SEND
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_action_done() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_CLASS_TEXT and info.inputType) != 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_FORCE_ASCII and info.imeOptions) != 0).isTrue()
+        assertThat(
+            (EditorInfo.IME_MASK_ACTION and info.imeOptions)
+                == EditorInfo.IME_ACTION_DONE
+        ).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line_with_default_action() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isFalse()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_single_line() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = true,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_single_line_changes_ime_from_unspecified_to_done() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = true,
+                keyboardType = KeyboardType.Text,
+                imeAction = ImeAction.Default
+            )
+        )
+
+        assertThat((EditorInfo.IME_ACTION_DONE and info.imeOptions) == 0).isFalse()
+        assertThat((EditorInfo.IME_ACTION_UNSPECIFIED and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_multi_line_not_set_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                singleLine = false,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_MULTI_LINE and info.inputType) == 0).isTrue()
+        assertThat((EditorInfo.IME_FLAG_NO_ENTER_ACTION and info.imeOptions) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_none() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.None,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_characters() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Characters,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isFalse()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_words() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Words,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isFalse()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_sentences() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Sentences,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done,
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_capitalization_not_added_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                capitalization = KeyboardCapitalization.Sentences,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done,
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_WORDS and info.inputType) == 0).isTrue()
+        assertThat((InputType.TYPE_TEXT_FLAG_CAP_SENTENCES and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun test_fill_editor_info_auto_correct_on() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = true,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isFalse()
+    }
+
+    @Test
+    fun test_fill_editor_info_auto_correct_off() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = false,
+                keyboardType = KeyboardType.Ascii,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun autocorrect_not_added_when_input_type_is_not_text() {
+        val info = EditorInfo()
+        info.update(
+            ImeOptions(
+                autoCorrect = true,
+                keyboardType = KeyboardType.Number,
+                imeAction = ImeAction.Done
+            )
+        )
+
+        assertThat((InputType.TYPE_TEXT_FLAG_AUTO_CORRECT and info.inputType) == 0).isTrue()
+    }
+
+    @Test
+    fun initial_default_selection_info_is_set() {
+        val info = EditorInfo()
+        info.update(ImeOptions.Default)
+
+        assertThat(info.initialSelStart).isEqualTo(0)
+        assertThat(info.initialSelEnd).isEqualTo(0)
+    }
+
+    @Test
+    fun initial_selection_info_is_set() {
+        val selection = TextRange(1, 2)
+        val info = EditorInfo()
+        info.update(TextFieldValue("abc", selection), ImeOptions.Default)
+
+        assertThat(info.initialSelStart).isEqualTo(selection.start)
+        assertThat(info.initialSelEnd).isEqualTo(selection.end)
+    }
+
+    private fun EditorInfo.update(imeOptions: ImeOptions) {
+        this.update(TextFieldValue(), imeOptions)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt
similarity index 98%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt
rename to compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt
index 624768d..35a023d 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/androidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnectionTest.kt
@@ -26,6 +26,7 @@
 import androidx.compose.foundation.text2.input.SetSelectionCommand
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.text.TextRange
+import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -48,6 +49,7 @@
 
     private var isOpen: Boolean = true
     private var value: TextFieldValue = TextFieldValue()
+    private var imeOptions: ImeOptions = ImeOptions.Default
     private var onRequestEdits: ((List<EditCommand>) -> Unit)? = null
 
     private val activeSessionProvider: () -> EditableTextInputSession? = { activeSession }
@@ -62,6 +64,9 @@
             override val value: TextFieldValue
                 get() = this@StatelessInputConnectionTest.value
 
+            override val imeOptions: ImeOptions
+                get() = this@StatelessInputConnectionTest.imeOptions
+
             override fun requestEdits(editCommands: List<EditCommand>) {
                 onRequestEdits?.invoke(editCommands)
             }
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
new file mode 100644
index 0000000..31dcd3d0
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/BasicTextField2.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.interaction.Interaction
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.foundation.text.DefaultMinLines
+import androidx.compose.foundation.text.InternalFoundationTextApi
+import androidx.compose.foundation.text.KeyboardOptions
+import androidx.compose.foundation.text.TextDelegate
+import androidx.compose.foundation.text.heightInLines
+import androidx.compose.foundation.text.selection.LocalTextSelectionColors
+import androidx.compose.foundation.text.textFieldMinSize
+import androidx.compose.foundation.text2.input.CommitTextCommand
+import androidx.compose.foundation.text2.input.DeleteAllCommand
+import androidx.compose.foundation.text2.service.AndroidTextInputPlugin
+import androidx.compose.foundation.text2.service.TextInputSession
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.DisposableEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.drawBehind
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
+import androidx.compose.ui.layout.FirstBaseline
+import androidx.compose.ui.layout.LastBaseline
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalFontFamilyResolver
+import androidx.compose.ui.platform.LocalPlatformTextInputPluginRegistry
+import androidx.compose.ui.semantics.disabled
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.setText
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextPainter
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.KeyboardType
+import androidx.compose.ui.unit.Density
+import kotlin.math.roundToInt
+
+/**
+ * BasicTextField2 is a new text input Composable under heavy development. Please refrain from
+ * using it in production since it has a very unstable API and implementation for the time being.
+ *
+ * @param state State object that holds the internal state of a [BasicTextField2]
+ * @param modifier optional [Modifier] for this text field.
+ * @param enabled controls the enabled state of the [BasicTextField2]. When `false`, the text
+ * field will be neither editable nor focusable, the input of the text field will not be selectable.
+ * @param readOnly controls the editable state of the [BasicTextField2]. When `true`, the text
+ * field can not be modified, however, a user can focus it and copy text from it. Read-only text
+ * fields are usually used to display pre-filled forms that user can not edit
+ * @param textStyle Style configuration for text content that's displayed in the editor.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this TextField. You can create and pass in your own remembered [MutableInteractionSource]
+ * if you want to observe [Interaction]s and customize the appearance / behavior of this TextField
+ * for different [Interaction]s.
+ * @param cursorBrush [Brush] to paint cursor with. If [SolidColor] with [Color.Unspecified]
+ * provided, there will be no cursor drawn
+ * @param minLines The minimum height in terms of minimum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
+ * @param maxLines The maximum height in terms of maximum number of visible lines. It is required
+ * that 1 <= [minLines] <= [maxLines].
+ * @param keyboardOptions software keyboard options that contains configuration such as
+ * [KeyboardType] and [ImeAction].
+ * @param onTextLayout Callback that is executed when a new text layout is calculated. A
+ * [TextLayoutResult] object that callback provides contains paragraph information, size of the
+ * text, baselines and other details. The callback can be used to add additional decoration or
+ * functionality to the text. For example, to draw a cursor or selection around the text. [Density]
+ * scope is the one that was used while creating the given text layout.
+ */
+@ExperimentalFoundationApi
+@OptIn(InternalFoundationTextApi::class)
+@Composable
+fun BasicTextField2(
+    state: TextFieldState,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    readOnly: Boolean = false,
+    textStyle: TextStyle = TextStyle.Default,
+    interactionSource: MutableInteractionSource? = null,
+    cursorBrush: Brush = SolidColor(Color.Black),
+    minLines: Int = DefaultMinLines,
+    maxLines: Int = Int.MAX_VALUE,
+    keyboardOptions: KeyboardOptions = KeyboardOptions.Default,
+    onTextLayout: Density.(TextLayoutResult) -> Unit = {}
+) {
+    // only read from local and create an adapter if this text field is enabled and editable
+    val textInputAdapter = LocalPlatformTextInputPluginRegistry.takeIf { enabled && !readOnly }
+        ?.current?.rememberAdapter(AndroidTextInputPlugin)
+
+    val focusRequester = remember { FocusRequester() }
+
+    val fontFamilyResolver = LocalFontFamilyResolver.current
+    val density = LocalDensity.current
+    val selectionBackgroundColor = LocalTextSelectionColors.current.backgroundColor
+    val singleLine = minLines == 1 && maxLines == 1
+
+    val textLayoutState = remember {
+        TextLayoutState(
+            TextDelegate(
+                text = state.value.annotatedString,
+                style = textStyle,
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                softWrap = true,
+                placeholders = emptyList()
+            )
+        )
+    }
+
+    var isFocused by remember { mutableStateOf(false) }
+
+    val textInputSessionState = remember { mutableStateOf<TextInputSession?>(null) }
+
+    // Hide the keyboard if made disabled or read-only while focused (b/237308379).
+    if (enabled && !readOnly) {
+        // TODO(b/230536793) This is a workaround since we don't get an explicit focus blur event
+        //  when the text field is removed from the composition entirely.
+        DisposableEffect(state) {
+            if (isFocused) {
+                textInputSessionState.value = textInputAdapter?.startInputSession(
+                    state,
+                    keyboardOptions.toImeOptions(singleLine)
+                )
+            }
+            onDispose {
+                if (isFocused) {
+                    textInputSessionState.value?.dispose()
+                    textInputSessionState.value = null
+                }
+            }
+        }
+    }
+
+    val semanticsModifier = Modifier.semantics {
+        if (!enabled) this.disabled()
+
+        setText { text ->
+            state.editProcessor.update(
+                listOf(
+                    DeleteAllCommand,
+                    CommitTextCommand(text, 1)
+                )
+            )
+            true
+        }
+        onClick {
+            // according to the documentation, we still need to provide proper semantics actions
+            // even if the state is 'disabled'
+            if (!isFocused) {
+                focusRequester.requestFocus()
+            }
+            true
+        }
+    }
+
+    val drawModifier = Modifier.drawBehind {
+        textLayoutState.layoutResult?.let { layoutResult ->
+            // draw selection
+            val value = state.value
+            if (!value.selection.collapsed) {
+                val start = value.selection.min
+                val end = value.selection.max
+                if (start != end) {
+                    val selectionPath = layoutResult.getPathForRange(start, end)
+                    drawPath(selectionPath, color = selectionBackgroundColor)
+                }
+            }
+            // draw text
+            drawIntoCanvas { canvas ->
+                TextPainter.paint(canvas, layoutResult)
+            }
+        }
+    }
+
+    val focusModifier = Modifier
+        .focusRequester(focusRequester)
+        .onFocusChanged {
+            if (isFocused == it.isFocused) {
+                return@onFocusChanged
+            }
+            isFocused = it.isFocused
+
+            if (it.isFocused) {
+                textInputSessionState.value = textInputAdapter?.startInputSession(
+                    state,
+                    keyboardOptions.toImeOptions(singleLine)
+                )
+                // TODO(halilibo): bringIntoView
+            } else {
+                state.deselect()
+            }
+        }
+        .focusable(interactionSource = interactionSource, enabled = enabled)
+
+    val cursorModifier = Modifier.cursor(
+        textLayoutState = textLayoutState,
+        isFocused = isFocused,
+        state = state,
+        cursorBrush = cursorBrush,
+        enabled = enabled && !readOnly
+    )
+
+    Layout(
+        content = {},
+        modifier = modifier
+            .then(focusModifier)
+            .heightInLines(
+                textStyle = textStyle,
+                minLines = minLines,
+                maxLines = maxLines
+            )
+            .then(drawModifier)
+            .textFieldMinSize(textStyle)
+            .then(cursorModifier)
+            .clickable {
+                focusRequester.requestFocus()
+            }
+            .then(semanticsModifier)
+            .then(TextFieldContentSemanticsElement(state))
+    ) { _, constraints ->
+        val result = with(textLayoutState) {
+            layout(
+                text = state.value.annotatedString,
+                textStyle = textStyle,
+                softWrap = true,
+                density = density,
+                fontFamilyResolver = fontFamilyResolver,
+                constraints = constraints,
+                onTextLayout = onTextLayout
+            )
+        }
+
+        // TODO: min height
+
+        layout(
+            width = result.size.width,
+            height = result.size.height,
+            alignmentLines = mapOf(
+                FirstBaseline to result.firstBaseline.roundToInt(),
+                LastBaseline to result.lastBaseline.roundToInt()
+            )
+        ) {}
+    }
+}
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldContentSemantics.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldContentSemantics.kt
new file mode 100644
index 0000000..a0e9a73
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldContentSemantics.kt
@@ -0,0 +1,94 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.SemanticsModifierNode
+import androidx.compose.ui.platform.InspectorInfo
+import androidx.compose.ui.semantics.SemanticsConfiguration
+import androidx.compose.ui.semantics.editableText
+import androidx.compose.ui.semantics.textSelectionRange
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextRange
+
+@OptIn(ExperimentalFoundationApi::class)
+internal class TextFieldContentSemanticsElement(
+    private val state: TextFieldState
+) : ModifierNodeElement<TextFieldContentSemanticsNode>() {
+    override fun create(): TextFieldContentSemanticsNode = TextFieldContentSemanticsNode(state)
+
+    override fun update(node: TextFieldContentSemanticsNode): TextFieldContentSemanticsNode {
+        node.state = state
+        return node
+    }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is TextFieldContentSemanticsElement) return false
+
+        if (state != other.state) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        return state.hashCode()
+    }
+
+    override fun InspectorInfo.inspectableProperties() {
+        // Show nothing in the inspector.
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+internal data class TextFieldContentSemanticsNode(
+    var state: TextFieldState
+) : Modifier.Node(), SemanticsModifierNode {
+
+    private var lastText: AnnotatedString? = null
+    private var lastSelection: TextRange? = null
+
+    private var _semanticsConfiguration: SemanticsConfiguration? = null
+
+    private fun generateSemantics(
+        text: AnnotatedString,
+        selection: TextRange
+    ): SemanticsConfiguration {
+        lastText = text
+        lastSelection = selection
+        return SemanticsConfiguration().also {
+            it.editableText = text
+            it.textSelectionRange = selection
+            _semanticsConfiguration = it
+        }
+    }
+
+    override val semanticsConfiguration: SemanticsConfiguration
+        get() {
+            var localSemantics = _semanticsConfiguration
+            val value = state.value
+            if (localSemantics == null ||
+                lastText != value.annotatedString ||
+                lastSelection != value.selection
+            ) {
+                localSemantics = generateSemantics(value.annotatedString, value.selection)
+            }
+            return localSemantics
+        }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldCursor.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldCursor.kt
new file mode 100644
index 0000000..9a14151
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldCursor.kt
@@ -0,0 +1,98 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.AnimationSpec
+import androidx.compose.animation.core.infiniteRepeatable
+import androidx.compose.animation.core.keyframes
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.MotionDurationScale
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.isUnspecified
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.withContext
+
+@OptIn(ExperimentalFoundationApi::class)
+internal fun Modifier.cursor(
+    textLayoutState: TextLayoutState,
+    isFocused: Boolean,
+    state: TextFieldState,
+    cursorBrush: Brush,
+    enabled: Boolean
+) = if (enabled) composed {
+    val cursorAlpha = remember { Animatable(1f) }
+    val isBrushSpecified = !(cursorBrush is SolidColor && cursorBrush.value.isUnspecified)
+    val value = state.value
+    if (isFocused && value.selection.collapsed && isBrushSpecified) {
+        LaunchedEffect(value.annotatedString, value.selection) {
+            // Animate the cursor even when animations are disabled by the system.
+            withContext(FixedMotionDurationScale) {
+                // ensure that the value is always 1f _this_ frame by calling snapTo
+                cursorAlpha.snapTo(1f)
+                // then start the cursor blinking on animation clock (500ms on to start)
+                cursorAlpha.animateTo(0f, cursorAnimationSpec)
+            }
+        }
+        drawWithContent {
+            this.drawContent()
+            val cursorAlphaValue = cursorAlpha.value.coerceIn(0f, 1f)
+            if (cursorAlphaValue != 0f) {
+                val cursorRect = textLayoutState.layoutResult?.getCursorRect(value.selection.start)
+                    ?: Rect(0f, 0f, 0f, 0f)
+                val cursorWidth = DefaultCursorThickness.toPx()
+                val cursorX = (cursorRect.left + cursorWidth / 2)
+                    .coerceAtMost(size.width - cursorWidth / 2)
+
+                drawLine(
+                    cursorBrush,
+                    Offset(cursorX, cursorRect.top),
+                    Offset(cursorX, cursorRect.bottom),
+                    alpha = cursorAlphaValue,
+                    strokeWidth = cursorWidth
+                )
+            }
+        }
+    } else {
+        Modifier
+    }
+} else this
+
+private val cursorAnimationSpec: AnimationSpec<Float> = infiniteRepeatable(
+    animation = keyframes {
+        durationMillis = 1000
+        1f at 0
+        1f at 499
+        0f at 500
+        0f at 999
+    }
+)
+
+internal val DefaultCursorThickness = 2.dp
+
+private object FixedMotionDurationScale : MotionDurationScale {
+    override val scaleFactor: Float
+        get() = 1f
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/TextFieldState.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldState.kt
similarity index 89%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/TextFieldState.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldState.kt
index 8e456e9..ca62583 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/TextFieldState.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextFieldState.kt
@@ -16,10 +16,12 @@
 
 package androidx.compose.foundation.text2
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.input.EditProcessor
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.input.TextFieldValue
 
+@ExperimentalFoundationApi
 class TextFieldState(
     initialValue: TextFieldValue = TextFieldValue(),
     filter: TextEditFilter = TextEditFilter.Default
@@ -31,6 +33,7 @@
         get() = editProcessor.value
 }
 
+@ExperimentalFoundationApi
 fun interface TextEditFilter {
 
     fun filter(oldValue: TextFieldValue, newValue: TextFieldValue): TextFieldValue
@@ -40,6 +43,7 @@
     }
 }
 
+@OptIn(ExperimentalFoundationApi::class)
 internal fun TextFieldState.deselect() {
     editProcessor.reset(value.copy(selection = TextRange.Zero, composition = TextRange.Zero))
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextLayoutState.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextLayoutState.kt
new file mode 100644
index 0000000..c211f83
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/TextLayoutState.kt
@@ -0,0 +1,81 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2
+
+import androidx.compose.foundation.text.InternalFoundationTextApi
+import androidx.compose.foundation.text.TextDelegate
+import androidx.compose.foundation.text.updateTextDelegate
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.text.TextLayoutResult
+import androidx.compose.ui.text.TextStyle
+import androidx.compose.ui.text.font.FontFamily
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
+
+@OptIn(InternalFoundationTextApi::class)
+internal class TextLayoutState(initialTextDelegate: TextDelegate) {
+    /**
+     * Set of parameters and an internal cache to compute text layout.
+     */
+    var textDelegate: TextDelegate by mutableStateOf(initialTextDelegate)
+        private set
+
+    /**
+     * Text Layout State.
+     */
+    var layoutResult: TextLayoutResult? by mutableStateOf(null)
+        private set
+
+    fun MeasureScope.layout(
+        text: AnnotatedString,
+        textStyle: TextStyle,
+        softWrap: Boolean,
+        density: Density,
+        fontFamilyResolver: FontFamily.Resolver,
+        constraints: Constraints,
+        onTextLayout: Density.(TextLayoutResult) -> Unit
+    ): TextLayoutResult {
+        val prevResult = Snapshot.withoutReadObservation { layoutResult }
+
+        val newTextDelegate = updateTextDelegate(
+            current = textDelegate,
+            text = text,
+            style = textStyle,
+            softWrap = softWrap,
+            density = density,
+            fontFamilyResolver = fontFamilyResolver,
+            placeholders = emptyList(),
+        )
+
+        return newTextDelegate.layout(
+            layoutDirection = layoutDirection,
+            constraints = constraints,
+            prevResult = prevResult
+        ).also {
+            textDelegate = newTextDelegate
+            if (prevResult != it) {
+                onTextLayout(it)
+            }
+            layoutResult = it
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/ApplyEditCommand.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/ApplyEditCommand.kt
similarity index 93%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/ApplyEditCommand.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/ApplyEditCommand.kt
index 0a1412a..685449b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/ApplyEditCommand.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/ApplyEditCommand.kt
@@ -174,15 +174,17 @@
 private fun EditingBuffer.applyDeleteSurroundingTextCommand(
     deleteSurroundingTextCommand: DeleteSurroundingTextCommand
 ) {
-    delete(
-        selectionEnd,
-        minOf(selectionEnd + deleteSurroundingTextCommand.lengthAfterCursor, length)
-    )
+    // calculate the end with safe addition since lengthAfterCursor can be set to e.g. Int.MAX
+    // by the input
+    val end = selectionEnd.addExactOrElse(deleteSurroundingTextCommand.lengthAfterCursor) { length }
+    delete(selectionEnd, minOf(end, length))
 
-    delete(
-        maxOf(0, selectionStart - deleteSurroundingTextCommand.lengthBeforeCursor),
-        selectionStart
-    )
+    // calculate the start with safe subtraction since lengthBeforeCursor can be set to e.g.
+    // Int.MAX by the input
+    val start = selectionStart.subtractExactOrElse(
+        deleteSurroundingTextCommand.lengthBeforeCursor
+    ) { 0 }
+    delete(maxOf(0, start), selectionStart)
 }
 
 private fun EditingBuffer.applyBackspaceCommand() {
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EditCommand.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditCommand.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EditCommand.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditCommand.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EdiProcessor.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditProcessor.kt
similarity index 98%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EdiProcessor.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditProcessor.kt
index a3a04e9..ed691a5 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EdiProcessor.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditProcessor.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.text2.input
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.TextEditFilter
 import androidx.compose.runtime.collection.mutableVectorOf
 import androidx.compose.runtime.getValue
@@ -35,6 +36,7 @@
  * When [TextInputService] provides [EditCommand]s, they should be applied to the internal
  * buffer using [apply].
  */
+@OptIn(ExperimentalFoundationApi::class)
 internal class EditProcessor(
     initialValue: TextFieldValue = TextFieldValue(
         EmptyAnnotatedString,
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EditingBuffer.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditingBuffer.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/EditingBuffer.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/EditingBuffer.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/GapBuffer.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/GapBuffer.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/input/GapBuffer.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/GapBuffer.kt
diff --git a/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/MathUItils.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/MathUItils.kt
new file mode 100644
index 0000000..9ac4025
--- /dev/null
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/input/MathUItils.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.text2.input
+
+/**
+ * Adds [this] and [right], and if an overflow occurs returns result of [defaultValue].
+ */
+internal inline fun Int.addExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this + right
+    // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+    return if (this xor result and (right xor result) < 0) defaultValue() else result
+}
+
+/**
+ * Subtracts [right] from [this], and if an overflow occurs returns result of [defaultValue].
+ */
+internal fun Int.subtractExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this - right
+    // HD 2-12 Overflow iff the arguments have different signs and
+    // the sign of the result is different from the sign of x
+    return if (this xor right and (this xor result) < 0) defaultValue() else result
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt
similarity index 71%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt
index a7d49ba..d1ef7d2 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputAdapter.kt
@@ -14,6 +14,8 @@
  * limitations under the License.
  */
 
+@file:OptIn(ExperimentalFoundationApi::class)
+
 package androidx.compose.foundation.text2.service
 
 import android.os.Looper
@@ -23,11 +25,16 @@
 import android.view.View
 import android.view.inputmethod.EditorInfo
 import android.view.inputmethod.InputConnection
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.text2.TextFieldState
+import androidx.compose.foundation.text2.input.CommitTextCommand
 import androidx.compose.foundation.text2.input.EditCommand
 import androidx.compose.foundation.text2.input.EditProcessor
 import androidx.compose.runtime.collection.mutableVectorOf
-import androidx.compose.ui.text.ExperimentalTextApi
+import androidx.compose.ui.text.input.ImeAction
+import androidx.compose.ui.text.input.ImeOptions
+import androidx.compose.ui.text.input.KeyboardCapitalization
+import androidx.compose.ui.text.input.KeyboardType
 import androidx.compose.ui.text.input.PlatformTextInput
 import androidx.compose.ui.text.input.PlatformTextInputAdapter
 import androidx.compose.ui.text.input.TextFieldValue
@@ -38,7 +45,6 @@
 private const val DEBUG = true
 private const val TAG = "BasicTextInputAdapter"
 
-@OptIn(ExperimentalTextApi::class)
 internal class AndroidTextInputAdapter constructor(
     view: View,
     private val platformTextInput: PlatformTextInput
@@ -50,20 +56,25 @@
 
     private val textInputCommandExecutor = TextInputCommandExecutor(view, inputMethodManager)
 
-    override val inputForTests: TextInputForTests
-        get() = error("Not implemented")
+    override val inputForTests: TextInputForTests = object : TextInputForTests {
+        private fun requireSession(): EditableTextInputSession =
+            currentTextInputSession ?: error("There is no active input session. Missing a focus?")
+
+        override fun inputTextForTest(text: String) {
+            requireSession().requestEdits(
+                listOf(CommitTextCommand(text, 1))
+            )
+        }
+    }
 
     override fun createInputConnection(outAttrs: EditorInfo): InputConnection {
         logDebug { "createInputConnection" }
         val value = currentTextInputSession?.value ?: TextFieldValue()
+        val imeOptions = currentTextInputSession?.imeOptions ?: ImeOptions.Default
 
         logDebug { "createInputConnection.value = $value" }
 
-        outAttrs.initialSelStart = value.selection.min
-        outAttrs.initialSelEnd = value.selection.max
-        outAttrs.inputType = InputType.TYPE_CLASS_TEXT
-        EditorInfoCompat.setInitialSurroundingText(outAttrs, value.text)
-        outAttrs.imeOptions = EditorInfo.IME_ACTION_DONE or EditorInfo.IME_FLAG_NO_FULLSCREEN
+        outAttrs.update(value, imeOptions)
 
         return StatelessInputConnection(
             activeSessionProvider = { currentTextInputSession }
@@ -88,7 +99,8 @@
     }
 
     fun startInputSession(
-        state: TextFieldState
+        state: TextFieldState,
+        imeOptions: ImeOptions
     ): TextInputSession {
         if (!isMainThread()) {
             throw IllegalStateException("Input sessions can only be started from the main thread.")
@@ -105,6 +117,8 @@
             override val value: TextFieldValue
                 get() = state.value
 
+            override val imeOptions: ImeOptions = imeOptions
+
             override fun requestEdits(editCommands: List<EditCommand>) {
                 state.editProcessor.update(editCommands)
             }
@@ -283,6 +297,99 @@
     postFrameCallback { runnable.run() }
 }
 
+/**
+ * Fills necessary info of EditorInfo.
+ */
+internal fun EditorInfo.update(textFieldValue: TextFieldValue, imeOptions: ImeOptions) {
+    this.imeOptions = when (imeOptions.imeAction) {
+        ImeAction.Default -> {
+            if (imeOptions.singleLine) {
+                // this is the last resort to enable single line
+                // Android IME still shows return key even if multi line is not send
+                // TextView.java#onCreateInputConnection
+                EditorInfo.IME_ACTION_DONE
+            } else {
+                EditorInfo.IME_ACTION_UNSPECIFIED
+            }
+        }
+        ImeAction.None -> EditorInfo.IME_ACTION_NONE
+        ImeAction.Go -> EditorInfo.IME_ACTION_GO
+        ImeAction.Next -> EditorInfo.IME_ACTION_NEXT
+        ImeAction.Previous -> EditorInfo.IME_ACTION_PREVIOUS
+        ImeAction.Search -> EditorInfo.IME_ACTION_SEARCH
+        ImeAction.Send -> EditorInfo.IME_ACTION_SEND
+        ImeAction.Done -> EditorInfo.IME_ACTION_DONE
+        else -> error("invalid ImeAction")
+    }
+
+    this.inputType = when (imeOptions.keyboardType) {
+        KeyboardType.Text -> InputType.TYPE_CLASS_TEXT
+        KeyboardType.Ascii -> {
+            this.imeOptions = this.imeOptions or EditorInfo.IME_FLAG_FORCE_ASCII
+            InputType.TYPE_CLASS_TEXT
+        }
+        KeyboardType.Number ->
+            InputType.TYPE_CLASS_NUMBER
+        KeyboardType.Phone ->
+            InputType.TYPE_CLASS_PHONE
+        KeyboardType.Uri ->
+            InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_URI
+        KeyboardType.Email ->
+            InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_EMAIL_ADDRESS
+        KeyboardType.Password ->
+            InputType.TYPE_CLASS_TEXT or EditorInfo.TYPE_TEXT_VARIATION_PASSWORD
+        KeyboardType.NumberPassword ->
+            InputType.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_VARIATION_PASSWORD
+        KeyboardType.Decimal ->
+            InputType.TYPE_CLASS_NUMBER or EditorInfo.TYPE_NUMBER_FLAG_DECIMAL
+        else -> error("Invalid Keyboard Type")
+    }
+
+    if (!imeOptions.singleLine) {
+        if (hasFlag(this.inputType, InputType.TYPE_CLASS_TEXT)) {
+            // TextView.java#setInputTypeSingleLine
+            this.inputType = this.inputType or InputType.TYPE_TEXT_FLAG_MULTI_LINE
+
+            if (imeOptions.imeAction == ImeAction.Default) {
+                this.imeOptions = this.imeOptions or EditorInfo.IME_FLAG_NO_ENTER_ACTION
+            }
+        }
+    }
+
+    if (hasFlag(this.inputType, InputType.TYPE_CLASS_TEXT)) {
+        when (imeOptions.capitalization) {
+            KeyboardCapitalization.Characters -> {
+                this.inputType = this.inputType or InputType.TYPE_TEXT_FLAG_CAP_CHARACTERS
+            }
+
+            KeyboardCapitalization.Words -> {
+                this.inputType = this.inputType or InputType.TYPE_TEXT_FLAG_CAP_WORDS
+            }
+
+            KeyboardCapitalization.Sentences -> {
+                this.inputType = this.inputType or InputType.TYPE_TEXT_FLAG_CAP_SENTENCES
+            }
+
+            else -> {
+                /* do nothing */
+            }
+        }
+
+        if (imeOptions.autoCorrect) {
+            this.inputType = this.inputType or InputType.TYPE_TEXT_FLAG_AUTO_CORRECT
+        }
+    }
+
+    this.initialSelStart = textFieldValue.selection.start
+    this.initialSelEnd = textFieldValue.selection.end
+
+    EditorInfoCompat.setInitialSurroundingText(this, textFieldValue.text)
+
+    this.imeOptions = this.imeOptions or EditorInfo.IME_FLAG_NO_FULLSCREEN
+}
+
+private fun hasFlag(bits: Int, flag: Int): Boolean = (bits and flag) == flag
+
 private fun logDebug(tag: String = TAG, content: () -> String) {
     if (DEBUG) {
         Log.d(tag, content())
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt
similarity index 92%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt
index 94b5951..9fb5c65 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/AndroidTextInputPlugin.kt
@@ -17,11 +17,9 @@
 package androidx.compose.foundation.text2.service
 
 import android.view.View
-import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.input.PlatformTextInput
 import androidx.compose.ui.text.input.PlatformTextInputPlugin
 
-@OptIn(ExperimentalTextApi::class)
 internal object AndroidTextInputPlugin : PlatformTextInputPlugin<AndroidTextInputAdapter> {
 
     override fun createAdapter(
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/ComposeInputMethodManager.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/ComposeInputMethodManager.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/ComposeInputMethodManager.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/ComposeInputMethodManager.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/StatelessInputConnection.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnection.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/StatelessInputConnection.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/StatelessInputConnection.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/TextInputSession.kt b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/TextInputSession.kt
similarity index 89%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/TextInputSession.kt
rename to compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/TextInputSession.kt
index c18e20e..4e29e6b 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/text2/service/TextInputSession.kt
+++ b/compose/foundation/foundation/src/androidMain/kotlin/androidx/compose/foundation/text2/service/TextInputSession.kt
@@ -16,8 +16,10 @@
 
 package androidx.compose.foundation.text2.service
 
+import android.view.inputmethod.InputConnection
 import androidx.compose.foundation.text2.TextFieldState
 import androidx.compose.foundation.text2.input.EditCommand
+import androidx.compose.ui.text.input.ImeOptions
 import androidx.compose.ui.text.input.TextFieldValue
 
 /**
@@ -59,4 +61,9 @@
      * Callback to execute for InputConnection to communicate the changes requested by the IME.
      */
     fun requestEdits(editCommands: List<EditCommand>)
+
+    /**
+     * IME configuration to use when creating new [InputConnection]s while this session is active.
+     */
+    val imeOptions: ImeOptions
 }
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
deleted file mode 100644
index 9479a6c..0000000
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/gestures/DragGestureDetectorTest.kt
+++ /dev/null
@@ -1,524 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.compose.foundation.gestures
-
-import androidx.compose.ui.geometry.Offset
-import androidx.compose.ui.input.pointer.changedToUpIgnoreConsumed
-import androidx.compose.ui.input.pointer.positionChange
-import org.junit.Assert.assertEquals
-import org.junit.Assert.assertFalse
-import org.junit.Assert.assertTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
-import org.junit.runners.Parameterized
-
-@RunWith(Parameterized::class)
-class DragGestureDetectorTest(dragType: GestureType) {
-    enum class GestureType {
-        VerticalDrag,
-        HorizontalDrag,
-        AwaitVerticalDragOrCancel,
-        AwaitHorizontalDragOrCancel,
-        AwaitDragOrCancel,
-        DragWithVertical,
-        DragWithHorizontal,
-    }
-
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters
-        fun parameters() = GestureType.values()
-    }
-
-    private var dragDistance = 0f
-    private var dragged = false
-    private var gestureEnded = false
-    private var gestureStarted = false
-    private var gestureCanceled = false
-    private var consumePositiveOnly = false
-    private var sloppyDetector = false
-    private var startOrder = -1
-    private var endOrder = -1
-    private var cancelOrder = -1
-    private var dragOrder = -1
-
-    private val DragTouchSlopUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        var count = 0
-        detectDragGestures(
-            onDragStart = {
-                gestureStarted = true
-                startOrder = count++
-            },
-            onDragEnd = {
-                gestureEnded = true
-                endOrder = count++
-            },
-            onDragCancel = {
-                gestureCanceled = true
-                cancelOrder = count++
-            }
-        ) { change, dragAmount ->
-            val positionChange = change.positionChange()
-            dragOrder = count++
-            if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
-                change.consume()
-                dragged = true
-                dragDistance += dragAmount.getDistance()
-            }
-        }
-    }
-
-    private val VerticalTouchSlopUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        var count = 0
-        detectVerticalDragGestures(
-            onDragStart = {
-                gestureStarted = true
-                startOrder = count++
-            },
-            onDragEnd = {
-                gestureEnded = true
-                endOrder = count++
-            },
-            onDragCancel = {
-                gestureCanceled = true
-                cancelOrder = count++
-            }
-        ) { change, dragAmount ->
-            dragOrder = count++
-            if (change.positionChange().y > 0f || !consumePositiveOnly) {
-                dragged = true
-                dragDistance += dragAmount
-            }
-        }
-    }
-
-    private val HorizontalTouchSlopUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        var count = 0
-        detectHorizontalDragGestures(
-            onDragStart = {
-                gestureStarted = true
-                startOrder = count++
-            },
-            onDragEnd = {
-                gestureEnded = true
-                endOrder = count++
-            },
-            onDragCancel = {
-                gestureCanceled = true
-                cancelOrder = count++
-            }
-        ) { change, dragAmount ->
-            dragOrder = count++
-            if (change.positionChange().x > 0f || !consumePositiveOnly) {
-                dragged = true
-                dragDistance += dragAmount
-            }
-        }
-    }
-
-    private val AwaitVerticalDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        awaitEachGesture {
-            val down = awaitFirstDown()
-            val slopChange = awaitVerticalTouchSlopOrCancellation(down.id) { change, overSlop ->
-                if (change.positionChange().y > 0f || !consumePositiveOnly) {
-                    dragged = true
-                    dragDistance = overSlop
-                    change.consume()
-                }
-            }
-            if (slopChange != null || sloppyDetector) {
-                gestureStarted = true
-                var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                do {
-                    val change = awaitVerticalDragOrCancellation(pointer)
-                    if (change == null) {
-                        gestureCanceled = true
-                    } else {
-                        dragDistance += change.positionChange().y
-                        change.consume()
-                        if (change.changedToUpIgnoreConsumed()) {
-                            gestureEnded = true
-                        }
-                        pointer = change.id
-                    }
-                } while (!gestureEnded && !gestureCanceled)
-            }
-        }
-    }
-
-    private val AwaitHorizontalDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        awaitEachGesture {
-            val down = awaitFirstDown()
-            val slopChange =
-                awaitHorizontalTouchSlopOrCancellation(down.id) { change, overSlop ->
-                    if (change.positionChange().x > 0f || !consumePositiveOnly) {
-                        dragged = true
-                        dragDistance = overSlop
-                        change.consume()
-                    }
-                }
-            if (slopChange != null || sloppyDetector) {
-                gestureStarted = true
-                var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                do {
-                    val change = awaitHorizontalDragOrCancellation(pointer)
-                    if (change == null) {
-                        gestureCanceled = true
-                    } else {
-                        dragDistance += change.positionChange().x
-                        change.consume()
-                        if (change.changedToUpIgnoreConsumed()) {
-                            gestureEnded = true
-                        }
-                        pointer = change.id
-                    }
-                } while (!gestureEnded && !gestureCanceled)
-            }
-        }
-    }
-
-    private val AwaitDragUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        awaitEachGesture {
-            val down = awaitFirstDown()
-            val slopChange = awaitTouchSlopOrCancellation(down.id) { change, overSlop ->
-                val positionChange = change.positionChange()
-                if (positionChange.x > 0f || positionChange.y > 0f || !consumePositiveOnly) {
-                    dragged = true
-                    dragDistance = overSlop.getDistance()
-                    change.consume()
-                }
-            }
-            if (slopChange != null || sloppyDetector) {
-                gestureStarted = true
-                var pointer = if (sloppyDetector) down.id else slopChange!!.id
-                do {
-                    val change = awaitDragOrCancellation(pointer)
-                    if (change == null) {
-                        gestureCanceled = true
-                    } else {
-                        dragDistance += change.positionChange().getDistance()
-                        change.consume()
-                        if (change.changedToUpIgnoreConsumed()) {
-                            gestureEnded = true
-                        }
-                        pointer = change.id
-                    }
-                } while (!gestureEnded && !gestureCanceled)
-            }
-        }
-    }
-
-    private val util = when (dragType) {
-        GestureType.VerticalDrag -> VerticalTouchSlopUtil
-        GestureType.HorizontalDrag -> HorizontalTouchSlopUtil
-        GestureType.AwaitVerticalDragOrCancel -> AwaitVerticalDragUtil
-        GestureType.AwaitHorizontalDragOrCancel -> AwaitHorizontalDragUtil
-        GestureType.AwaitDragOrCancel -> AwaitDragUtil
-        GestureType.DragWithVertical -> DragTouchSlopUtil
-        GestureType.DragWithHorizontal -> DragTouchSlopUtil
-    }
-
-    private val dragMotion = when (dragType) {
-        GestureType.VerticalDrag,
-        GestureType.AwaitVerticalDragOrCancel,
-        GestureType.DragWithVertical -> Offset(0f, 18f)
-        else -> Offset(18f, 0f)
-    }
-
-    private val crossDragMotion = when (dragType) {
-        GestureType.VerticalDrag,
-        GestureType.AwaitVerticalDragOrCancel,
-        GestureType.DragWithVertical -> Offset(18f, 0f)
-        else -> Offset(0f, 18f)
-    }
-
-    private val twoAxisDrag = when (dragType) {
-        GestureType.DragWithVertical,
-        GestureType.DragWithHorizontal,
-        GestureType.AwaitDragOrCancel -> true
-        else -> false
-    }
-
-    private val supportsSloppyGesture = when (dragType) {
-        GestureType.AwaitVerticalDragOrCancel,
-        GestureType.AwaitHorizontalDragOrCancel,
-        GestureType.AwaitDragOrCancel -> true
-        else -> false
-    }
-
-    @Before
-    fun setup() {
-        dragDistance = 0f
-        dragged = false
-        gestureEnded = false
-    }
-
-    /**
-     * A normal drag, just to ensure that the drag worked.
-     */
-    @Test
-    fun normalDrag() = util.executeInComposition {
-        val move = down().moveBy(dragMotion)
-        assertTrue(gestureStarted)
-        assertTrue(dragged)
-        assertEquals(0f, dragDistance)
-        val move2 = move.moveBy(dragMotion)
-        assertEquals(18f, dragDistance)
-        assertFalse(gestureEnded)
-        move2.up()
-        assertTrue(gestureEnded)
-    }
-
-    /**
-     * A drag in the opposite direction doesn't cause a drag event.
-     */
-    @Test
-    fun crossDrag() = util.executeInComposition {
-        if (!twoAxisDrag) {
-            down().moveBy(crossDragMotion).up()
-            assertFalse(gestureStarted)
-            assertFalse(dragged)
-            assertFalse(gestureEnded)
-
-            // now try a normal drag to ensure that it is still working.
-            down().moveBy(dragMotion).up()
-            assertTrue(gestureStarted)
-            assertTrue(dragged)
-            assertEquals(0f, dragDistance)
-            assertTrue(gestureEnded)
-        }
-    }
-
-    /**
-     * Use two fingers and lift the finger before the touch slop is reached.
-     */
-    @Test
-    fun twoFingerDrag_upBeforeSlop() = util.executeInComposition {
-        val finger1 = down()
-        val finger2 = down()
-
-        // second finger shouldn't cause a drag. It should follow finger1
-        val moveFinger2 = finger2.moveBy(dragMotion)
-
-        assertFalse(gestureStarted)
-        assertFalse(dragged)
-
-        // now it should move to finger 2
-        finger1.up()
-
-        moveFinger2.moveBy(dragMotion).moveBy(dragMotion).up()
-
-        assertTrue(dragged)
-        assertEquals(18f, dragDistance)
-        assertTrue(gestureEnded)
-    }
-
-    /**
-     * Use two fingers and lift the finger after the touch slop is reached.
-     */
-    @Test
-    fun twoFingerDrag_upAfterSlop() = util.executeInComposition {
-        val finger1 = down()
-        val finger2 = down()
-
-        finger1.moveBy(dragMotion).up()
-
-        assertTrue(gestureStarted)
-        assertTrue(dragged)
-        assertEquals(0f, dragDistance)
-        assertFalse(gestureEnded)
-
-        finger2.moveBy(dragMotion).up()
-
-        assertEquals(18f, dragDistance)
-        assertTrue(gestureEnded)
-    }
-
-    /**
-     * Cancel drag during touch slop
-     */
-    @Test
-    fun cancelDragDuringSlop() = util.executeInComposition {
-        down().moveBy(dragMotion) { consume() }.moveBy(dragMotion).up()
-        assertFalse(gestureStarted)
-        assertFalse(dragged)
-        assertFalse(gestureEnded)
-        assertFalse(gestureCanceled) // only canceled if the touch slop was crossed first
-    }
-
-    /**
-     * Cancel drag after touch slop
-     */
-    @Test
-    fun cancelDragAfterSlop() = util.executeInComposition {
-        down().moveBy(dragMotion).moveBy(dragMotion) { consume() }.up()
-        assertTrue(gestureStarted)
-        assertTrue(dragged)
-        assertFalse(gestureEnded)
-        assertTrue(gestureCanceled)
-        assertEquals(0f, dragDistance)
-    }
-
-    /**
-     * When this drag direction is more than the other drag direction, it should have priority
-     * in locking the orientation.
-     */
-    @Test
-    fun dragLockedWithPriority() = util.executeInComposition {
-        if (!twoAxisDrag) {
-            down().moveBy(
-                (dragMotion * 2f) + crossDragMotion,
-                final = {
-                    // This should have priority because it has moved more than the other direction.
-                    assertTrue(isConsumed)
-                }
-            )
-                .up()
-            assertTrue(gestureStarted)
-            assertTrue(dragged)
-            assertTrue(gestureEnded)
-            assertFalse(gestureCanceled)
-            assertEquals(18f, dragDistance)
-        }
-    }
-
-    /**
-     * When a drag is not consumed, it should lead to the touch slop being reset. This is
-     * important when you drag your finger to
-     */
-    @Test
-    fun dragBackAndForth() = util.executeInComposition {
-        if (supportsSloppyGesture) {
-            try {
-                consumePositiveOnly = true
-
-                val back = down().moveBy(-dragMotion)
-
-                assertFalse(gestureStarted)
-                assertFalse(dragged)
-                back.moveBy(dragMotion).up()
-
-                assertTrue(gestureStarted)
-                assertTrue(dragged)
-            } finally {
-                consumePositiveOnly = false
-            }
-        }
-    }
-
-    /**
-     * When gesture detectors use the wrong pointer for the drag, it should just not
-     * detect the touch.
-     */
-    @Test
-    fun pointerUpTooQuickly() = util.executeInComposition {
-        if (supportsSloppyGesture) {
-            try {
-                sloppyDetector = true
-
-                val finger1 = down()
-                val finger2 = down()
-                finger1.up()
-                finger2.moveBy(dragMotion).up()
-
-                // The sloppy detector doesn't know to look at finger2
-                assertTrue(gestureCanceled)
-            } finally {
-                sloppyDetector = false
-            }
-        }
-    }
-
-    @Test
-    fun dragGestureCallbackOrder_normalFinish() = util.executeInComposition {
-        if (!supportsSloppyGesture) {
-            val progress = down().moveBy(Offset(50f, 50f))
-            assertTrue(startOrder < dragOrder)
-            progress.up()
-            assertTrue(startOrder < dragOrder)
-            assertTrue(dragOrder < endOrder)
-            assertTrue(cancelOrder == -1)
-        }
-    }
-
-    @Test
-    fun dragGestureCallbackOrder_cancel() = util.executeInComposition {
-        if (!supportsSloppyGesture) {
-            down().moveBy(dragMotion).moveBy(dragMotion) { consume() }
-            assertTrue(startOrder < dragOrder)
-            assertTrue(dragOrder < cancelOrder)
-            assertTrue(endOrder == -1)
-        }
-    }
-
-    // An error in the pointer input stream should stop the gesture without error.
-    @Test
-    fun interruptedBeforeDrag() = util.executeInComposition {
-        down()
-        clearPointerStream()
-        // The next stream doesn't have the existing pointer, so we lose the dragging
-        down().up()
-        assertFalse(gestureStarted)
-    }
-
-    // An error in the pointer input stream should stop the gesture without error.
-    @Test
-    fun interruptedBeforeTouchSlop() = util.executeInComposition {
-        down().moveBy(dragMotion / 2f)
-        clearPointerStream()
-        // The next stream doesn't have the existing pointer, so we lose the dragging
-        down().up()
-        assertFalse(gestureStarted)
-    }
-
-    // An error in the pointer input stream should end in a drag cancellation.
-    @Test
-    fun interruptedAfterTouchSlop() = util.executeInComposition {
-        down().moveBy(dragMotion * 2f)
-        clearPointerStream()
-        // The next stream doesn't have the existing pointer, so we lose the dragging
-        down().up()
-        assertTrue(gestureCanceled)
-    }
-}
-
-@RunWith(JUnit4::class)
-class DragGestureOrderTest {
-    var startCount = -1
-    var stopCount = -1
-    var dragCount = -1
-    private val DragOrderUtil = SuspendingGestureTestUtil(width = 100, height = 100) {
-        var counter = 0
-        detectDragGestures(
-            onDragStart = { startCount = counter++ },
-            onDragEnd = { stopCount = counter++ }
-        ) { _, _ ->
-            dragCount = counter++
-        }
-    }
-
-    @Test
-    fun dragGestureCallbackOrder() = DragOrderUtil.executeInComposition {
-        val progress = down().moveBy(Offset(50f, 50f))
-        assertTrue(startCount < dragCount)
-        progress.up()
-        assertTrue(startCount < dragCount)
-        assertTrue(dragCount < stopCount)
-    }
-}
\ No newline at end of file
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/CommitTextCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/CommitTextCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/CommitTextCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/CommitTextCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextInCodePointsCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextInCodePointsCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextInCodePointsCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/DeleteSurroundingTextInCodePointsCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditProcessorTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditProcessorTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditProcessorTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditProcessorTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferDeleteRangeTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferDeleteRangeTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferDeleteRangeTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferDeleteRangeTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/EditingBufferTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/FinishComposingTextCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/FinishComposingTextCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/FinishComposingTextCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/FinishComposingTextCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/GapBufferTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/GapBufferTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/GapBufferTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/GapBufferTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingRegionCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingRegionCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingRegionCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingRegionCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingTextCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingTextCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingTextCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetComposingTextCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetSelectionCommandTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetSelectionCommandTest.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/SetSelectionCommandTest.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/SetSelectionCommandTest.kt
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/matchers/EditBufferSubject.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/matchers/EditBufferSubject.kt
similarity index 100%
rename from compose/foundation/foundation/integration-tests/foundation-demos/src/test/kotlin/androidx/compose/foundation/text2/input/matchers/EditBufferSubject.kt
rename to compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text2/input/matchers/EditBufferSubject.kt
diff --git a/compose/material/material/build.gradle b/compose/material/material/build.gradle
index f800f7b..cc48985 100644
--- a/compose/material/material/build.gradle
+++ b/compose/material/material/build.gradle
@@ -47,9 +47,9 @@
         implementation("androidx.compose.ui:ui-util:1.2.1")
 
         // TODO: remove next 3 dependencies when b/202810604 is fixed
-        implementation("androidx.savedstate:savedstate:1.2.0")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+        implementation("androidx.savedstate:savedstate:1.2.1")
+        implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
 
         testImplementation(libs.testRules)
         testImplementation(libs.testRunner)
@@ -106,9 +106,9 @@
                 api("androidx.annotation:annotation:1.1.0")
 
                 // TODO: remove next 3 dependencies when b/202810604 is fixed
-                implementation("androidx.savedstate:savedstate:1.2.0")
-                implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+                implementation("androidx.savedstate:savedstate:1.2.1")
+                implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
             }
 
             desktopMain.dependencies {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
index c5cbba9..86c4206 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/ModalBottomSheetTest.kt
@@ -1207,4 +1207,40 @@
         rule.waitForIdle()
         assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.HalfExpanded)
     }
+
+    // TODO: Migrate to manual clock mode once b/269613287 is fixed
+    @Test
+    fun modalBottomSheet_anchorChangeHandler_missingAnchor_immediatelySnapsForInitialization() {
+        val stateRestorationTester = StateRestorationTester(rule)
+
+        // Not backed by state as we don't want changes to cause recompositions
+        var sheetState = ModalBottomSheetState(ModalBottomSheetValue.HalfExpanded)
+        var tallSheetContent = true
+
+        stateRestorationTester.setContent {
+            ModalBottomSheetLayout(
+                sheetState = sheetState,
+                sheetContent = {
+                    Box(Modifier.fillMaxHeight(if (tallSheetContent) 1f else 0.4f))
+                },
+                content = { Box(Modifier.fillMaxSize()) }
+            )
+        }
+
+        assertThat(sheetState.hasHalfExpandedState).isTrue()
+        assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.HalfExpanded)
+
+        tallSheetContent = false
+        // Recreate the sheet state so it doesn't have anchors or an offset yet
+        sheetState = ModalBottomSheetState(ModalBottomSheetValue.HalfExpanded)
+
+        assertThat(sheetState.swipeableState.anchors).isEmpty()
+        assertThat(sheetState.swipeableState.offset).isNull()
+
+        stateRestorationTester.emulateSavedInstanceStateRestore()
+        rule.waitForIdle()
+
+        assertThat(sheetState.currentValue).isEqualTo(ModalBottomSheetValue.Expanded)
+        assertThat(sheetState.hasHalfExpandedState).isFalse()
+    }
 }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2StateTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2StateTest.kt
index 0e04ef0..5fcdcc9 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2StateTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/swipeable/SwipeableV2StateTest.kt
@@ -32,6 +32,7 @@
 import androidx.compose.material.swipeable.TestState.B
 import androidx.compose.material.swipeable.TestState.C
 import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MonotonicFrameClock
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -52,6 +53,8 @@
 import com.google.common.truth.Truth.assertWithMessage
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
 import org.junit.Ignore
@@ -441,4 +444,38 @@
         shouldInvokeChangeHandler = state.updateAnchors(mapOf(A to 100f, B to 500f, C to 700f))
         assertThat(shouldInvokeChangeHandler).isTrue()
     }
+
+    @Test
+    fun swipeable_updateAnchors_ongoingOffsetMutation_shouldNotUpdate() = runBlocking {
+        val clock = HandPumpTestFrameClock()
+        val animationScope = CoroutineScope(clock)
+        val animationDuration = 2000
+        val state = SwipeableV2State(initialValue = A, animationSpec = tween(animationDuration))
+        val anchors = mapOf(A to 0f, B to 200f, C to 300f)
+
+        state.updateAnchors(anchors)
+        animationScope.launch(start = CoroutineStart.UNDISPATCHED) {
+            state.animateTo(B)
+        }
+        clock.advanceByFrame()
+
+        assertThat(state.isAnimationRunning).isTrue()
+
+        val offsetBeforeAnchorUpdate = state.offset
+        val shouldInvokeChangeHandler = state.updateAnchors(mapOf(A to 100f, B to 500f, C to 700f))
+        assertThat(offsetBeforeAnchorUpdate).isEqualTo(state.offset)
+        assertThat(shouldInvokeChangeHandler).isTrue()
+    }
+
+    private class HandPumpTestFrameClock : MonotonicFrameClock {
+        private val frameCh = Channel<Long>(1)
+
+        suspend fun advanceByFrame() {
+            frameCh.send(16_000_000L)
+        }
+
+        override suspend fun <R> withFrameNanos(onFrame: (frameTimeNanos: Long) -> R): R {
+            return onFrame(frameCh.receive())
+        }
+    }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
index ac4def2..c40c277 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
@@ -25,6 +25,7 @@
 import androidx.compose.material.GOLDEN_MATERIAL
 import androidx.compose.material.Icon
 import androidx.compose.material.LocalContentColor
+import androidx.compose.material.LocalTextStyle
 import androidx.compose.material.OutlinedTextField
 import androidx.compose.material.Text
 import androidx.compose.material.icons.Icons
@@ -44,7 +45,6 @@
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.text.TextRange
-import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.style.TextAlign
 import androidx.compose.ui.unit.LayoutDirection
@@ -476,7 +476,7 @@
                 value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.width(300.dp).testTag(TextFieldTag),
-                textStyle = TextStyle(textAlign = TextAlign.Center),
+                textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.Center),
                 singleLine = true
             )
         }
@@ -492,7 +492,7 @@
                 value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.fillMaxWidth().testTag(TextFieldTag),
-                textStyle = TextStyle(textAlign = TextAlign.End),
+                textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.End),
                 singleLine = true
             )
         }
diff --git a/compose/material/material/src/androidMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.android.kt b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.android.kt
new file mode 100644
index 0000000..2e65f0c
--- /dev/null
+++ b/compose/material/material/src/androidMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.android.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.ui.text.PlatformTextStyle
+
+private const val DefaultIncludeFontPadding = true
+
+@Suppress("DEPRECATION")
+private val DefaultPlatformTextStyle = PlatformTextStyle(
+    includeFontPadding = DefaultIncludeFontPadding
+)
+internal actual fun defaultPlatformTextStyle(): PlatformTextStyle? = DefaultPlatformTextStyle
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt
new file mode 100644
index 0000000..326c77e
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/InternalMutatorMutex.kt
@@ -0,0 +1,170 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.runtime.Stable
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/*** This is an internal copy of androidx.compose.foundation.MutatorMutex with an additional
+ * tryMutate method. Do not modify, except for tryMutate. ***/
+
+expect class InternalAtomicReference<V>(value: V) {
+    fun get(): V
+    fun set(value: V)
+    fun getAndSet(value: V): V
+    fun compareAndSet(expect: V, newValue: V): Boolean
+}
+
+/**
+ * Mutual exclusion for UI state mutation over time.
+ *
+ * [mutate] permits interruptible state mutation over time using a standard [MutatePriority].
+ * A [InternalMutatorMutex] enforces that only a single writer can be active at a time for a particular
+ * state resource. Instead of queueing callers that would acquire the lock like a traditional
+ * [Mutex], new attempts to [mutate] the guarded state will either cancel the current mutator or
+ * if the current mutator has a higher priority, the new caller will throw [CancellationException].
+ *
+ * [InternalMutatorMutex] should be used for implementing hoisted state objects that many mutators may
+ * want to manipulate over time such that those mutators can coordinate with one another. The
+ * [InternalMutatorMutex] instance should be hidden as an implementation detail. For example:
+ *
+ */
+@Stable
+internal class InternalMutatorMutex {
+    private class Mutator(val priority: MutatePriority, val job: Job) {
+        fun canInterrupt(other: Mutator) = priority >= other.priority
+
+        fun cancel() = job.cancel()
+    }
+
+    private val currentMutator = InternalAtomicReference<Mutator?>(null)
+    private val mutex = Mutex()
+
+    private fun tryMutateOrCancel(mutator: Mutator) {
+        while (true) {
+            val oldMutator = currentMutator.get()
+            if (oldMutator == null || mutator.canInterrupt(oldMutator)) {
+                if (currentMutator.compareAndSet(oldMutator, mutator)) {
+                    oldMutator?.cancel()
+                    break
+                }
+            } else throw CancellationException("Current mutation had a higher priority")
+        }
+    }
+
+    /**
+     * Enforce that only a single caller may be active at a time.
+     *
+     * If [mutate] is called while another call to [mutate] or [mutateWith] is in progress, their
+     * [priority] values are compared. If the new caller has a [priority] equal to or higher than
+     * the call in progress, the call in progress will be cancelled, throwing
+     * [CancellationException] and the new caller's [block] will be invoked. If the call in
+     * progress had a higher [priority] than the new caller, the new caller will throw
+     * [CancellationException] without invoking [block].
+     *
+     * @param priority the priority of this mutation; [MutatePriority.Default] by default.
+     * Higher priority mutations will interrupt lower priority mutations.
+     * @param block mutation code to run mutually exclusive with any other call to [mutate],
+     * [mutateWith] or [tryMutate].
+     */
+    suspend fun <R> mutate(
+        priority: MutatePriority = MutatePriority.Default,
+        block: suspend () -> R
+    ) = coroutineScope {
+        val mutator = Mutator(priority, coroutineContext[Job]!!)
+
+        tryMutateOrCancel(mutator)
+
+        mutex.withLock {
+            try {
+                block()
+            } finally {
+                currentMutator.compareAndSet(mutator, null)
+            }
+        }
+    }
+
+    /**
+     * Enforce that only a single caller may be active at a time.
+     *
+     * If [mutateWith] is called while another call to [mutate] or [mutateWith] is in progress,
+     * their [priority] values are compared. If the new caller has a [priority] equal to or
+     * higher than the call in progress, the call in progress will be cancelled, throwing
+     * [CancellationException] and the new caller's [block] will be invoked. If the call in
+     * progress had a higher [priority] than the new caller, the new caller will throw
+     * [CancellationException] without invoking [block].
+     *
+     * This variant of [mutate] calls its [block] with a [receiver], removing the need to create
+     * an additional capturing lambda to invoke it with a receiver object. This can be used to
+     * expose a mutable scope to the provided [block] while leaving the rest of the state object
+     * read-only. For example:
+     *
+     * @param receiver the receiver `this` that [block] will be called with
+     * @param priority the priority of this mutation; [MutatePriority.Default] by default.
+     * Higher priority mutations will interrupt lower priority mutations.
+     * @param block mutation code to run mutually exclusive with any other call to [mutate],
+     * [mutateWith] or [tryMutate].
+     */
+    suspend fun <T, R> mutateWith(
+        receiver: T,
+        priority: MutatePriority = MutatePriority.Default,
+        block: suspend T.() -> R
+    ) = coroutineScope {
+        val mutator = Mutator(priority, coroutineContext[Job]!!)
+
+        tryMutateOrCancel(mutator)
+
+        mutex.withLock {
+            try {
+                receiver.block()
+            } finally {
+                currentMutator.compareAndSet(mutator, null)
+            }
+        }
+    }
+
+    /**
+     * Attempt to mutate synchronously if there is no other active caller.
+     * If there is no other active caller, the [block] will be executed in a lock. If there is
+     * another active caller, this method will return false, indicating that the active caller
+     * needs to be cancelled through a [mutate] or [mutateWith] call with an equal or higher
+     * mutation priority.
+     *
+     * Calls to [mutate] and [mutateWith] will suspend until execution of the [block] has finished.
+     *
+     * @param block mutation code to run mutually exclusive with any other call to [mutate],
+     * [mutateWith] or [tryMutate].
+     * @return true if the [block] was executed, false if there was another active caller and the
+     * [block] was not executed.
+     */
+    fun tryMutate(block: () -> Unit): Boolean {
+        val didLock = mutex.tryLock()
+        if (didLock) {
+            try {
+                block()
+            } finally {
+                mutex.unlock()
+            }
+        }
+        return didLock
+    }
+}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
index 5ea9086..5bd062c 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/ModalBottomSheet.kt
@@ -245,6 +245,10 @@
 
     internal suspend fun snapTo(target: ModalBottomSheetValue) = swipeableState.snapTo(target)
 
+    internal fun trySnapTo(target: ModalBottomSheetValue): Boolean {
+        return swipeableState.trySnapTo(target)
+    }
+
     internal fun requireOffset() = swipeableState.requireOffset()
 
     internal val lastVelocity: Float get() = swipeableState.lastVelocity
@@ -448,7 +452,10 @@
             animateTo = { target, velocity ->
                 scope.launch { sheetState.animateTo(target, velocity = velocity) }
             },
-            snapTo = { target -> scope.launch { sheetState.snapTo(target) } }
+            snapTo = { target ->
+                val didSnapSynchronously = sheetState.trySnapTo(target)
+                if (!didSnapSynchronously) scope.launch { sheetState.snapTo(target) }
+            }
         )
     }
     BoxWithConstraints(modifier) {
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
index ec40501..d5d27ae 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/SwipeableV2.kt
@@ -19,6 +19,8 @@
 import androidx.compose.animation.core.AnimationSpec
 import androidx.compose.animation.core.SpringSpec
 import androidx.compose.animation.core.animate
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.gestures.DragScope
 import androidx.compose.foundation.gestures.DraggableState
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.draggable
@@ -48,6 +50,7 @@
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
 import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.launch
 
 /**
@@ -77,7 +80,7 @@
     reverseDirection: Boolean = false,
     interactionSource: MutableInteractionSource? = null
 ) = draggable(
-    state = state.draggableState,
+    state = state.swipeDraggableState,
     orientation = orientation,
     enabled = enabled,
     interactionSource = interactionSource,
@@ -120,7 +123,11 @@
             val previousTarget = state.targetValue
             val stateRequiresCleanup = state.updateAnchors(newAnchors)
             if (stateRequiresCleanup) {
-                anchorChangeHandler?.onAnchorsChanged(previousTarget, previousAnchors, newAnchors)
+                anchorChangeHandler?.onAnchorsChanged(
+                    previousTarget,
+                    previousAnchors,
+                    newAnchors
+                )
             }
         }
     },
@@ -163,6 +170,27 @@
     internal val velocityThreshold: Dp = SwipeableV2Defaults.VelocityThreshold,
 ) {
 
+    private val swipeMutex = InternalMutatorMutex()
+
+    internal val swipeDraggableState = object : DraggableState {
+        private val dragScope = object : DragScope {
+            override fun dragBy(pixels: Float) {
+                this@SwipeableV2State.dispatchRawDelta(pixels)
+            }
+        }
+
+        override suspend fun drag(
+            dragPriority: MutatePriority,
+            block: suspend DragScope.() -> Unit
+        ) {
+            swipe(dragPriority) { dragScope.block() }
+        }
+
+        override fun dispatchRawDelta(delta: Float) {
+            this@SwipeableV2State.dispatchRawDelta(delta)
+        }
+    }
+
     /**
      * The current value of the [SwipeableV2State].
      */
@@ -253,9 +281,6 @@
     val maxOffset by derivedStateOf { anchors.maxOrNull() ?: Float.POSITIVE_INFINITY }
 
     private var animationTarget: T? by mutableStateOf(null)
-    internal val draggableState = DraggableState {
-        offset = ((offset ?: 0f) + it).coerceIn(minOffset, maxOffset)
-    }
 
     internal var anchors by mutableStateOf(emptyMap<T, Float>())
 
@@ -274,9 +299,10 @@
         val previousAnchorsEmpty = anchors.isEmpty()
         anchors = newAnchors
         val initialValueHasAnchor = if (previousAnchorsEmpty) {
-            val initialValueAnchor = anchors[currentValue]
+            val initialValue = currentValue
+            val initialValueAnchor = anchors[initialValue]
             val initialValueHasAnchor = initialValueAnchor != null
-            if (initialValueHasAnchor) offset = initialValueAnchor
+            if (initialValueHasAnchor) trySnapTo(initialValue)
             initialValueHasAnchor
         } else true
         return !initialValueHasAnchor || !previousAnchorsEmpty
@@ -298,20 +324,7 @@
      * @param targetValue The target value of the animation
      */
     suspend fun snapTo(targetValue: T) {
-        val targetOffset = anchors[targetValue]
-        if (targetOffset != null) {
-            try {
-                draggableState.drag {
-                    animationTarget = targetValue
-                    dragBy(targetOffset - requireOffset())
-                }
-                this.currentValue = targetValue
-            } finally {
-                animationTarget = null
-            }
-        } else {
-            currentValue = targetValue
-        }
+        swipe { snap(targetValue) }
     }
 
     /**
@@ -332,7 +345,7 @@
         val targetOffset = anchors[targetValue]
         if (targetOffset != null) {
             try {
-                draggableState.drag {
+                swipe {
                     animationTarget = targetValue
                     var prev = offset ?: 0f
                     animate(prev, targetOffset, velocity, animationSpec) { value, velocity ->
@@ -379,17 +392,17 @@
     }
 
     /**
-     * Swipe by the [delta], coerce it in the bounds and dispatch it to the [draggableState].
+     * Swipe by the [delta], coerce it in the bounds and dispatch it to the [SwipeableV2State].
      *
-     * @return The delta the [draggableState] will consume
+     * @return The delta the consumed by the [SwipeableV2State]
      */
     fun dispatchRawDelta(delta: Float): Float {
         val currentDragPosition = offset ?: 0f
         val potentiallyConsumed = currentDragPosition + delta
         val clamped = potentiallyConsumed.coerceIn(minOffset, maxOffset)
         val deltaToConsume = clamped - currentDragPosition
-        if (abs(deltaToConsume) > 0) {
-            draggableState.dispatchRawDelta(deltaToConsume)
+        if (abs(deltaToConsume) >= 0) {
+            offset = ((offset ?: 0f) + deltaToConsume).coerceIn(minOffset, maxOffset)
         }
         return deltaToConsume
     }
@@ -441,6 +454,31 @@
             "this=$this SwipeableState?"
     }
 
+    private suspend fun swipe(
+        swipePriority: MutatePriority = MutatePriority.Default,
+        action: suspend () -> Unit
+    ): Unit = coroutineScope { swipeMutex.mutate(swipePriority, action) }
+
+    /**
+     * Attempt to snap synchronously. Snapping can happen synchronously when there is no other swipe
+     * transaction like a drag or an animation is progress. If there is another interaction in
+     * progress, the suspending [snapTo] overload needs to be used.
+     *
+     * @return true if the synchronous snap was successful, or false if we couldn't snap synchronous
+     */
+    internal fun trySnapTo(targetValue: T): Boolean = swipeMutex.tryMutate { snap(targetValue) }
+
+    private fun snap(targetValue: T) {
+        val targetOffset = anchors[targetValue]
+        if (targetOffset != null) {
+            dispatchRawDelta(targetOffset - (offset ?: 0f))
+            currentValue = targetValue
+            animationTarget = null
+        } else {
+            currentValue = targetValue
+        }
+    }
+
     companion object {
         /**
          * The default [Saver] implementation for [SwipeableV2State].
@@ -649,6 +687,3 @@
 
 private fun <T> Map<T, Float>.minOrNull() = minOfOrNull { (_, offset) -> offset }
 private fun <T> Map<T, Float>.maxOrNull() = maxOfOrNull { (_, offset) -> offset }
-private fun <T> Map<T, Float>.requireAnchor(value: T) = requireNotNull(this[value]) {
-    "Required anchor $value was not found in anchors. Current anchors: ${this.toMap()}"
-}
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
index aec0946..9398dcd1 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Text.kt
@@ -347,7 +347,7 @@
  *
  * @see ProvideTextStyle
  */
-val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { DefaultTextStyle }
 
 // TODO: b/156598010 remove this and replace with fold definition on the backing CompositionLocal
 /**
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Typography.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Typography.kt
index 63e208c..492072d 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Typography.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Typography.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.runtime.Immutable
 import androidx.compose.runtime.staticCompositionLocalOf
+import androidx.compose.ui.text.PlatformTextStyle
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
@@ -117,67 +118,67 @@
      */
     constructor(
         defaultFontFamily: FontFamily = FontFamily.Default,
-        h1: TextStyle = TextStyle(
+        h1: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Light,
             fontSize = 96.sp,
             letterSpacing = (-1.5).sp
         ),
-        h2: TextStyle = TextStyle(
+        h2: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Light,
             fontSize = 60.sp,
             letterSpacing = (-0.5).sp
         ),
-        h3: TextStyle = TextStyle(
+        h3: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 48.sp,
             letterSpacing = 0.sp
         ),
-        h4: TextStyle = TextStyle(
+        h4: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 34.sp,
             letterSpacing = 0.25.sp
         ),
-        h5: TextStyle = TextStyle(
+        h5: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 24.sp,
             letterSpacing = 0.sp
         ),
-        h6: TextStyle = TextStyle(
+        h6: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Medium,
             fontSize = 20.sp,
             letterSpacing = 0.15.sp
         ),
-        subtitle1: TextStyle = TextStyle(
+        subtitle1: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 16.sp,
             letterSpacing = 0.15.sp
         ),
-        subtitle2: TextStyle = TextStyle(
+        subtitle2: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Medium,
             fontSize = 14.sp,
             letterSpacing = 0.1.sp
         ),
-        body1: TextStyle = TextStyle(
+        body1: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 16.sp,
             letterSpacing = 0.5.sp
         ),
-        body2: TextStyle = TextStyle(
+        body2: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 14.sp,
             letterSpacing = 0.25.sp
         ),
-        button: TextStyle = TextStyle(
+        button: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Medium,
             fontSize = 14.sp,
             letterSpacing = 1.25.sp
         ),
-        caption: TextStyle = TextStyle(
+        caption: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 12.sp,
             letterSpacing = 0.4.sp
         ),
-        overline: TextStyle = TextStyle(
+        overline: TextStyle = DefaultTextStyle.copy(
             fontWeight = FontWeight.Normal,
             fontSize = 10.sp,
             letterSpacing = 1.5.sp
@@ -284,6 +285,15 @@
     return if (fontFamily != null) this else copy(fontFamily = default)
 }
 
+internal val DefaultTextStyle = TextStyle.Default.copy(
+    platformStyle = defaultPlatformTextStyle()
+)
+
+/**
+ * Returns Default [PlatformTextStyle].
+ */
+internal expect fun defaultPlatformTextStyle(): PlatformTextStyle?
+
 /**
  * This CompositionLocal holds on to the current definition of typography for this application as
  * described by the Material spec. You can read the values in it when creating custom components
diff --git a/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt b/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt
new file mode 100644
index 0000000..d8b6ef6
--- /dev/null
+++ b/compose/material/material/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.ui.text.PlatformTextStyle
+
+internal actual fun defaultPlatformTextStyle(): PlatformTextStyle? = null
\ No newline at end of file
diff --git a/compose/material/material/src/jvmMain/kotlin/androidx/compose/material/ActualJvm.kt b/compose/material/material/src/jvmMain/kotlin/androidx/compose/material/ActualJvm.kt
new file mode 100644
index 0000000..307fd82
--- /dev/null
+++ b/compose/material/material/src/jvmMain/kotlin/androidx/compose/material/ActualJvm.kt
@@ -0,0 +1,22 @@
+// ktlint-disable filename
+
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+internal actual typealias InternalAtomicReference<V> =
+    java.util.concurrent.atomic.AtomicReference<V>
\ No newline at end of file
diff --git a/compose/material/material/src/test/kotlin/androidx/compose/material/InternalMutatorMutexTest.kt b/compose/material/material/src/test/kotlin/androidx/compose/material/InternalMutatorMutexTest.kt
new file mode 100644
index 0000000..b603846
--- /dev/null
+++ b/compose/material/material/src/test/kotlin/androidx/compose/material/InternalMutatorMutexTest.kt
@@ -0,0 +1,175 @@
+/*
+ * Copyright 2020 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material
+
+import androidx.compose.foundation.MutatePriority
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.cancelAndJoin
+import kotlinx.coroutines.coroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.suspendCancellableCoroutine
+import org.junit.Assert
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertSame
+import org.junit.Assert.assertTrue
+import org.junit.Assert.fail
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@Suppress("RemoveExplicitTypeArguments")
+@RunWith(JUnit4::class)
+internal class InternalMutatorMutexTest {
+    interface MutateCaller {
+        suspend fun <R> mutate(
+            priority: MutatePriority = MutatePriority.Default,
+            block: suspend () -> R
+        ): R
+    }
+
+    class MutateWithoutReceiverCaller(private val mutex: InternalMutatorMutex) : MutateCaller {
+        override suspend fun <R> mutate(
+            priority: MutatePriority,
+            block: suspend () -> R
+        ): R = mutex.mutate(priority, block)
+    }
+
+    class MutateWithReceiverCaller(private val mutex: InternalMutatorMutex) : MutateCaller {
+        override suspend fun <R> mutate(
+            priority: MutatePriority,
+            block: suspend () -> R
+        ): R {
+            val receiver = Any()
+            return mutex.mutateWith(receiver, priority) {
+                assertSame("mutateWith receiver", receiver, this)
+                block()
+            }
+        }
+    }
+
+    @Test
+    fun newMutatorCancelsOld() = runBlocking<Unit> {
+        val mutex = InternalMutatorMutex()
+        runNewMutatorCancelsOld(MutateWithoutReceiverCaller(mutex))
+        runNewMutatorCancelsOld(MutateWithReceiverCaller(mutex))
+    }
+
+    private suspend fun runNewMutatorCancelsOld(mutex: MutateCaller) = coroutineScope<Unit> {
+        val firstMutatorJob = launch(start = CoroutineStart.UNDISPATCHED) {
+            mutex.mutate {
+                // Suspend forever
+                suspendCancellableCoroutine<Unit> { }
+            }
+            fail("mutator should have thrown CancellationException")
+        }
+
+        // Cancel firstMutatorJob
+        mutex.mutate { }
+        assertTrue("first mutator was cancelled", firstMutatorJob.isCancelled)
+    }
+
+    @Test
+    fun mutatorsCancelByPriority() = runBlocking<Unit> {
+        val mutex = InternalMutatorMutex()
+        runMutatorsCancelByPriority(MutateWithoutReceiverCaller(mutex))
+        runMutatorsCancelByPriority(MutateWithReceiverCaller(mutex))
+    }
+
+    @Test
+    fun tryMutateBlockingSuspendsSubsequentMutate() = runBlocking<Unit> {
+        val mutex = InternalMutatorMutex()
+        val tryMutateJob = launch(start = CoroutineStart.LAZY) {
+            mutex.tryMutate {
+                while (true) { /* Block forever */ }
+            }
+        }
+        val mutateJob = launch(start = CoroutineStart.LAZY) {
+            mutex.mutate {
+                if (tryMutateJob.isActive) fail("Attempted to mutate before tryMutate finished")
+            }
+        }
+        tryMutateJob.start()
+        mutateJob.start()
+
+        tryMutateJob.cancelAndJoin()
+        mutateJob.cancelAndJoin()
+    }
+
+    @Test
+    fun tryMutateDoesNotOverrideActiveCaller() = runBlocking<Unit> {
+        val mutex = InternalMutatorMutex()
+        val mutateJob = launch(start = CoroutineStart.UNDISPATCHED) {
+            mutex.mutate {
+                suspendCancellableCoroutine { } // Suspend forever
+            }
+        }
+        val tryMutateSuccessful = mutex.tryMutate { }
+        Assert.assertFalse(
+            "tryMutate should not run if there is an ongoing mutation",
+            tryMutateSuccessful
+        )
+        mutateJob.cancelAndJoin()
+    }
+
+    @Test
+    fun tryMutateBlockingTryMutateLocks() = runBlocking<Unit> {
+        val mutex = InternalMutatorMutex()
+        mutex.tryMutate {
+            val tryMutateSuccessful = mutex.tryMutate { }
+            Assert.assertFalse(
+                "tryMutate should not run if there is an ongoing mutation",
+                tryMutateSuccessful
+            )
+        }
+    }
+
+    private suspend fun runMutatorsCancelByPriority(mutex: MutateCaller) = coroutineScope<Unit> {
+        for (firstPriority in MutatePriority.values()) {
+            for (secondPriority in MutatePriority.values()) {
+                val firstMutatorJob = launch(start = CoroutineStart.UNDISPATCHED) {
+                    mutex.mutate(firstPriority) {
+                        // Suspend forever
+                        suspendCancellableCoroutine<Unit> { }
+                    }
+                    fail("mutator should have thrown CancellationException")
+                }
+
+                // Attempt mutation and (maybe) cause cancellation
+                try {
+                    mutex.mutate(secondPriority) { }
+                } catch (ce: CancellationException) {
+                    assertTrue(
+                        "attempted second mutation was cancelled with lower priority",
+                        secondPriority < firstPriority
+                    )
+                }
+                assertEquals(
+                    "first mutator of priority $firstPriority cancelled by second " +
+                        "mutator of priority $secondPriority",
+                    secondPriority >= firstPriority,
+                    firstMutatorJob.isCancelled
+                )
+
+                // Cleanup regardless of results
+                firstMutatorJob.cancel()
+                firstMutatorJob.join()
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index e6d7e923..7e2de6f 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -578,22 +578,6 @@
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
   }
 
-  public final class SearchBarDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDockedShape();
-    method public float getElevation();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFullScreenShape();
-    method public float getInputFieldHeight();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getInputFieldShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property public final float Elevation;
-    property public final float InputFieldHeight;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape dockedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape fullScreenShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape inputFieldShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.SearchBarDefaults INSTANCE;
-  }
-
   public final class ShapeDefaults {
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index ab72961..65b968a 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -96,7 +96,7 @@
   }
 
   public final class BottomSheetScaffoldKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.BottomSheetScaffoldState scaffoldState, optional float sheetPeekHeight, optional androidx.compose.ui.graphics.Shape sheetShape, optional long sheetContainerColor, optional long sheetContentColor, optional float sheetTonalElevation, optional kotlin.jvm.functions.Function0<kotlin.Unit>? sheetDragHandle, optional boolean sheetSwipeEnabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SnackbarHostState,kotlin.Unit> snackbarHost, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void BottomSheetScaffold(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> sheetContent, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.BottomSheetScaffoldState scaffoldState, optional float sheetPeekHeight, optional androidx.compose.ui.graphics.Shape sheetShape, optional long sheetContainerColor, optional long sheetContentColor, optional float sheetTonalElevation, optional float sheetShadowElevation, optional kotlin.jvm.functions.Function0<kotlin.Unit>? sheetDragHandle, optional boolean sheetSwipeEnabled, optional kotlin.jvm.functions.Function0<kotlin.Unit>? topBar, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SnackbarHostState,kotlin.Unit> snackbarHost, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.BottomSheetScaffoldState rememberBottomSheetScaffoldState(optional androidx.compose.material3.SheetState bottomSheetState, optional androidx.compose.material3.SnackbarHostState snackbarHostState);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberStandardBottomSheetState(optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
   }
@@ -331,7 +331,7 @@
   }
 
   public final class DatePickerKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DatePicker(androidx.compose.material3.DatePickerState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DatePickerFormatter dateFormatter, optional kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> dateValidator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit> headline, optional boolean showModeToggle, optional androidx.compose.material3.DatePickerColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DatePicker(androidx.compose.material3.DatePickerState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DatePickerFormatter dateFormatter, optional kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> dateValidator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? headline, optional boolean showModeToggle, optional androidx.compose.material3.DatePickerColors colors);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DatePickerState rememberDatePickerState(optional Long? initialSelectedDateMillis, optional Long? initialDisplayedMonthMillis, optional kotlin.ranges.IntRange yearRange, optional int initialDisplayMode);
   }
 
@@ -351,13 +351,13 @@
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class DateRangePickerDefaults {
-    method @androidx.compose.runtime.Composable public void DateRangePickerHeadline(androidx.compose.material3.DateRangePickerState state, androidx.compose.material3.DatePickerFormatter dateFormatter, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
-    method @androidx.compose.runtime.Composable public void DateRangePickerTitle(androidx.compose.material3.DateRangePickerState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.layout.PaddingValues contentPadding);
+    method @androidx.compose.runtime.Composable public void DateRangePickerHeadline(androidx.compose.material3.DateRangePickerState state, androidx.compose.material3.DatePickerFormatter dateFormatter, optional androidx.compose.ui.Modifier modifier);
+    method @androidx.compose.runtime.Composable public void DateRangePickerTitle(androidx.compose.material3.DateRangePickerState state, optional androidx.compose.ui.Modifier modifier);
     field public static final androidx.compose.material3.DateRangePickerDefaults INSTANCE;
   }
 
   public final class DateRangePickerKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DateRangePicker(androidx.compose.material3.DateRangePickerState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DatePickerFormatter dateFormatter, optional kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> dateValidator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit> headline, optional boolean showModeToggle, optional androidx.compose.material3.DatePickerColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DateRangePicker(androidx.compose.material3.DateRangePickerState state, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.DatePickerFormatter dateFormatter, optional kotlin.jvm.functions.Function1<? super java.lang.Long,java.lang.Boolean> dateValidator, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? headline, optional boolean showModeToggle, optional androidx.compose.material3.DatePickerColors colors);
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.DateRangePickerState rememberDateRangePickerState(optional Long? initialSelectedStartDateMillis, optional Long? initialSelectedEndDateMillis, optional Long? initialDisplayedMonthMillis, optional kotlin.ranges.IntRange yearRange, optional int initialDisplayMode);
   }
 
@@ -858,16 +858,16 @@
     property public final androidx.compose.material3.TextFieldColors inputFieldColors;
   }
 
-  public final class SearchBarDefaults {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.SearchBarColors colors(optional long containerColor, optional long dividerColor, optional androidx.compose.material3.TextFieldColors inputFieldColors);
+  @androidx.compose.material3.ExperimentalMaterial3Api public final class SearchBarDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.SearchBarColors colors(optional long containerColor, optional long dividerColor, optional androidx.compose.material3.TextFieldColors inputFieldColors);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDockedShape();
     method public float getElevation();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFullScreenShape();
     method public float getInputFieldHeight();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getInputFieldShape();
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors inputFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long cursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor);
-    method @Deprecated @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors! inputFieldColors(optional long textColor, optional long disabledTextColor, optional long cursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors inputFieldColors(optional long focusedTextColor, optional long unfocusedTextColor, optional long disabledTextColor, optional long cursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long focusedPlaceholderColor, optional long unfocusedPlaceholderColor, optional long disabledPlaceholderColor);
+    method @Deprecated @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors! inputFieldColors(optional long textColor, optional long disabledTextColor, optional long cursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long placeholderColor, optional long disabledPlaceholderColor);
     property public final float Elevation;
     property public final float InputFieldHeight;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape dockedShape;
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index e6d7e923..7e2de6f 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -578,22 +578,6 @@
     method @androidx.compose.runtime.Composable public static void Scaffold(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> topBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> bottomBar, optional kotlin.jvm.functions.Function0<kotlin.Unit> snackbarHost, optional kotlin.jvm.functions.Function0<kotlin.Unit> floatingActionButton, optional int floatingActionButtonPosition, optional long containerColor, optional long contentColor, optional androidx.compose.foundation.layout.WindowInsets contentWindowInsets, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.PaddingValues,kotlin.Unit> content);
   }
 
-  public final class SearchBarDefaults {
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getDockedShape();
-    method public float getElevation();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getFullScreenShape();
-    method public float getInputFieldHeight();
-    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getInputFieldShape();
-    method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getWindowInsets();
-    property public final float Elevation;
-    property public final float InputFieldHeight;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape dockedShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape fullScreenShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape inputFieldShape;
-    property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets windowInsets;
-    field public static final androidx.compose.material3.SearchBarDefaults INSTANCE;
-  }
-
   public final class ShapeDefaults {
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraLarge();
     method public androidx.compose.foundation.shape.CornerBasedShape getExtraSmall();
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 8114d47..5e3f0aa 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -49,11 +49,11 @@
         api("androidx.compose.ui:ui-text:1.4.0-beta02")
 
         // TODO: remove next 3 dependencies when b/202810604 is fixed
-        implementation("androidx.savedstate:savedstate-ktx:1.2.0")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+        implementation("androidx.savedstate:savedstate-ktx:1.2.1")
+        implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
 
-        implementation("androidx.lifecycle:lifecycle-common-java8:2.6.0")
+        implementation("androidx.lifecycle:lifecycle-common-java8:2.6.1")
 
         testImplementation(libs.testRules)
         testImplementation(libs.testRunner)
@@ -109,11 +109,11 @@
                 implementation("androidx.activity:activity-compose:1.5.0")
 
                 // TODO: remove next 3 dependencies when b/202810604 is fixed
-                implementation("androidx.savedstate:savedstate-ktx:1.2.0")
-                implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+                implementation("androidx.savedstate:savedstate-ktx:1.2.1")
+                implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
 
-                implementation("androidx.lifecycle:lifecycle-common-java8:2.6.0")
+                implementation("androidx.lifecycle:lifecycle-common-java8:2.6.1")
             }
 
             desktopMain.dependencies {
diff --git a/compose/material3/material3/samples/build.gradle b/compose/material3/material3/samples/build.gradle
index 9cf5a69..35f4181 100644
--- a/compose/material3/material3/samples/build.gradle
+++ b/compose/material3/material3/samples/build.gradle
@@ -40,7 +40,7 @@
     implementation("androidx.compose.runtime:runtime:1.2.1")
     implementation("androidx.compose.ui:ui:1.2.1")
     implementation("androidx.compose.ui:ui-text:1.2.1")
-    implementation("androidx.savedstate:savedstate-ktx:1.2.0")
+    implementation("androidx.savedstate:savedstate-ktx:1.2.1")
     implementation("androidx.compose.ui:ui-tooling-preview:1.4.0-beta02")
 
     debugImplementation("androidx.compose.ui:ui-tooling:1.4.0-beta02")
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
index 94a3911..180c2ec 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
@@ -22,17 +22,17 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.IntrinsicSize
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
-import androidx.compose.material.Button
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.outlined.Keyboard
 import androidx.compose.material.icons.outlined.Schedule
+import androidx.compose.material3.Button
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
@@ -54,10 +54,13 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalConfiguration
+import androidx.compose.ui.semantics.isContainer
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.window.Dialog
 import androidx.compose.ui.window.DialogProperties
+import androidx.compose.ui.zIndex
 import java.text.SimpleDateFormat
 import java.util.Calendar
 import java.util.Locale
@@ -179,28 +182,43 @@
                 showTimePicker = false
             },
             toggle = {
-                if (configuration.screenHeightDp > 540) {
-                    IconButton(
-                        modifier = Modifier.offset(x = -(8.dp)),
-                        onClick = { showingPicker.value = !showingPicker.value }) {
-                        val icon = if (showingPicker.value) {
-                            Icons.Outlined.Keyboard
-                        } else {
-                            Icons.Outlined.Schedule
-                        }
-                        Icon(
-                            icon,
-                            contentDescription = if (showingPicker.value) {
-                                "Switch to Text Input"
+                if (configuration.screenHeightDp > 400) {
+                    // Make this take the entire viewport. This will guarantee that Screen readers
+                    // focus the toggle first.
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .semantics { isContainer = true }
+                    ) {
+                        IconButton(
+                            modifier = Modifier
+                                // This is a workaround so that the Icon comes up first
+                                // in the talkback traversal order. So that users of a11y
+                                // services can use the text input. When talkback traversal
+                                // order is customizable we can remove this.
+                                .size(64.dp, 72.dp)
+                                .align(Alignment.BottomStart)
+                                .zIndex(5f),
+                            onClick = { showingPicker.value = !showingPicker.value }) {
+                            val icon = if (showingPicker.value) {
+                                Icons.Outlined.Keyboard
                             } else {
-                                "Switch to Touch Input"
+                                Icons.Outlined.Schedule
                             }
-                        )
+                            Icon(
+                                icon,
+                                contentDescription = if (showingPicker.value) {
+                                    "Switch to Text Input"
+                                } else {
+                                    "Switch to Touch Input"
+                                }
+                            )
+                        }
                     }
                 }
             }
         ) {
-            if (showingPicker.value && configuration.screenHeightDp > 540) {
+            if (showingPicker.value && configuration.screenHeightDp > 400) {
                 TimePicker(state = state)
             } else {
                 TimeInput(state = state)
@@ -214,7 +232,7 @@
     title: String = "Select Time",
     onCancel: () -> Unit,
     onConfirm: () -> Unit,
-    toggle: @Composable RowScope.() -> Unit = {},
+    toggle: @Composable () -> Unit = {},
     content: @Composable () -> Unit,
 ) {
     Dialog(
@@ -226,11 +244,13 @@
             tonalElevation = 6.dp,
             modifier = Modifier
                 .width(IntrinsicSize.Min)
+                .height(IntrinsicSize.Min)
                 .background(
                     shape = MaterialTheme.shapes.extraLarge,
                     color = MaterialTheme.colorScheme.surface
                 ),
         ) {
+            toggle()
             Column(
                 modifier = Modifier.padding(24.dp),
                 horizontalAlignment = Alignment.CenterHorizontally
@@ -248,7 +268,6 @@
                         .height(40.dp)
                         .fillMaxWidth()
                 ) {
-                    toggle()
                     Spacer(modifier = Modifier.weight(1f))
                     TextButton(
                         onClick = onCancel
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
index d3fcf7f..5dc6547 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/BottomSheetScaffoldTest.kt
@@ -28,29 +28,38 @@
 import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.size
+import androidx.compose.material3.tokens.SheetBottomTokens
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.testutils.assertShape
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.shadow
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.RectangleShape
+import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.compositeOver
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.onGloballyPositioned
+import androidx.compose.ui.layout.positionInRoot
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.SemanticsActions
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.SemanticsMatcher
 import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.StateRestorationTester
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onParent
 import androidx.compose.ui.test.performSemanticsAction
@@ -166,7 +175,10 @@
                             .requiredHeight(sheetHeight)
                             .testTag(sheetTag))
                 },
-                sheetDragHandle = { Box(Modifier.testTag(dragHandleTag).size(dragHandleSize)) },
+                sheetDragHandle = { Box(
+                    Modifier
+                        .testTag(dragHandleTag)
+                        .size(dragHandleSize)) },
                 sheetPeekHeight = peekHeight
             ) {
                 Text("Content")
@@ -200,7 +212,10 @@
                             .requiredHeight(sheetHeight)
                             .testTag(sheetTag))
                 },
-                sheetDragHandle = { Box(Modifier.testTag(dragHandleTag).size(dragHandleSize)) },
+                sheetDragHandle = { Box(
+                    Modifier
+                        .testTag(dragHandleTag)
+                        .size(dragHandleSize)) },
                 sheetPeekHeight = peekHeight
             ) {
                 Text("Content")
@@ -566,4 +581,95 @@
             rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
         }
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun bottomSheetScaffold_slotsPositionedAppropriately() {
+        val topBarHeight = 56.dp
+        val expectedDragHandleVerticalPadding = 22.dp
+        val hostState = SnackbarHostState()
+        var snackbarSize: IntSize? = null
+        var snackbarPosition: Offset? = null
+        var density: Density? = null
+        var dragHandleContentDescription = ""
+        var dragHandleColor: Color = Color.Unspecified
+        var surface: Color = Color.Unspecified
+        val dragHandleShape: Shape = RectangleShape
+
+        rule.setContent {
+            dragHandleContentDescription = getString(Strings.BottomSheetDragHandleDescription)
+            dragHandleColor = SheetBottomTokens.DockedDragHandleColor.toColor()
+                .copy(SheetBottomTokens.DockedDragHandleOpacity)
+            surface = MaterialTheme.colorScheme.surface
+            density = LocalDensity.current
+            BottomSheetScaffold(
+                sheetContent = {
+                    Box(
+                        Modifier
+                            .height(sheetHeight)
+                            .fillMaxWidth()
+                            .testTag(sheetTag)
+                        )
+                    },
+                sheetPeekHeight = peekHeight,
+                sheetDragHandle = {
+                    BottomSheetDefaults.DragHandle(
+                        shape = dragHandleShape,
+                    )
+                },
+                topBar = {
+                    Box(modifier = Modifier
+                        .height(topBarHeight)
+                        .fillMaxWidth()
+                        .testTag("TopBar")
+                    )
+                },
+                snackbarHost = {
+                    SnackbarHost(
+                        hostState = hostState,
+                        modifier = Modifier
+                            .onGloballyPositioned {
+                                snackbarSize = it.size
+                                snackbarPosition = it.positionInRoot()
+                            },
+                    )
+                },
+            ) {
+                Box(Modifier.padding(it)) {
+                    Text("Scaffold Content", Modifier.testTag("ScaffoldContent"))
+                }
+            }
+        }
+        // Assert that the drag handle has vertical padding of 22.dp
+        rule
+            .onNodeWithContentDescription(dragHandleContentDescription)
+            .captureToImage()
+            .assertShape(
+                density = rule.density,
+                horizontalPadding = 0.dp,
+                verticalPadding = 22.dp,
+                backgroundColor = dragHandleColor.compositeOver(surface),
+                shapeColor = dragHandleColor.compositeOver(surface),
+                shape = dragHandleShape
+            )
+        // Assert sheet content is positioned at the sheet peek height + drag handle height + 22.dp
+        // top and bottom padding.
+        rule.onNodeWithTag(sheetTag).assertTopPositionInRootIsEqualTo(
+            rule.rootHeight() - peekHeight +
+                (expectedDragHandleVerticalPadding * 2) + SheetBottomTokens.DockedDragHandleHeight
+        )
+        // Assert TopBar is placed at the top of the app.
+        rule.onNodeWithTag("TopBar").assertTopPositionInRootIsEqualTo(0.dp)
+        // Assert TopBar is sized appropriately.
+        rule.onNodeWithTag("TopBar").assertHeightIsEqualTo(topBarHeight)
+        rule.onNodeWithTag("TopBar").assertWidthIsEqualTo(rule.rootWidth())
+        // Assert scaffold content consumes TopBar height for padding.
+        rule.onNodeWithTag("ScaffoldContent").assertTopPositionInRootIsEqualTo(topBarHeight)
+
+        // Assert snackbar is placed above bottom sheet when partially expanded.
+        val snackbarBottomOffset = snackbarPosition!!.y + snackbarSize!!.height.toFloat()
+        val expectedSnackbarBottomOffset =
+            with(density!!) { rule.rootHeight().toPx() - peekHeight.toPx() - snackbarSize!!.height }
+        assertThat(snackbarBottomOffset).isWithin(1f).of(expectedSnackbarBottomOffset)
+    }
 }
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
index ae46835..fa4a4be 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/OutlinedTextFieldTest.kt
@@ -76,6 +76,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
@@ -86,7 +87,6 @@
 import com.nhaarman.mockitokotlin2.eq
 import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.verify
-import kotlin.math.max
 import kotlin.math.roundToInt
 import org.junit.Rule
 import org.junit.Test
@@ -227,39 +227,58 @@
 
     @Test
     fun testOutlinedTextField_labelPosition_initial_singleLine() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    singleLine = true,
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                singleLine = true,
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is centered, plus additional padding allowance on top
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                ((ExpectedMinimumTextFieldHeight - MinTextLineHeight) / 2 +
+                    OutlinedTextFieldTopPadding).toPx()
             )
-            // label is centered in 56.dp default container, plus additional 8.dp padding on top
-            val minimumHeight = ExpectedMinimumTextFieldHeight.roundToPx()
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((minimumHeight - labelSize.value!!.height) / 2f).roundToInt() + 8.dp.roundToPx()
+        }
+    }
+
+    @Test
+    fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
+        val labelPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        rule.runOnIdleWithDensity {
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + default padding + label padding allowance
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
@@ -268,22 +287,22 @@
     fun testOutlinedTextField_labelPosition_initial_withMultiLineLabel() {
         val textFieldWidth = 200.dp
         val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.requiredWidth(textFieldWidth),
-                    label = {
-                        Text(
-                            text = "long long long long long long long long long long long long",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.requiredWidth(textFieldWidth),
+                label = {
+                    Text(
+                        text = "long long long long long long long long long long long long",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelSize.value = it.size
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
@@ -292,50 +311,20 @@
             assertThat(labelSize.value?.height).isGreaterThan(0)
             assertThat(labelSize.value?.width)
                 .isEqualTo(textFieldWidth.roundToPx() - 2 * ExpectedPadding.roundToPx())
-        }
-    }
 
-    @Test
-    fun testOutlinedTextField_labelPosition_initial_withDefaultHeight() {
-        val labelSize = Ref<IntSize>()
-        val labelPosition = Ref<Offset>()
-        rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
-        }
-
-        rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // label is aligned to the top with padding, plus additional 8.dp padding on top
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx() + 8.dp.roundToPx()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + default padding + label padding allowance
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
 
     @Test
     fun testOutlinedTextField_labelPosition_whenFocused() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
+        val labelSize = MinFocusedLabelLineHeight
 
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
@@ -343,11 +332,10 @@
                 value = "",
                 onValueChange = {},
                 label = {
-                    Text(
-                        text = "label",
-                        modifier = Modifier.onGloballyPositioned {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
                             labelPosition.value = it.positionInRoot()
-                            labelSize.value = it.size
                         }
                     )
                 }
@@ -358,15 +346,10 @@
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // label position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
             )
-            assertThat(labelPosition.value?.y).isEqualTo(getLabelPosition(labelSize))
         }
     }
 
@@ -374,22 +357,22 @@
     fun testOutlinedTextField_labelPosition_whenFocused_withMultiLineLabel() {
         val textFieldWidth = 200.dp
         val labelSize = Ref<IntSize>()
+        val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.testTag(TextFieldTag).requiredWidth(textFieldWidth),
-                    label = {
-                        Text(
-                            text = "long long long long long long long long long long long long",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.testTag(TextFieldTag).requiredWidth(textFieldWidth),
+                label = {
+                    Text(
+                        text = "long long long long long long long long long long long long",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelSize.value = it.size
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -399,8 +382,15 @@
             // label size
             assertThat(labelSize.value).isNotNull()
             assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width)
-                .isEqualTo(textFieldWidth.roundToPx() - 2 * ExpectedPadding.roundToPx())
+            assertThat(labelSize.value?.width!!.toFloat()).isWithin(1f).of(
+                (textFieldWidth - ExpectedPadding * 2).toPx()
+            )
+
+            // label position
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.value!!.height).toFloat()
+            )
         }
     }
 
@@ -455,18 +445,17 @@
 
     @Test
     fun testOutlinedTextField_labelPosition_whenInput() {
-        val labelSize = Ref<IntSize>()
+        val labelSize = MinFocusedLabelLineHeight
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
                 value = "input",
                 onValueChange = {},
                 label = {
-                    Text(
-                        text = "label",
-                        modifier = Modifier.onGloballyPositioned {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
                             labelPosition.value = it.positionInRoot()
-                            labelSize.value = it.size
                         }
                     )
                 }
@@ -474,15 +463,11 @@
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
             // label position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
             )
-            assertThat(labelPosition.value?.y).isEqualTo(getLabelPosition(labelSize))
         }
     }
 
@@ -513,7 +498,7 @@
 
     @Test
     fun testOutlinedTextField_placeholderPosition_withLabel() {
-        val placeholderSize = Ref<IntSize>()
+        val placeholderSize = MinTextLineHeight
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             Box {
@@ -521,13 +506,12 @@
                     modifier = Modifier.testTag(TextFieldTag),
                     value = "",
                     onValueChange = {},
-                    label = { Text("label") },
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(placeholderSize)
+                            .onGloballyPositioned {
                                 placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
                             }
                         )
                     }
@@ -538,57 +522,38 @@
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // placeholder is aligned to the top with padding, plus additional 8.dp padding on top
-            assertThat(placeholderPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx() + 8.dp.roundToPx()
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
             )
         }
     }
 
     @Test
     fun testOutlinedTextField_placeholderPosition_whenNoLabel() {
-        val placeholderSize = Ref<IntSize>()
+        val placeholderSize = MinTextLineHeight
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            // placeholder is placed with fixed padding
-            assertThat(placeholderPosition.value?.y).isEqualTo(TextFieldPadding.roundToPx())
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -597,22 +562,20 @@
         val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Column {
-                OutlinedTextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "input",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "input",
+                onValueChange = {},
+                placeholder = {
+                    Text(
+                        text = "placeholder",
+                        modifier = Modifier.onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                            placeholderSize.value = it.size
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -644,6 +607,61 @@
     }
 
     @Test
+    fun testOutlinedTextField_labelAndPlaceholderPosition_whenSmallerThanMinimumHeight() {
+        val labelSize = 10.dp
+        val labelPosition = Ref<Offset>()
+        val placeholderSize = 20.dp
+        val placeholderPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            OutlinedTextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        // click to focus
+        rule.onNodeWithTag(TextFieldTag).performClick()
+
+        rule.runOnIdleWithDensity {
+            // size
+            assertThat(labelSize).isLessThan(MinFocusedLabelLineHeight)
+            assertThat(placeholderSize).isLessThan(MinTextLineHeight)
+
+            // label position
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                getLabelPosition(labelSize.roundToPx()).toFloat()
+            )
+
+            // placeholder position
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // placeholder y position is top + default padding + label padding allowance, then
+            // centered within allocated space
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (ExpectedPadding + OutlinedTextFieldTopPadding +
+                    (MinTextLineHeight - placeholderSize) / 2).toPx()
+            )
+        }
+    }
+
+    @Test
     fun testOutlinedTextField_trailingAndLeading_sizeAndPosition_defaultIcon() {
         val textFieldWidth = 300.dp
         val leadingPosition = Ref<Offset>()
@@ -841,11 +859,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_withLabel() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
-        val labelSize = Ref<IntSize>()
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -853,29 +870,21 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
+                    modifier = Modifier.width(textFieldWidth),
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -888,14 +897,16 @@
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(
-                    (ExpectedPadding + 8.dp).toPx())
+                    (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
+                )
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(
-                    (ExpectedPadding + 8.dp).toPx())
+                    (ExpectedPadding + OutlinedTextFieldTopPadding).toPx()
+                )
             }
         }
     }
@@ -903,10 +914,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_whenNoLabel() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -914,21 +925,20 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(textFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -944,7 +954,7 @@
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -954,10 +964,10 @@
     @Test
     fun testOutlinedTextField_prefixAndSuffixPosition_withIcons() {
         val textFieldWidth = 300.dp
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -965,21 +975,20 @@
                 OutlinedTextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(textFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(textFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     },
@@ -995,13 +1004,13 @@
 
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(
-                    (ExpectedPadding + IconPadding + iconSize).toPx())
+                    (ExpectedPadding + IconPadding + iconSize).toPx()
+                )
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (textFieldWidth - IconPadding - iconSize - ExpectedPadding -
-                        suffixSize.value!!.width.toDp()).toPx()
+                    (textFieldWidth - IconPadding - iconSize - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -1012,28 +1021,26 @@
     fun testOutlinedTextField_labelPositionX_initial_withTrailingAndLeading() {
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = { Icon(Icons.Default.Favorite, null) },
-                    leadingIcon = { Icon(Icons.Default.Favorite, null) }
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = { Icon(Icons.Default.Favorite, null) },
+                leadingIcon = { Icon(Icons.Default.Favorite, null) }
+            )
         }
 
         rule.runOnIdleWithDensity {
             val iconSize = 24.dp // default icon size
-            assertThat(labelPosition.value?.x).isEqualTo(
-                (ExpectedPadding + IconPadding + iconSize).roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(
+                (ExpectedPadding + IconPadding + iconSize).toPx()
             )
         }
     }
@@ -1042,28 +1049,24 @@
     fun testOutlinedTextField_labelPositionX_initial_withNullTrailingAndLeading() {
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                OutlinedTextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = null,
-                    leadingIcon = null
-                )
-            }
+            OutlinedTextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = null,
+                leadingIcon = null
+            )
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -1106,21 +1109,16 @@
 
     @Test
     fun testOutlinedTextField_supportingText_position() {
-        val tfSize = Ref<IntSize>()
-        val supportingSize = Ref<IntSize>()
         val supportingPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
                 value = "",
                 onValueChange = {},
-                modifier = Modifier.onGloballyPositioned {
-                    tfSize.value = it.size
-                },
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                 supportingText = {
-                    Text(
-                        text = "Supporting",
-                        modifier = Modifier.onGloballyPositioned {
-                            supportingSize.value = it.size
+                    Box(Modifier
+                        .size(MinSupportingTextLineHeight)
+                        .onGloballyPositioned {
                             supportingPosition.value = it.positionInRoot()
                         }
                     )
@@ -1129,11 +1127,9 @@
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(supportingPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(supportingPosition.value?.y).isEqualTo(
-                tfSize.value!!.height - supportingSize.value!!.height
+            assertThat(supportingPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(supportingPosition.value?.y).isWithin(1f).of(
+                (ExpectedMinimumTextFieldHeight + SupportingTopPadding).toPx()
             )
         }
     }
@@ -1277,7 +1273,7 @@
     }
 
     @Test
-    fun testErrorSemantics_defaultMessage() {
+    fun testOutlinedTextField_errorSemantics_defaultMessage() {
         lateinit var errorMessage: String
         rule.setMaterialContent(lightColorScheme()) {
             OutlinedTextField(
@@ -1296,7 +1292,7 @@
     }
 
     @Test
-    fun testErrorSemantics_messageOverridable() {
+    fun testOutlinedTextField_errorSemantics_messageOverridable() {
         val errorMessage = "Special symbols not allowed"
         rule.setMaterialContent(lightColorScheme()) {
             val isError = remember { mutableStateOf(true) }
@@ -1602,7 +1598,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") }
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                     )
                     Divider(Modifier.fillMaxHeight())
                 }
@@ -1626,7 +1622,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         leadingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1651,7 +1647,7 @@
                     OutlinedTextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         trailingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1697,11 +1693,11 @@
         }
     }
 
-    private fun getLabelPosition(labelSize: Ref<IntSize>): Int {
-        val labelHalfHeight = labelSize.value!!.height / 2
+    private fun getLabelPosition(labelHeight: Int): Int {
+        val labelHalfHeight = labelHeight / 2
         val paddingTop = with(rule.density) { OutlinedTextFieldTopPadding.toPx() }
-        // vertical position is the default padding - half height
-        // in case negative position, fix to 0
-        return max(paddingTop - labelHalfHeight, 0f).roundToInt()
+        // Vertical position is the default padding - half height.
+        // This can be negative, meaning default padding is not enough for the focused label.
+        return (paddingTop - labelHalfHeight).roundToInt()
     }
 }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
index 447e330..4b994e6 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TextFieldTest.kt
@@ -31,7 +31,6 @@
 import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
-import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
@@ -126,7 +125,7 @@
     val rule = createComposeRule()
 
     @Test
-    fun testTextField_minimumHeight() {
+    fun testTextField_setSmallHeight() {
         rule.setMaterialContentForSizeAssertions {
             TextField(
                 value = "input",
@@ -150,6 +149,18 @@
     }
 
     @Test
+    fun testTextField_defaultHeight() {
+        rule.setMaterialContentForSizeAssertions {
+            TextField(
+                value = "",
+                onValueChange = {},
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
+            )
+        }
+            .assertHeightIsEqualTo(ExpectedDefaultTextFieldHeight)
+    }
+
+    @Test
     fun testTextField_defaultWidth() {
         rule.setMaterialContentForSizeAssertions {
             TextField(
@@ -322,293 +333,207 @@
 
     @Test
     fun testTextField_labelPosition_initial_singleLine() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    singleLine = true,
-                    label = {
-                        Text(
-                            text = "label",
-                            fontSize = 10.sp,
-                            modifier = Modifier
-                                .onGloballyPositioned {
-                                    labelPosition.value = it.positionInRoot()
-                                    labelSize.value = it.size
-                                }
-                        )
-                    },
-                    modifier = Modifier.height(56.dp)
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                singleLine = true,
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ((ExpectedDefaultTextFieldHeight.roundToPx() - labelSize.value!!.height) / 2f)
-                    .roundToInt().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is centered
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (ExpectedDefaultTextFieldHeight - MinTextLineHeight).toPx() / 2f
             )
         }
     }
 
     @Test
     fun testTextField_labelPosition_initial_withDefaultHeight() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            fontSize = 10.sp,
-                            modifier = Modifier
-                                .onGloballyPositioned {
-                                    labelPosition.value = it.positionInRoot()
-                                    labelSize.value = it.size
-                                }
-                        )
-                    },
-                    modifier = Modifier.height(56.dp)
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ExpectedPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
     @Test
     fun testTextField_labelPosition_initial_withCustomHeight() {
         val height = 80.dp
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                modifier = Modifier.height(height),
+                label = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                ExpectedPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
     @Test
     fun testTextField_labelPosition_whenFocused() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldWithLabelVerticalPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + (different) padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                TextFieldWithLabelVerticalPadding.toPx()
             )
         }
     }
 
     @Test
     fun testTextField_labelPosition_whenInput() {
-        val labelSize = Ref<IntSize>()
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "input",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                                labelSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                value = "input",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(MinFocusedLabelLineHeight)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(labelSize.value?.height).isGreaterThan(0)
-            assertThat(labelSize.value?.width).isGreaterThan(0)
-
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(labelPosition.value?.y).isEqualTo(
-                TextFieldWithLabelVerticalPadding.roundToPx().toFloat()
+            // x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + (different) padding
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                TextFieldWithLabelVerticalPadding.toPx()
             )
         }
     }
 
     @Test
     fun testTextField_placeholderPosition_withLabel() {
-        val labelSize = Ref<IntSize>()
-        val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier
-                        .height(60.dp)
-                        .testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
+                placeholder = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
+
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(labelSize.value).isNotNull()
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isGreaterThan(0)
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // placeholder's position
+            // x position is start + padding
             assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
-            assertThat(placeholderPosition.value?.y).isWithin(1f)
-                .of(
-                    TextFieldWithLabelVerticalPadding.toPx() +
-                        labelSize.value!!.height.toFloat()
-                )
+            // y position is top + padding + label height
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+            )
         }
     }
 
     @Test
     fun testTextField_placeholderPosition_whenNoLabel() {
-        val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
-        val height = 60.dp
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    modifier = Modifier.height(height).testTag(TextFieldTag),
-                    value = "",
-                    onValueChange = {},
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.requiredHeight(20.dp)
-                                .onGloballyPositioned {
-                                    placeholderPosition.value = it.positionInRoot()
-                                    placeholderSize.value = it.size
-                                }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                placeholder = {
+                    Box(Modifier
+                        .size(MinTextLineHeight)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
         }
+
         // click to focus
         rule.onNodeWithTag(TextFieldTag).performClick()
 
         rule.runOnIdleWithDensity {
-            // size
-            assertThat(placeholderSize.value).isNotNull()
-            assertThat(placeholderSize.value?.height).isEqualTo(20.dp.roundToPx())
-            assertThat(placeholderSize.value?.width).isGreaterThan(0)
-            // centered position
-            assertThat(placeholderPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(placeholderPosition.value?.y).isEqualTo(
-                TextFieldPadding.roundToPx()
-            )
+            // x position is start + padding
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // y position is top + padding
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -617,23 +542,20 @@
         val placeholderSize = Ref<IntSize>()
         val placeholderPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Column {
-                TextField(
-                    modifier = Modifier.testTag(TextFieldTag),
-                    value = "input",
-                    onValueChange = {},
-
-                    placeholder = {
-                        Text(
-                            text = "placeholder",
-                            modifier = Modifier.onGloballyPositioned {
-                                placeholderPosition.value = it.positionInRoot()
-                                placeholderSize.value = it.size
-                            }
-                        )
-                    }
-                )
-            }
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "input",
+                onValueChange = {},
+                placeholder = {
+                    Text(
+                        text = "placeholder",
+                        modifier = Modifier.onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                            placeholderSize.value = it.size
+                        }
+                    )
+                }
+            )
         }
 
         // click to focus
@@ -665,6 +587,63 @@
     }
 
     @Test
+    fun testTextField_labelAndPlaceholderPosition_whenSmallerThanMinimumHeight() {
+        val labelSize = 10.dp
+        val labelPosition = Ref<Offset>()
+        val placeholderSize = 20.dp
+        val placeholderPosition = Ref<Offset>()
+        rule.setMaterialContent(lightColorScheme()) {
+            TextField(
+                modifier = Modifier.testTag(TextFieldTag),
+                value = "",
+                onValueChange = {},
+                label = {
+                    Box(Modifier
+                        .size(labelSize)
+                        .onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                placeholder = {
+                    Box(Modifier
+                        .size(placeholderSize)
+                        .onGloballyPositioned {
+                            placeholderPosition.value = it.positionInRoot()
+                        }
+                    )
+                }
+            )
+        }
+
+        // click to focus
+        rule.onNodeWithTag(TextFieldTag).performClick()
+
+        rule.runOnIdleWithDensity {
+            // size
+            assertThat(labelSize).isLessThan(MinFocusedLabelLineHeight)
+            assertThat(placeholderSize).isLessThan(MinTextLineHeight)
+
+            // label x position is start + padding
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // label y position is top + padding, then centered within allocated space
+            assertThat(labelPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + (MinFocusedLabelLineHeight - labelSize) / 2)
+                    .toPx()
+            )
+
+            // placeholder x position is start + padding
+            assertThat(placeholderPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            // placeholder y position is top + padding + label height, then centered within
+            // allocated space
+            assertThat(placeholderPosition.value?.y).isWithin(1f).of(
+                (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight +
+                    (MinTextLineHeight - placeholderSize) / 2).toPx()
+            )
+        }
+    }
+
+    @Test
     fun testTextField_trailingAndLeading_sizeAndPosition_defaultIcon() {
         val textFieldHeight = 60.dp
         val leadingPosition = Ref<Offset>()
@@ -868,11 +847,10 @@
 
     @Test
     fun testTextField_prefixAndSuffixPosition_withLabel() {
-        val textFieldHeight = 60.dp
-        val labelSize = Ref<IntSize>()
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -880,29 +858,21 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelSize.value = it.size
-                            }
-                        )
-                    },
+                    modifier = Modifier.width(TextFieldWidth),
+                    label = { Box(Modifier.size(MinFocusedLabelLineHeight)) },
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -914,33 +884,27 @@
             with(density) {
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
-                assertThat(prefixPosition.value?.y)
-                    .isWithin(1f)
-                    .of(
-                        TextFieldWithLabelVerticalPadding.toPx() +
-                            labelSize.value!!.height.toFloat()
-                    )
+                assertThat(prefixPosition.value?.y).isWithin(1f).of(
+                    (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+                )
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
-                assertThat(suffixPosition.value?.y)
-                    .isWithin(1f)
-                    .of(
-                        TextFieldWithLabelVerticalPadding.toPx() +
-                            labelSize.value!!.height.toFloat()
-                    )
+                assertThat(suffixPosition.value?.y).isWithin(1f).of(
+                    (TextFieldWithLabelVerticalPadding + MinFocusedLabelLineHeight).toPx()
+                )
             }
         }
     }
 
     @Test
     fun testTextField_prefixAndSuffixPosition_whenNoLabel() {
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -948,21 +912,20 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(TextFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     }
@@ -978,7 +941,7 @@
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - ExpectedPadding - suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -987,10 +950,10 @@
 
     @Test
     fun testTextField_prefixAndSuffixPosition_withIcons() {
-        val textFieldHeight = 60.dp
         val prefixPosition = Ref<Offset>()
+        val prefixSize = MinTextLineHeight
         val suffixPosition = Ref<Offset>()
-        val suffixSize = Ref<IntSize>()
+        val suffixSize = MinTextLineHeight
         val density = Density(2f)
 
         rule.setMaterialContent(lightColorScheme()) {
@@ -998,21 +961,20 @@
                 TextField(
                     value = "text",
                     onValueChange = {},
-                    modifier = Modifier.size(TextFieldWidth, textFieldHeight),
+                    modifier = Modifier.width(TextFieldWidth),
                     prefix = {
-                        Text(
-                            text = "P",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(prefixSize)
+                            .onGloballyPositioned {
                                 prefixPosition.value = it.positionInRoot()
                             }
                         )
                     },
                     suffix = {
-                        Text(
-                            text = "S",
-                            modifier = Modifier.onGloballyPositioned {
+                        Box(Modifier
+                            .size(suffixSize)
+                            .onGloballyPositioned {
                                 suffixPosition.value = it.positionInRoot()
-                                suffixSize.value = it.size
                             }
                         )
                     },
@@ -1028,13 +990,13 @@
 
                 // prefix
                 assertThat(prefixPosition.value?.x).isWithin(1f).of(
-                    (ExpectedPadding + IconPadding + iconSize).toPx())
+                    (ExpectedPadding + IconPadding + iconSize).toPx()
+                )
                 assertThat(prefixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
 
                 // suffix
                 assertThat(suffixPosition.value?.x).isWithin(1f).of(
-                    (TextFieldWidth - IconPadding - iconSize - ExpectedPadding -
-                        suffixSize.value!!.width.toDp()).toPx()
+                    (TextFieldWidth - IconPadding - iconSize - ExpectedPadding - suffixSize).toPx()
                 )
                 assertThat(suffixPosition.value?.y).isWithin(1f).of(ExpectedPadding.toPx())
             }
@@ -1043,64 +1005,54 @@
 
     @Test
     fun testTextField_labelPositionX_initial_withTrailingAndLeading() {
-        val height = 60.dp
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = { Icon(Icons.Default.Favorite, null) },
-                    leadingIcon = { Icon(Icons.Default.Favorite, null) }
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = { Icon(Icons.Default.Favorite, null) },
+                leadingIcon = { Icon(Icons.Default.Favorite, null) }
+            )
         }
 
         rule.runOnIdleWithDensity {
             val iconSize = 24.dp // default icon size
-            assertThat(labelPosition.value?.x).isEqualTo(
-                (ExpectedPadding + IconPadding + iconSize).roundToPx().toFloat()
+            assertThat(labelPosition.value?.x).isWithin(1f).of(
+                (ExpectedPadding + IconPadding + iconSize).toPx()
             )
         }
     }
 
     @Test
     fun testTextField_labelPositionX_initial_withNullTrailingAndLeading() {
-        val height = 60.dp
         val labelPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
-            Box {
-                TextField(
-                    value = "",
-                    onValueChange = {},
-                    modifier = Modifier.height(height),
-                    label = {
-                        Text(
-                            text = "label",
-                            modifier = Modifier.onGloballyPositioned {
-                                labelPosition.value = it.positionInRoot()
-                            }
-                        )
-                    },
-                    trailingIcon = null,
-                    leadingIcon = null
-                )
-            }
+            TextField(
+                value = "",
+                onValueChange = {},
+                label = {
+                    Text(
+                        text = "label",
+                        modifier = Modifier.onGloballyPositioned {
+                            labelPosition.value = it.positionInRoot()
+                        }
+                    )
+                },
+                trailingIcon = null,
+                leadingIcon = null
+            )
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(labelPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
+            assertThat(labelPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
         }
     }
 
@@ -1142,22 +1094,17 @@
     }
 
     @Test
-    fun testTextField_supportingText_position() {
-        val tfSize = Ref<IntSize>()
-        val supportingSize = Ref<IntSize>()
+    fun testTextField_supportingTextPosition_withDefaultHeight() {
         val supportingPosition = Ref<Offset>()
         rule.setMaterialContent(lightColorScheme()) {
             TextField(
                 value = "",
                 onValueChange = {},
-                modifier = Modifier.onGloballyPositioned {
-                    tfSize.value = it.size
-                },
+                textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                 supportingText = {
-                    Text(
-                        text = "Supporting",
-                        modifier = Modifier.onGloballyPositioned {
-                            supportingSize.value = it.size
+                    Box(Modifier
+                        .size(MinSupportingTextLineHeight)
+                        .onGloballyPositioned {
                             supportingPosition.value = it.positionInRoot()
                         }
                     )
@@ -1166,11 +1113,9 @@
         }
 
         rule.runOnIdleWithDensity {
-            assertThat(supportingPosition.value?.x).isEqualTo(
-                ExpectedPadding.roundToPx().toFloat()
-            )
-            assertThat(supportingPosition.value?.y).isEqualTo(
-                tfSize.value!!.height - supportingSize.value!!.height
+            assertThat(supportingPosition.value?.x).isWithin(1f).of(ExpectedPadding.toPx())
+            assertThat(supportingPosition.value?.y).isWithin(1f).of(
+                (ExpectedDefaultTextFieldHeight + SupportingTopPadding).toPx()
             )
         }
     }
@@ -1374,7 +1319,7 @@
     @Test
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun testTransformedTextIsUsed_toDefineLabelPosition() {
+    fun testTextField_transformedTextIsUsed_toDefineLabelPosition() {
         // if non-transformed value were used to check if the text input is empty, the label
         // wouldn't be aligned to the top, as a result it would be obscured by text
         val prefixTransformation = VisualTransformation { text ->
@@ -1410,7 +1355,7 @@
     @Test
     @LargeTest
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
-    fun testTransformedTextIsUsed_toDefineIfPlaceholderNeeded() {
+    fun testTextField_transformedTextIsUsed_toDefineIfPlaceholderNeeded() {
         // if original value were used to check if the text input is empty, the placeholder would be
         // displayed on top of the text
         val prefixTransformation = VisualTransformation { text ->
@@ -1454,7 +1399,7 @@
     }
 
     @Test
-    fun testErrorSemantics_defaultMessage() {
+    fun testTextField_errorSemantics_defaultMessage() {
         lateinit var errorMessage: String
         rule.setMaterialContent(lightColorScheme()) {
             TextField(
@@ -1471,10 +1416,10 @@
     }
 
     @Test
-    fun testErrorSemantics_messageOverridable() {
+    fun testTextField_errorSemantics_messageOverridable() {
         val errorMessage = "Special symbols not allowed"
         rule.setMaterialContent(lightColorScheme()) {
-            var isError = remember { mutableStateOf(true) }
+            val isError = remember { mutableStateOf(true) }
             TextField(
                 value = "test",
                 onValueChange = {},
@@ -1732,7 +1677,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") }
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                     )
                     Divider(Modifier.fillMaxHeight())
                 }
@@ -1756,7 +1701,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         leadingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
@@ -1781,7 +1726,7 @@
                     TextField(
                         value = text.value,
                         onValueChange = { text.value = it },
-                        placeholder = { Text("placeholder") },
+                        textStyle = TextStyle(fontSize = 1.sp), // ensure text size is minimum
                         trailingIcon = { Icon(Icons.Default.Favorite, null) }
                     )
                     Divider(Modifier.fillMaxHeight())
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TimePickerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TimePickerTest.kt
index b26f237..6fb42e6 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TimePickerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TimePickerTest.kt
@@ -17,17 +17,20 @@
 package androidx.compose.material3
 
 import android.content.Context
+import android.content.res.Resources
 import android.os.Build
 import android.text.format.DateFormat
-import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsProperties
 import androidx.compose.ui.semantics.SemanticsProperties.SelectableGroup
 import androidx.compose.ui.test.ExperimentalTestApi
 import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue
 import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionsProvider
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertAll
 import androidx.compose.ui.test.assertCountEquals
@@ -49,6 +52,7 @@
 import androidx.compose.ui.test.onAllNodesWithContentDescription
 import androidx.compose.ui.test.onAllNodesWithText
 import androidx.compose.ui.test.onChildren
+import androidx.compose.ui.test.onFirst
 import androidx.compose.ui.test.onLast
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithText
@@ -61,6 +65,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
 import com.android.dx.mockito.inline.extended.ExtendedMockito.doReturn
 import com.android.dx.mockito.inline.extended.ExtendedMockito.mockitoSession
 import com.android.dx.mockito.inline.extended.MockedMethod
@@ -85,9 +90,15 @@
             TimePicker(state)
         }
 
-        rule.onAllNodesWithText("23").assertCountEquals(1)
+       rule.onNodeWithTimeValue(
+           number = 2,
+           selection = Selection.Hour,
+       ).assertIsSelected()
 
-        rule.onNodeWithText("02").assertIsSelected()
+        rule.onNodeWithTimeValue(
+            number = 23,
+            selection = Selection.Minute,
+        ).assertExists()
 
         rule.onNodeWithText("AM").assertExists()
 
@@ -101,9 +112,14 @@
             TimePicker(state)
         }
 
-        rule.onNodeWithText("23").performClick()
+        rule.onNodeWithTimeValue(
+            number = 23,
+            selection = Selection.Minute,
+        ).performClick()
 
-        rule.onNodeWithText("55").assertExists()
+        rule.runOnIdle {
+            assertThat(state.selection).isEqualTo(Selection.Minute)
+        }
     }
 
     @Test
@@ -113,7 +129,7 @@
             TimePicker(state)
         }
 
-        rule.onNodeWithText("6").performClick()
+        rule.onNodeWithTimeValue(number = 6, selection = Selection.Hour).performClick()
 
         // shows 06 in display
         rule.onNodeWithText("06").assertExists()
@@ -479,7 +495,7 @@
         }
 
         repeat(24) { number ->
-            rule.onNodeWithText(number.toString()).performClick()
+            rule.onNodeWithTimeValue(number, Selection.Hour, is24Hour = true).performClick()
             rule.runOnIdle {
                 state.selection = Selection.Hour
                 assertThat(state.hour).isEqualTo(number)
@@ -506,11 +522,78 @@
                 else -> number
             }
 
-            rule.onNodeWithText("$hour").performClick()
+            rule.onNodeWithTimeValue(hour, Selection.Hour).performClick()
             rule.runOnIdle {
                 state.selection = Selection.Hour
                 assertThat(state.hour).isEqualTo(number)
             }
         }
     }
+
+    @Test
+    fun clockFace_24HourMinutes_everyValue() {
+        val state = TimePickerState(initialHour = 10, initialMinute = 23, is24Hour = true)
+        state.selection = Selection.Minute
+        rule.setMaterialContent(lightColorScheme()) {
+            ClockFace(state, TimePickerDefaults.colors())
+        }
+
+        repeat(11) { number ->
+            rule.onNodeWithTimeValue(
+                number * 5,
+                Selection.Minute,
+                is24Hour = true
+            ).performClick()
+            rule.runOnIdle {
+                assertThat(state.minute).isEqualTo(number * 5)
+            }
+        }
+    }
+
+    @Test
+    fun clockFace_12HourMinutes_everyValue() {
+        val state = TimePickerState(initialHour = 10, initialMinute = 23, is24Hour = false)
+        state.selection = Selection.Minute
+        rule.setMaterialContent(lightColorScheme()) {
+            ClockFace(state, TimePickerDefaults.colors())
+        }
+
+        repeat(11) { number ->
+            rule.onNodeWithTimeValue(number * 5, Selection.Minute).performClick()
+            rule.runOnIdle {
+                assertThat(state.minute).isEqualTo(number * 5)
+            }
+        }
+    }
+
+    private fun contentDescriptionForValue(
+        resources: Resources,
+        selection: Selection,
+        is24Hour: Boolean,
+        number: Int
+    ): String {
+
+        val id = if (selection == Selection.Minute) {
+            R.string.time_picker_minute_suffix
+        } else if (is24Hour) {
+            R.string.time_picker_hour_24h_suffix
+        } else {
+            R.string.time_picker_hour_suffix
+        }
+
+        return resources.getString(id, number)
+    }
+
+    private fun SemanticsNodeInteractionsProvider.onNodeWithTimeValue(
+        number: Int,
+        selection: Selection,
+        is24Hour: Boolean = false,
+    ): SemanticsNodeInteraction = onAllNodesWithContentDescription(
+        contentDescriptionForValue(
+            InstrumentationRegistry.getInstrumentation().context.resources,
+            selection,
+            is24Hour,
+            number
+        )
+    ).onFirst()
 }
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.android.kt
new file mode 100644
index 0000000..f3ed0e3
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.android.kt
@@ -0,0 +1,27 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.ui.text.PlatformTextStyle
+
+private const val DefaultIncludeFontPadding = true
+
+@Suppress("DEPRECATION")
+private val DefaultPlatformTextStyle = PlatformTextStyle(
+    includeFontPadding = DefaultIncludeFontPadding
+)
+internal actual fun defaultPlatformTextStyle(): PlatformTextStyle? = DefaultPlatformTextStyle
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
index 1c4cbf3..0956291 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/SearchBar.kt
@@ -469,6 +469,7 @@
 /**
  * Defaults used in [SearchBar] and [DockedSearchBar].
  */
+@ExperimentalMaterial3Api
 object SearchBarDefaults {
     /** Default elevation for a search bar. */
     val Elevation: Dp = SearchBarTokens.ContainerElevation
@@ -497,7 +498,6 @@
      * @param dividerColor the color of the divider between the input field and the search results
      * @param inputFieldColors the colors of the input field
      */
-    @ExperimentalMaterial3Api
     @Composable
     fun colors(
         containerColor: Color = SearchBarTokens.ContainerColor.toColor(),
@@ -533,7 +533,6 @@
      * @param unfocusedPlaceholderColor the placeholder color for this input field when not focused
      * @param disabledPlaceholderColor the placeholder color for this input field when disabled
      */
-    @ExperimentalMaterial3Api
     @Composable
     fun inputFieldColors(
         focusedTextColor: Color = SearchBarTokens.InputTextColor.toColor(),
@@ -573,7 +572,6 @@
         )
 
     @Deprecated("Maintained for binary compatibility", level = DeprecationLevel.HIDDEN)
-    @ExperimentalMaterial3Api
     @Composable
     fun inputFieldColors(
         textColor: Color = SearchBarTokens.InputTextColor.toColor(),
@@ -681,6 +679,7 @@
 }
 
 // Measurement specs
+@OptIn(ExperimentalMaterial3Api::class)
 private val SearchBarCornerRadius: Dp = InputFieldHeight / 2
 internal val DockedActiveTableMinHeight: Dp = 240.dp
 private const val DockedActiveTableMaxHeightScreenRatio: Float = 2f / 3f
diff --git a/compose/material3/material3/src/androidMain/res/values-af/strings.xml b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
index db5b6f4..580f969 100644
--- a/compose/material3/material3/src/androidMain/res/values-af/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Binne reikwydte"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Voer datums in"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ongeldige datumreeksinvoer"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Sleephandvatsel"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Nutswenk"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Wys nutswenk"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-am/strings.xml b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
index de076ad..66ab384 100644
--- a/compose/material3/material3/src/androidMain/res/values-am/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
@@ -51,14 +51,17 @@
     <string name="date_range_picker_end_headline" msgid="4766270708882012148">"የማብቂያ ቀን"</string>
     <string name="date_range_picker_scroll_to_next_month" msgid="51495506931835470">"ቀጣዩን ወር ለማሳየት ይሸብልሉ"</string>
     <string name="date_range_picker_scroll_to_previous_month" msgid="4371570854614540700">"ቀዳሚውን ወር ለማሳየት ይሸብልሉ"</string>
-    <!-- no translation found for date_range_picker_day_in_range (9048690781645835833) -->
-    <skip />
+    <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"በክልል ውስጥ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ቀናትን ያስገቡ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ልክ ያልሆነ የቀን ክልል ግቤት"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"መያዣ ይጎትቱ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
     <skip />
-    <!-- no translation found for tooltip_pane_description (8191239805703103845) -->
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
     <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
+    <skip />
+    <string name="tooltip_pane_description" msgid="8191239805703103845">"የመሣሪያ ጥቆማ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"መሣሪያ ጥቆማን አሳይ"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"ከሰዓት"</string>
     <string name="time_picker_am" msgid="5096144640014509074">"ጠዋት"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
index ed9d65e..033383e 100644
--- a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"في النطاق"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"إدخال التواريخ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"إدخال نطاق زمني غير صالح"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"مقبض السحب"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"تلميح"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"إظهار التلميح"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-as/strings.xml b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
index 35b467e..4b34266 100644
--- a/compose/material3/material3/src/androidMain/res/values-as/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"পৰিসৰৰ ভিতৰত আছে"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"তাৰিখ দিয়ক"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"অমান্য তাৰিখৰ পৰিসৰৰ ইনপুট"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ড্ৰেগ হেণ্ডেল"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"টুলটিপ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"টুলটিপ দেখুৱাওক"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-az/strings.xml b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
index 6f4bfcc..0ca1afe 100644
--- a/compose/material3/material3/src/androidMain/res/values-az/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Bu aralıqda"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Tarixləri daxil edin"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Yanlış tarix aralığı daxiletməsi"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Dəstəyi çəkin"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Alət izahı"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"İpucu göstərin"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
index d31e83f..0bc4a3a 100644
--- a/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-b+sr+Latn/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"U dometu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Unesite datume"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Unos opsega datuma je nevažeći"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Identifikator za prevlačenje"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Objašnjenje"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Prikaži objašnjenje"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-be/strings.xml b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
index 261e25f..6f983e8 100644
--- a/compose/material3/material3/src/androidMain/res/values-be/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"У зоне дасягальнасці"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Увядзіце даты"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Уведзены няправільны дыяпазон дат"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Маркер перацягвання"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Падказка"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Паказваць усплывальную падказку"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
index 966c78d..9159c1d 100644
--- a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"В диапазона"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Въведете дати"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Въведен е невалиден период от време"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Манипулатор за преместване с плъзгане"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Подсказка"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Показване на подсказка"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
index 607c6a0..6afcc01 100644
--- a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"সীমার মধ্যে"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"তারিখ লিখুন"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"তারিখের ব্যাপ্তি সম্পর্কিত ইনপুট ভুল দেওয়া আছে"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"টেনে আনার হ্যান্ডেল"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"টুলটিপ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"টুলটিপ দেখান"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
index dd13b17..d02537d 100644
--- a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"U dometu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Unesite datume"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Nevažeći unos raspona datuma"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Ručica za prevlačenje"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Skočni opis"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Prikaži skočni opis"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
index 3dd63ca..82bea54 100644
--- a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dins de l\'interval"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Introdueix les dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"S\'ha introduït un interval de dades no vàlid"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Ansa per arrossegar"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Descripció emergent"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostra la descripció emergent"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
index db084b3..69cd262 100644
--- a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"V rozsahu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Zadejte data"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Neplatné období"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Úchyt pro přetažení"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Popisek"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Zobrazit popisek"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-da/strings.xml b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
index ec0f863..5a2d660 100644
--- a/compose/material3/material3/src/androidMain/res/values-da/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Inden for de valgte dage"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Angiv datoer"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ugyldig angivelse af datainterval"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Håndtag"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Værktøjstip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Se værktøjstip"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-de/strings.xml b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
index 8167daa..1e0e158 100644
--- a/compose/material3/material3/src/androidMain/res/values-de/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
@@ -44,22 +44,22 @@
     <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Datum liegt außerhalb des erwarteten Jahresbereichs (%1$s–%2$s)"</string>
     <string name="date_picker_switch_to_calendar_mode" msgid="9029369254443419167">"In den Kalendereingabemodus wechseln"</string>
     <string name="date_picker_switch_to_input_mode" msgid="1496750567914156598">"In den Texteingabemodus wechseln"</string>
-    <!-- no translation found for date_picker_scroll_to_later_years (3226341140390493774) -->
-    <skip />
-    <!-- no translation found for date_picker_scroll_to_earlier_years (8911933542023210271) -->
-    <skip />
+    <string name="date_picker_scroll_to_later_years" msgid="3226341140390493774">"Zum Ansehen nachfolgender Jahre scrollen"</string>
+    <string name="date_picker_scroll_to_earlier_years" msgid="8911933542023210271">"Zum Ansehen vorheriger Jahre scrollen"</string>
     <string name="date_range_picker_title" msgid="690080476639943577">"Daten auswählen"</string>
     <string name="date_range_picker_start_headline" msgid="5759491386723090559">"Startdatum"</string>
     <string name="date_range_picker_end_headline" msgid="4766270708882012148">"Enddatum"</string>
-    <!-- no translation found for date_range_picker_scroll_to_next_month (51495506931835470) -->
-    <skip />
-    <!-- no translation found for date_range_picker_scroll_to_previous_month (4371570854614540700) -->
-    <skip />
+    <string name="date_range_picker_scroll_to_next_month" msgid="51495506931835470">"Zum Ansehen des nächsten Monats scrollen"</string>
+    <string name="date_range_picker_scroll_to_previous_month" msgid="4371570854614540700">"Zum Ansehen des vorherigen Monats scrollen"</string>
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Im Zeitraum"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Daten eingeben"</string>
-    <!-- no translation found for date_range_input_invalid_range_input (1891592555781755601) -->
+    <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Angegebener Zeitraum ungültig"</string>
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Ziehpunkt"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
     <skip />
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Kurzinfo"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Kurzinfo anzeigen"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-el/strings.xml b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
index f8617d1..97b435b 100644
--- a/compose/material3/material3/src/androidMain/res/values-el/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Εντός εύρους"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Εισαγωγή ημερομηνιών"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Μη έγκυρη εισαγωγή εύρους ημερομηνιών"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Λαβή μεταφοράς"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Επεξήγηση εργαλείου"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Προβολή επεξήγησης"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
index 147bc0c..7a7e021 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rAU/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"In range"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Enter dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Invalid date range input"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Drag handle"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Collapse bottom sheet"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dismiss bottom sheet"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Expand bottom sheet"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
index b5bcf94..af4dc76 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rCA/strings.xml
@@ -55,6 +55,9 @@
     <string name="date_range_input_title" msgid="2366412111888449406">"Enter dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Invalid date range input"</string>
     <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Drag handle"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Collapse bottom sheet"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dismiss bottom sheet"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Expand bottom sheet"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
index 147bc0c..7a7e021 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rGB/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"In range"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Enter dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Invalid date range input"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Drag handle"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Collapse bottom sheet"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dismiss bottom sheet"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Expand bottom sheet"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
index 147bc0c..7a7e021 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rIN/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"In range"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Enter dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Invalid date range input"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Drag handle"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Collapse bottom sheet"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dismiss bottom sheet"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Expand bottom sheet"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
index e666b89..0d341cb7 100644
--- a/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-en-rXC/strings.xml
@@ -55,6 +55,9 @@
     <string name="date_range_input_title" msgid="2366412111888449406">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‎‎‏‎‏‏‏‏‎‏‎‎‎‏‏‏‎‎‏‎‏‎‎‏‎‏‎‎‏‎‏‎‏‏‏‏‏‏‏‏‎‏‏‏‏‏‏‎‎Enter dates‎‏‎‎‏‎"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‎‏‎‎‎‎‎‎‎‏‎‎‏‎‎‏‎‏‏‎‏‎‎‎‎‏‎‎‏‎‏‏‎‎‏‎‎‏‎‏‎‎‎‏‎‎‏‎‏‏‎‏‎‎‎‏‎Invalid date range input‎‏‎‎‏‎"</string>
     <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‎‏‎‏‏‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‎‎‏‎‎‏‏‏‎‏‎‏‏‏‏‎‎‎‎‏‏‎‎‏‎‎‏‏‏‎‏‏‏‎‏‏‎‎‎Drag handle‎‏‎‎‏‎"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‏‎‏‏‏‎‎‏‏‏‎‎‎‎‎‎‎‎‎‎‏‎‎‎‎‎‏‎‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‎‎Collapse bottom sheet‎‏‎‎‏‎"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‏‏‏‏‎‎‏‎‏‎‎‏‎‏‎‏‎‏‎‏‎‎‎‎‏‏‏‎‎‎‎‏‏‎‎‏‎‎‎‎‎‎‎‎‏‎‎‏‏‎‎‎‎Dismiss bottom sheet‎‏‎‎‏‎"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‏‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‎‏‎‏‎‏‎‎‎‎‏‏‎‏‎‎‎‏‏‏‎‏‏‎‏‎Expand bottom sheet‎‏‎‎‏‎"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‏‏‎‏‎‏‏‎‏‎‎‏‎‎‎‎‏‎‎‏‏‎‏‎‎‎‏‎‎‏‎‎‎‏‏‏‏‏‎‎‏‎‎‏‎‎‏‎‏‎‏‏‎‎‏‎‏‎Tooltip‎‏‎‎‏‎"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎Show tooltip‎‏‎‎‏‎"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‎‏‏‏‏‏‎‏‏‏‏‏‏‎‎‎‎‏‎‎‏‏‏‎‏‏‎‎‎‎‎‏‏‏‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‏‎‏‎‏‎‎‏‎‎PM‎‏‎‎‏‎"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
index 3d70f7f..98f1ffc 100644
--- a/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es-rUS/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"En el rango"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Ingresar fechas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Se introdujo un período no válido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Controlador de arrastre"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Información"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar información"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-es/strings.xml b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
index 60b4e3e..48f642b 100644
--- a/compose/material3/material3/src/androidMain/res/values-es/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dentro del intervalo"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Introducir fechas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"El intervalo de fechas no es válido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Controlador de arrastre"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Descripción emergente"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar descripción emergente"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-et/strings.xml b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
index f90404a..1a05d680 100644
--- a/compose/material3/material3/src/androidMain/res/values-et/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Vahemikus"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Sisestage kuupäevad"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Sisestati sobimatu kuupäevavahemik"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Lohistamispide"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Kohtspikker"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Kuva kohtspikker"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
index ff74867..43d79354 100644
--- a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
@@ -51,14 +51,17 @@
     <string name="date_range_picker_end_headline" msgid="4766270708882012148">"Amaiera-data"</string>
     <string name="date_range_picker_scroll_to_next_month" msgid="51495506931835470">"Egin gora/behera hurrengo hilabetea erakusteko"</string>
     <string name="date_range_picker_scroll_to_previous_month" msgid="4371570854614540700">"Egin gora/behera aurreko hilabetea erakusteko"</string>
-    <!-- no translation found for date_range_picker_day_in_range (9048690781645835833) -->
-    <skip />
+    <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Tartean"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Idatzi datak"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Idatzitako data tarteak ez du balio"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Arrastatzeko kontrol-puntua"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
     <skip />
-    <!-- no translation found for tooltip_pane_description (8191239805703103845) -->
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
     <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
+    <skip />
+    <string name="tooltip_pane_description" msgid="8191239805703103845">"Aholkua"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Erakutsi aholkua"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
     <string name="time_picker_am" msgid="5096144640014509074">"AM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
index d259b3e..0ba4c72 100644
--- a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"در محدوده"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"تاریخ‌ها را وارد کنید"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"محدوده تاریخ واردشده نامعتبر است"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"دستگیره کشاندن"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"نکته‌ابزار"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"نمایش نکته‌ابزار"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
index cbd5551..efea19e 100644
--- a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Valitulla välillä"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Lisää päivämäärät"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Virheellinen ajanjakso"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Vetokahva"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Vihjeteksti"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Näytä vihjeteksti"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
index 249e214..2ce849d 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr-rCA/strings.xml
@@ -44,26 +44,24 @@
     <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Date non comprise dans la fourchette prévue des années %1$s à %2$s"</string>
     <string name="date_picker_switch_to_calendar_mode" msgid="9029369254443419167">"Passer au mode d\'entrée de l\'Agenda"</string>
     <string name="date_picker_switch_to_input_mode" msgid="1496750567914156598">"Passer au mode d\'entrée de texte"</string>
-    <!-- no translation found for date_picker_scroll_to_later_years (3226341140390493774) -->
-    <skip />
-    <!-- no translation found for date_picker_scroll_to_earlier_years (8911933542023210271) -->
-    <skip />
+    <string name="date_picker_scroll_to_later_years" msgid="3226341140390493774">"Faites défiler pour afficher les années suivantes"</string>
+    <string name="date_picker_scroll_to_earlier_years" msgid="8911933542023210271">"Faites défiler pour afficher les années précédentes"</string>
     <string name="date_range_picker_title" msgid="690080476639943577">"Sélectionner les dates"</string>
     <string name="date_range_picker_start_headline" msgid="5759491386723090559">"Date de début"</string>
     <string name="date_range_picker_end_headline" msgid="4766270708882012148">"Date de fin"</string>
-    <!-- no translation found for date_range_picker_scroll_to_next_month (51495506931835470) -->
-    <skip />
-    <!-- no translation found for date_range_picker_scroll_to_previous_month (4371570854614540700) -->
-    <skip />
-    <!-- no translation found for date_range_picker_day_in_range (9048690781645835833) -->
-    <skip />
+    <string name="date_range_picker_scroll_to_next_month" msgid="51495506931835470">"Faites défiler pour afficher le mois suivant"</string>
+    <string name="date_range_picker_scroll_to_previous_month" msgid="4371570854614540700">"Faites défiler pour afficher le mois précédent"</string>
+    <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"À portée"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Entrer les dates"</string>
-    <!-- no translation found for date_range_input_invalid_range_input (1891592555781755601) -->
+    <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Entrée de période incorrecte"</string>
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Poignée de déplacement"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
     <skip />
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
     <skip />
-    <!-- no translation found for tooltip_pane_description (8191239805703103845) -->
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
+    <string name="tooltip_pane_description" msgid="8191239805703103845">"Infobulle"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Afficher une infobulle"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
     <string name="time_picker_am" msgid="5096144640014509074">"AM"</string>
@@ -73,8 +71,6 @@
     <string name="time_picker_hour_suffix" msgid="6952032626122080528">"%1$d h"</string>
     <string name="time_picker_hour_24h_suffix" msgid="4149641012513526783">"%1$d h"</string>
     <string name="time_picker_minute_suffix" msgid="3206486707779478173">"%1$d minutes"</string>
-    <!-- no translation found for time_picker_minute (6116528647594005945) -->
-    <skip />
-    <!-- no translation found for time_picker_hour (7241191970823415723) -->
-    <skip />
+    <string name="time_picker_minute" msgid="6116528647594005945">"Minute"</string>
+    <string name="time_picker_hour" msgid="7241191970823415723">"Heure"</string>
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
index 2263aa9..5f7e380 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dans la plage"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Saisir des dates"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Plage de dates non valide"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Poignée de déplacement"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Info-bulle"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Afficher l\'info-bulle"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
index 380ace64..a22ec51 100644
--- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dentro do intervalo"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Indica as datas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Indicouse un intervalo de datas que non é válido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Controlador de arrastre"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Cadro de información"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar cadro de información"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
index 2d0e4e5..b45aabf 100644
--- a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"રેન્જમાં છે"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"તારીખો દાખલ કરો"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"તારીખની શ્રેણીનું અમાન્ય ઇનપુટ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ઑબ્જેક્ટ ખેંચવાનું હૅન્ડલ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ટૂલટિપ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ટૂલટિપ બતાવો"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
index c33262a..35d585e 100644
--- a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
@@ -55,6 +55,12 @@
     <string name="date_range_input_title" msgid="2366412111888449406">"तारीखें डालें"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"तारीख की दी गई सीमा गलत है"</string>
     <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"खींचकर छोड़ने वाला हैंडल"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
+    <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"टूलटिप"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"टूलटिप देखें"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
index 393d8c6..2f9c3cb 100644
--- a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"U dometu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Unos datuma"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Unos datumskog raspona nije važeći"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Marker za povlačenje"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Opis"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Prikaži opis"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
index 8648239..500c866 100644
--- a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Hatókörön belül"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Dátumok megadása"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Érvénytelen a megadott dátum"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Fogópont"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Elemleírás"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Elemleírás megjelenítése"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
index 6fd2eac..50d319f 100644
--- a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Միջակայքում"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Մուտքագրեք ամսաթվերը"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Մուտքագրված ամսաթվերի միջակայքն անվավեր է"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Տեղափոխման նշիչ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Հուշակ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Ցուցադրել հուշում"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-in/strings.xml b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
index 321c7b7..cf6645c 100644
--- a/compose/material3/material3/src/androidMain/res/values-in/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dalam rentang"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Masukkan tanggal"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Input rentang tanggal tidak valid"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Handel geser"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Tampilkan tooltip"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-is/strings.xml b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
index fe71264..f090921 100644
--- a/compose/material3/material3/src/androidMain/res/values-is/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Innan tímabils"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Sláðu inn dagsetningar"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ógilt tímabil fært inn"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Dragkló"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Ábending"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Sýna ábendingu"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-it/strings.xml b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
index c76514c..a841077 100644
--- a/compose/material3/material3/src/androidMain/res/values-it/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Nell\'intervallo"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Inserisci date"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Intervallo di date inserito non valido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Punto di trascinamento"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Comprimi il riquadro inferiore"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Chiudi il riquadro inferiore"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Espandi il riquadro inferiore"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Descrizione comando"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostra descrizione comando"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
index 40df3ba..095cd13 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"בטווח"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"הזנת תאריכים"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"קלט טווח תאריכים לא חוקי"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"נקודת אחיזה לגרירה"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"הסבר קצר"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"הצגת הסבר קצר"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
index 4417dbb..750d905 100644
--- a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"範囲内"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"日付の入力"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"入力された期間は無効です"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ドラッグ ハンドル"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ツールチップ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ツールチップを表示します"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
index 5ed0a48..f59ba7d 100644
--- a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"არეალშია"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"თარიღების შეყვანა"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"შეყვანილია თარიღების არასწორი დიაპაზონი"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"სახელური ჩავლებისთვის"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"მინიშნება"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"მინიშნების ჩვენება"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
index 4cddab1..0a65d47 100644
--- a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Күндер аралығында"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Күндерді енгізіңіз"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Жарамсыз күндер аралығы енгізілген."</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Сүйрейтін тетік"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Қалқыма көмек"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Қалқыма көмекті көрсету"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-km/strings.xml b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
index d8c65bc..b247f86 100644
--- a/compose/material3/material3/src/androidMain/res/values-km/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"ក្នុងចន្លោះ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"បញ្ចូល​កាលបរិច្ឆេទ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ការបញ្ចូលចន្លោះកាលបរិច្ឆេទមិនត្រឹមត្រូវ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ដង​អូស"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"កំណត់​ពន្យល់"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"បង្ហាញ​កំណត់​ពន្យល់"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
index 2b30d81..eaf20df 100644
--- a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"ವ್ಯಾಪ್ತಿಯಲ್ಲಿದೆ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ದಿನಾಂಕಗಳನ್ನು ನಮೂದಿಸಿ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ದಿನಾಂಕ ವ್ಯಾಪ್ತಿಯ ಇನ್‌ಪುಟ್ ಅಮಾನ್ಯವಾಗಿದೆ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ಹ್ಯಾಂಡಲ್ ಡ್ರ್ಯಾಗ್ ಮಾಡಿ"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"ಕೆಳಭಾಗದ ಶೀಟ್ ಅನ್ನು ಕುಗ್ಗಿಸಿ"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"ಕೆಳಭಾಗದ ಶೀಟ್ ಅನ್ನು ವಜಾಗೊಳಿಸಿ"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"ಕೆಳಭಾಗದ ಶೀಟ್ ಅನ್ನು ವಿಸ್ತರಿಸಿ"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ಟೂಲ್‌ಟಿಪ್"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ಟೂಲ್‌ಟಿಪ್ ಅನ್ನು ತೋರಿಸಿ"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
index aa5bb7e..24cc091 100644
--- a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"범위 내"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"날짜 입력"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"잘못된 기간 입력"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"드래그 핸들"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"도움말"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"도움말 표시"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
index feee611..d9e1152 100644
--- a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Төмөнкү убакыт аралыгындагы күн"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Күндөрдү киргизүү"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Даталар диапазону туура эмес тандалды"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Тизменин керектүү жерине сүйрөп баруу"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Калкып чыгуучу кеңеш"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Калкып чыгуучу кеңешти көрсөтүү"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
index c169535..ebb96d9 100644
--- a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"ຢູ່ໃນໄລຍະ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ໃສ່ວັນທີ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ອິນພຸດໄລຍະວັນທີບໍ່ຖືກຕ້ອງ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ບ່ອນຈັບລາກ"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"ຫຍໍ້ຊີດລຸ່ມສຸດລົງ"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"ປິດຊີດລຸ່ມສຸດໄວ້"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"ຂະຫຍາຍຊີດລຸ່ມສຸດ"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ຄຳແນະນຳ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ສະແດງຄຳແນະນຳ"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"ຫຼັງທ່ຽງ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
index 72b09a68..2f2b031 100644
--- a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Diapazone"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Įvesti datas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Netinkama dienų sekos įvestis"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Vilkimo rankenėlė"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Patarimas"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Rodyti patarimą"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
index c069a37..288096b 100644
--- a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Atlasītajā diapazonā"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Ievadiet datumus"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ievadīts nederīgs datumu diapazons."</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Vilkšanas turis"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Rīka padoms"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Rādīt rīka padomu"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
index ddc48bc..634be65 100644
--- a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Во опсег"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Внесете датуми"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Внесовте неважечки временски период"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Рачка за влечење"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Собери го долниот лист"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Отфрли го долниот лист"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Прошири го долниот лист"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Совет за алатка"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Прикажи совет за алатка"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"попладне"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
index 49110c7..cf41cc2 100644
--- a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"പരിധിയിൽ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"തീയതികൾ നൽകുക"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"തീയതി ശ്രേണി ഇൻപുട്ട് അസാധുവാണ്"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"വലിച്ചിടുന്നതിനുള്ള ഹാൻഡിൽ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ടൂൾടിപ്പ്"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ടൂൾടിപ്പ് കാണിക്കുക"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
index 4ec2056..a55fdf6 100644
--- a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Хүрээнд байгаа"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Огноо оруулах"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Хугацааны интервалын оролт буруу байна"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Чирэх бариул"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Зөвлөмж"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Зөвлөмж харуулах"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
index 2135340..7dd8357 100644
--- a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"रेंजमध्ये"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"तारखा एंटर करा"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"तारीख रेंजचे इनपुट चुकीचे आहे"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ड्रॅग हॅंडल"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"टूलटिप"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"टूलटिप दाखवा"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
index 9ad815e..9dcc19d 100644
--- a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dalam liputan"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Masukkan tarikh"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Input julat tarikh tidak sah"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Pemegang seret"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tip alat"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Tunjukkan tip alat"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-my/strings.xml b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
index 2b48e0e..09f9c67 100644
--- a/compose/material3/material3/src/androidMain/res/values-my/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"အပိုင်းအခြားအတွင်း"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ရက်စွဲများထည့်ပါ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ဒေတာအပိုင်းအခြား ထည့်သွင်းမှု မမှန်ပါ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ဖိဆွဲအထိန်း"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"အကြံပြုချက်ပြ ပေါ့အပ် ဝင်းဒိုး"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"အကြံပြုချက်ပြ ပေါ့အပ်ဝင်းဒိုး ပြရန်"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
index e334098..3ba360f 100644
--- a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Innen rekkevidde"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Legg inn datoer"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"En ugyldig dataperiode er skrevet inn"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Håndtak"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Verktøytips"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Vis verktøytips"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
index fd92a85..9abf195 100644
--- a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"चयन गरिएका मितिभित्र पर्ने"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"मितिहरू हाल्नुहोस्"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"मितिको अवैध दायरा तोकियो"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ड्र्याग ह्यान्डल"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"टुलटिप"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"टुलटिप देखाइयोस्"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
index 46d9069..2ecf2a7 100644
--- a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Binnen bereik"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Datums opgeven"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ongeldige invoer voor periode"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Handgreep voor slepen"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Tooltip tonen"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-or/strings.xml b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
index d731fbb..fc09715 100644
--- a/compose/material3/material3/src/androidMain/res/values-or/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"ରେଞ୍ଜରେ ଅଛି"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ତାରିଖଗୁଡ଼ିକ ଲେଖନ୍ତୁ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ଅବୈଧ ତାରିଖ ରେଞ୍ଜ ଇନପୁଟ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ଡ୍ରାଗ ହେଣ୍ଡେଲ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ଟୁଲଟିପ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ଟୁଲଟିପ ଦେଖାନ୍ତୁ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
index 1069d5d..f69dd51 100644
--- a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"ਰੇਂਜ ਵਿੱਚ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ਤਾਰੀਖਾਂ ਦਾਖਲ ਕਰੋ"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"ਇਨਪੁੱਟ ਕੀਤੀ ਗਈ ਤਾਰੀਖ ਦੀ ਰੇਂਜ ਅਵੈਧ ਹੈ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ਘਸੀਟਣ ਵਾਲਾ ਹੈਂਡਲ"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ਟੂਲ-ਟਿੱਪ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ਟੂਲ-ਟਿੱਪ ਦਿਖਾਓ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
index 05f450f..2ecf3ea 100644
--- a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"W zasięgu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Wprowadź daty"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Nieprawidłowy zakres dat"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Uchwyt do przeciągania"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Etykietka"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Pokaż etykietkę"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
index 7ac872d..1ebeb8e 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rBR/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dentro do alcance"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Informar datas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Período inválido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Alça de arrastar"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Fechar página inferior"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dispensar página inferior"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Abrir página inferior"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Dica"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar dica"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
index 24233ed9..ce146b4 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt-rPT/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dentro do alcance"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Introduza as datas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Entrada do intervalo de datas inválida"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Indicador para arrastar"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Reduza a secção inferior"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Ignore a secção inferior"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Expanda a secção inferior"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Sugestão"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar sugestão"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
index 7ac872d..1ebeb8e 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
@@ -54,8 +54,10 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Dentro do alcance"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Informar datas"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Período inválido"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
-    <skip />
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Alça de arrastar"</string>
+    <string name="bottom_sheet_collapse_description" msgid="6128938260108474660">"Fechar página inferior"</string>
+    <string name="bottom_sheet_dismiss_description" msgid="1918297411568599192">"Dispensar página inferior"</string>
+    <string name="bottom_sheet_expand_description" msgid="4324434199045499117">"Abrir página inferior"</string>
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Dica"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostrar dica"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"PM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
index d4042a0..1bbe278 100644
--- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"În interval"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Introdu datele"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Intervalul de date introdus nu este valid"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Ghidaj de tragere"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Balon explicativ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Afișează balonul explicativ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
index 95900d3..64e4e01 100644
--- a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"День в диапазоне дат"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Введите даты"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Указан недопустимый диапазон дат."</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Маркер перемещения"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Подсказка"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Показать подсказку"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-si/strings.xml b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
index 092a741..b0b144f 100644
--- a/compose/material3/material3/src/androidMain/res/values-si/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"පරාසය තුළ"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"දින ඇතුළු කරන්න"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"අවලංගු දින පරාස ආදානය"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"ඇදීම් හැඬලය"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"මෙවලම් ඉඟිය"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"මෙවලම් ඉඟිය පෙන්වන්න"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
index 3a809d2e..a9adb7e 100644
--- a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"V rozsahu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Zadajte dátumy"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Neplatný vstup obdobia"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Presúvadlo"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Popis"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Zobraziť opis"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
index 6ee8ab0..3bbf720 100644
--- a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Znotraj obdobja"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Vnesite datume"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Neveljaven vnos obdobja."</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Ročica za vlečenje"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Opis orodja"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Pokaži opis orodja"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
index 10e79d7..5c58dff 100644
--- a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Brenda rrezes"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Fut datat"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Hyrje e pavlefshme e diapazonit të datave"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Doreza e zvarritjes"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Këshilla për veglën"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Shfaq këshillat për veglën"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
index 7216e6c..c253b45 100644
--- a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"У домету"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Унесите датуме"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Унос опсега датума је неважећи"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Идентификатор за превлачење"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Објашњење"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Прикажи објашњење"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
index fcbb2ec..8613182 100644
--- a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
@@ -56,6 +56,12 @@
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Ett ogiltigt datumintervall har angetts"</string>
     <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
     <skip />
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
+    <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Beskrivning"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Visa beskrivning"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"EM"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
index f66a54e..e38417d 100644
--- a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Ipo karibu"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Weka tarehe"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Kipindi kilichowekwa si sahihi"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Aikoni ya buruta"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Kidirisha cha vidokezo"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Onyesha kidirisha cha vidokezo"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
index abd1916..0580cfe 100644
--- a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"வரம்பிற்குள் உள்ளது"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"தேதிகளை உள்ளிடுங்கள்"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"தவறான தேதி வரம்பை உள்ளிட்டுள்ளீர்கள்"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"இழுப்பதற்கான ஹேண்டில்"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"உதவிக்குறிப்பு"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"உதவிக்குறிப்பைக் காட்டு"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-te/strings.xml b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
index 00060fa..9cfd074 100644
--- a/compose/material3/material3/src/androidMain/res/values-te/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"పరిధిలో ఉంది"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"తేదీలను ఎంటర్ చేయండి"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"తేదీల పరిధి ఇన్‌పుట్ చెల్లదు"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"లాగే హ్యాండిల్"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"టూల్‌టిప్"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"టూల్‌టిప్‌ను చూపించు"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-th/strings.xml b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
index 28e0c18..1c1e1222d8 100644
--- a/compose/material3/material3/src/androidMain/res/values-th/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"อยู่ในช่วงวันที่ที่เลือก"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"ป้อนวันที่"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"การป้อนข้อมูลช่วงวันที่ไม่ถูกต้อง"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"แฮนเดิลการลาก"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"เคล็ดลับเครื่องมือ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"แสดงเคล็ดลับเครื่องมือ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
index 2902ef8..4ec31b1 100644
--- a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"May signal"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Maglagay ng mga petsa"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Invalid ang input na hanay ng petsa"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Handle sa pag-drag"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Tooltip"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Ipakita ang tooltip"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
index 5d2bc8a..61bc807 100644
--- a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Aralıkta"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Tarihleri girin"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Geçersiz tarih aralığı girişi"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Sürükleme tutamacı"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"İpucu"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Araç ipucunu göster"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
index 22cee08..433711fa 100644
--- a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"У діапазоні"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Введіть дати"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Указано недійсний діапазон дат"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Маркер переміщення"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Спливаюча підказка"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Показати підказку"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
index a4a8497..045d61d 100644
--- a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"رینج میں ہے"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"تواریخ درج کریں"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"تاریخ کی حد کا غلط ان پٹ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"گھسیٹنے کا ہینڈل"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"ٹول ٹپ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"ٹول ٹپ دکھائیں"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
index f7a99a5..2ffa0a5 100644
--- a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
@@ -55,6 +55,12 @@
     <string name="date_range_input_title" msgid="2366412111888449406">"Sanalarni kiriting"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Kiritilgan muddat yaroqsiz"</string>
     <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Surish dastagi"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
+    <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Maslahat oynasi"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Maslahat oynasini koʻrsatish"</string>
     <string name="time_picker_pm" msgid="2232702812657998674">"TK"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
index 961db550..2f65eb9 100644
--- a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Trong khoảng"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Nhập ngày"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Phạm vi ngày đã nhập không hợp lệ"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Nút kéo"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Chú giải công cụ"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Hiển thị chú giải công cụ"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
index 829e14e..30ebb15 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rCN/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"在范围内"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"输入日期"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"输入的日期范围无效"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"拖动手柄"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"提示"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"显示提示"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
index b96bf87..e906fe1c 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rHK/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"在指定日期範圍內"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"輸入日期"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"輸入的日期範圍無效"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"拖曳控點"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"提示"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"顯示提示"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
index dc3e203..c7fb463 100644
--- a/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zh-rTW/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"在指定日期範圍內"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"輸入日期"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"輸入的日期範圍無效"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"拖曳控點"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"工具提示"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"顯示工具提示"</string>
diff --git a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
index 1cf0d20..c3c5857 100644
--- a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
@@ -54,7 +54,12 @@
     <string name="date_range_picker_day_in_range" msgid="9048690781645835833">"Kubanga"</string>
     <string name="date_range_input_title" msgid="2366412111888449406">"Faka izinsuku"</string>
     <string name="date_range_input_invalid_range_input" msgid="1891592555781755601">"Okokufaka kwebanga losuku okungavumelekile"</string>
-    <!-- no translation found for bottom_sheet_drag_handle_description (7772321844937772780) -->
+    <string name="bottom_sheet_drag_handle_description" msgid="7772321844937772780">"Hudula isibambi"</string>
+    <!-- no translation found for bottom_sheet_collapse_description (6128938260108474660) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_dismiss_description (1918297411568599192) -->
+    <skip />
+    <!-- no translation found for bottom_sheet_expand_description (4324434199045499117) -->
     <skip />
     <string name="tooltip_pane_description" msgid="8191239805703103845">"Ithulithiphu"</string>
     <string name="tooltip_long_press_label" msgid="2732804537909054941">"Bonisa ithulithiphu"</string>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
index da9bf4d..a434b7e 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/BottomSheetScaffold.kt
@@ -74,6 +74,7 @@
  * children. Defaults to the matching content color for [sheetContainerColor], or if that is
  * not a color from the theme, this will keep the same content color set above the bottom sheet.
  * @param sheetTonalElevation the tonal elevation of the bottom sheet
+ * @param sheetShadowElevation the shadow elevation of the bottom sheet
  * @param sheetDragHandle optional visual marker to pull the scaffold's bottom sheet
  * @param sheetSwipeEnabled whether the sheet swiping is enabled and should react to the user's
  * input
@@ -101,6 +102,7 @@
     sheetContainerColor: Color = BottomSheetDefaults.ContainerColor,
     sheetContentColor: Color = contentColorFor(sheetContainerColor),
     sheetTonalElevation: Dp = BottomSheetDefaults.Elevation,
+    sheetShadowElevation: Dp = BottomSheetDefaults.Elevation,
     sheetDragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
     sheetSwipeEnabled: Boolean = true,
     topBar: @Composable (() -> Unit)? = null,
@@ -129,6 +131,7 @@
                     containerColor = sheetContainerColor,
                     contentColor = sheetContentColor,
                     tonalElevation = sheetTonalElevation,
+                    shadowElevation = sheetShadowElevation,
                     dragHandle = sheetDragHandle,
                     content = sheetContent
                 )
@@ -196,6 +199,7 @@
     containerColor: Color,
     contentColor: Color,
     tonalElevation: Dp,
+    shadowElevation: Dp,
     dragHandle: @Composable (() -> Unit)?,
     content: @Composable ColumnScope.() -> Unit
 ) {
@@ -257,7 +261,8 @@
         shape = shape,
         color = containerColor,
         contentColor = contentColor,
-        tonalElevation = tonalElevation
+        tonalElevation = tonalElevation,
+        shadowElevation = shadowElevation,
     ) {
         Column(Modifier.fillMaxWidth()) {
             if (dragHandle != null) {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
index 3e50b3a..47e1162 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DatePicker.kt
@@ -35,7 +35,6 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.Row
-import androidx.compose.foundation.layout.RowScope
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.defaultMinSize
 import androidx.compose.foundation.layout.fillMaxWidth
@@ -153,11 +152,17 @@
     modifier: Modifier = Modifier,
     dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() },
     dateValidator: (Long) -> Boolean = { true },
-    title: (@Composable () -> Unit)? = { DatePickerDefaults.DatePickerTitle(state) },
-    headline: @Composable () -> Unit = {
+    title: (@Composable () -> Unit)? = {
+        DatePickerDefaults.DatePickerTitle(
+            state,
+            modifier = Modifier.padding(DatePickerTitlePadding)
+        )
+    },
+    headline: (@Composable () -> Unit)? = {
         DatePickerDefaults.DatePickerHeadline(
             state,
-            dateFormatter
+            dateFormatter,
+            modifier = Modifier.padding(DatePickerHeadlinePadding)
         )
     },
     showModeToggle: Boolean = true,
@@ -170,6 +175,7 @@
         modeToggleButton = if (showModeToggle) {
             {
                 DisplayModeToggleButton(
+                    modifier = Modifier.padding(DatePickerModeTogglePadding),
                     displayMode = state.displayMode,
                     onDisplayModeChange = { displayMode ->
                         state.stateData.switchDisplayMode(
@@ -185,7 +191,6 @@
             DatePickerModalTokens.HeaderHeadlineFont
         ),
         headerMinHeight = DatePickerModalTokens.HeaderContainerHeight,
-        headerContentPadding = DatePickerHeaderPadding,
         colors = colors
     ) {
         SwitchableDateEntryContent(
@@ -1006,12 +1011,11 @@
 internal fun DateEntryContainer(
     modifier: Modifier,
     title: (@Composable () -> Unit)?,
-    headline: @Composable () -> Unit,
+    headline: (@Composable () -> Unit)?,
     modeToggleButton: (@Composable () -> Unit)?,
     colors: DatePickerColors,
     headlineTextStyle: TextStyle,
     headerMinHeight: Dp,
-    headerContentPadding: PaddingValues,
     content: @Composable () -> Unit
 ) {
     Column(
@@ -1020,17 +1024,34 @@
             .semantics { isContainer = true }
     ) {
         DatePickerHeader(
-            modifier = Modifier.padding(DatePickerHorizontalPadding),
+            modifier = Modifier,
             title = title,
             titleContentColor = colors.titleContentColor,
             headlineContentColor = colors.headlineContentColor,
-            minHeight = headerMinHeight,
-            contentPadding = headerContentPadding
+            minHeight = headerMinHeight
         ) {
-            ProvideTextStyle(value = headlineTextStyle, content = headline)
-            modeToggleButton?.invoke()
+            Column(modifier = Modifier.fillMaxWidth()) {
+                val horizontalArrangement = when {
+                    headline != null && modeToggleButton != null -> Arrangement.SpaceBetween
+                    headline != null -> Arrangement.Start
+                    else -> Arrangement.End
+                }
+                Row(
+                    modifier = Modifier.fillMaxWidth(),
+                    horizontalArrangement = horizontalArrangement,
+                    verticalAlignment = Alignment.CenterVertically
+                ) {
+                    if (headline != null) {
+                        ProvideTextStyle(value = headlineTextStyle, content = headline)
+                    }
+                    modeToggleButton?.invoke()
+                }
+                // Display a divider only when there is a title, headline, or a mode toggle.
+                if (title != null || headline != null || modeToggleButton != null) {
+                    Divider()
+                }
+            }
         }
-        Divider()
         content()
     }
 }
@@ -1038,20 +1059,19 @@
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 internal fun DisplayModeToggleButton(
+    modifier: Modifier,
     displayMode: DisplayMode,
     onDisplayModeChange: (DisplayMode) -> Unit
 ) {
     if (displayMode == DisplayMode.Picker) {
-        IconButton(onClick = { onDisplayModeChange(DisplayMode.Input) }) {
+        IconButton(onClick = { onDisplayModeChange(DisplayMode.Input) }, modifier = modifier) {
             Icon(
                 imageVector = Icons.Filled.Edit,
                 contentDescription = getString(Strings.DatePickerSwitchToInputMode)
             )
         }
     } else {
-        IconButton(
-            onClick = { onDisplayModeChange(DisplayMode.Picker) }
-        ) {
+        IconButton(onClick = { onDisplayModeChange(DisplayMode.Picker) }, modifier = modifier) {
             Icon(
                 imageVector = Icons.Filled.DateRange,
                 contentDescription = getString(Strings.DatePickerSwitchToCalendarMode)
@@ -1116,7 +1136,7 @@
     val defaultLocale = defaultLocale()
     Column {
         MonthsNavigation(
-            modifier = Modifier.padding(DatePickerHorizontalPadding),
+            modifier = Modifier.padding(horizontal = DatePickerHorizontalPadding),
             nextAvailable = monthsListState.canScrollForward,
             previousAvailable = monthsListState.canScrollBackward,
             yearPickerVisible = yearPickerVisible,
@@ -1143,7 +1163,7 @@
         )
 
         Box {
-            Column(modifier = Modifier.padding(DatePickerHorizontalPadding)) {
+            Column(modifier = Modifier.padding(horizontal = DatePickerHorizontalPadding)) {
                 WeekDays(colors, stateData.calendarModel)
                 HorizontalMonthsList(
                     onDateSelected = onDateSelected,
@@ -1175,7 +1195,7 @@
                                 RecommendedSizeForAccessibility * (MaxCalendarRows + 1) -
                                     DividerDefaults.Thickness
                             )
-                            .padding(DatePickerHorizontalPadding),
+                            .padding(horizontal = DatePickerHorizontalPadding),
                         onYearSelected = { year ->
                             // Switch back to the monthly calendar and scroll to the selected year.
                             yearPickerVisible = !yearPickerVisible
@@ -1207,8 +1227,7 @@
     titleContentColor: Color,
     headlineContentColor: Color,
     minHeight: Dp,
-    contentPadding: PaddingValues,
-    content: @Composable RowScope.() -> Unit
+    content: @Composable () -> Unit
 ) {
     // Apply a defaultMinSize only when the title is not null.
     val heightModifier =
@@ -1220,8 +1239,7 @@
     Column(
         modifier
             .fillMaxWidth()
-            .then(heightModifier)
-            .padding(contentPadding),
+            .then(heightModifier),
         verticalArrangement = Arrangement.SpaceBetween
     ) {
         if (title != null) {
@@ -1237,14 +1255,9 @@
                 }
             }
         }
-        CompositionLocalProvider(LocalContentColor provides headlineContentColor) {
-            Row(
-                modifier = Modifier.fillMaxWidth(),
-                horizontalArrangement = Arrangement.SpaceBetween,
-                verticalAlignment = Alignment.CenterVertically,
-                content = content
-            )
-        }
+        CompositionLocalProvider(
+            LocalContentColor provides headlineContentColor, content = content
+        )
     }
 }
 
@@ -1889,12 +1902,11 @@
 
 internal val RecommendedSizeForAccessibility = 48.dp
 internal val MonthYearHeight = 56.dp
-internal val DatePickerHorizontalPadding = PaddingValues(horizontal = 12.dp)
-private val DatePickerHeaderPadding = PaddingValues(
-    start = 12.dp,
-    top = 16.dp,
-    bottom = 12.dp
-)
+internal val DatePickerHorizontalPadding = 12.dp
+internal val DatePickerModeTogglePadding = PaddingValues(end = 12.dp, bottom = 12.dp)
+
+private val DatePickerTitlePadding = PaddingValues(start = 24.dp, end = 12.dp, top = 16.dp)
+private val DatePickerHeadlinePadding = PaddingValues(start = 24.dp, end = 12.dp, bottom = 12.dp)
 
 private val YearsVerticalPadding = 16.dp
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
index 58fa2a1..2962286 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
@@ -88,12 +88,16 @@
     dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() },
     dateValidator: (Long) -> Boolean = { true },
     title: (@Composable () -> Unit)? = {
-        DateRangePickerDefaults.DateRangePickerTitle(state = state)
+        DateRangePickerDefaults.DateRangePickerTitle(
+            state = state,
+            modifier = Modifier.padding(DateRangePickerTitlePadding)
+        )
     },
-    headline: @Composable () -> Unit = {
+    headline: (@Composable () -> Unit)? = {
         DateRangePickerDefaults.DateRangePickerHeadline(
             state,
-            dateFormatter
+            dateFormatter,
+            modifier = Modifier.padding(DateRangePickerHeadlinePadding)
         )
     },
     showModeToggle: Boolean = true,
@@ -106,6 +110,7 @@
         modeToggleButton = if (showModeToggle) {
             {
                 DisplayModeToggleButton(
+                    modifier = Modifier.padding(DatePickerModeTogglePadding),
                     displayMode = state.displayMode,
                     onDisplayModeChange = { displayMode ->
                         state.stateData.switchDisplayMode(
@@ -122,7 +127,6 @@
         ),
         headerMinHeight = DatePickerModalTokens.RangeSelectionHeaderContainerHeight -
             HeaderHeightOffset,
-        headerContentPadding = DateRangePickerHeaderPadding,
         colors = colors
     ) {
         SwitchableDateEntryContent(
@@ -294,23 +298,21 @@
      *
      * @param state a [DatePickerState] that will help determine the title's content
      * @param modifier a [Modifier] to be applied for the title
-     * @param contentPadding [PaddingValues] to be applied for the title
      */
     @Composable
     fun DateRangePickerTitle(
         state: DateRangePickerState,
-        modifier: Modifier = Modifier,
-        contentPadding: PaddingValues = PaddingValues(start = DateRangePickerHeaderStartPadding)
+        modifier: Modifier = Modifier
     ) {
         when (state.displayMode) {
             DisplayMode.Picker -> Text(
                 getString(string = Strings.DateRangePickerTitle),
-                modifier = modifier.padding(paddingValues = contentPadding)
+                modifier = modifier
             )
 
             DisplayMode.Input -> Text(
                 getString(string = Strings.DateRangeInputTitle),
-                modifier = modifier.padding(paddingValues = contentPadding)
+                modifier = modifier
             )
         }
     }
@@ -322,14 +324,12 @@
      * @param state a [DateRangePickerState] that will help determine the headline
      * @param dateFormatter a [DatePickerFormatter]
      * @param modifier a [Modifier] to be applied for the headline
-     * @param contentPadding [PaddingValues] to be applied for the headline row
      */
     @Composable
     fun DateRangePickerHeadline(
         state: DateRangePickerState,
         dateFormatter: DatePickerFormatter,
-        modifier: Modifier = Modifier,
-        contentPadding: PaddingValues = PaddingValues(start = DateRangePickerHeaderStartPadding)
+        modifier: Modifier = Modifier
     ) {
         val startDateText = getString(Strings.DateRangePickerStartHeadline)
         val endDateText = getString(Strings.DateRangePickerEndHeadline)
@@ -342,7 +342,6 @@
             startDatePlaceholder = { Text(text = startDateText) },
             endDatePlaceholder = { Text(text = endDateText) },
             datesDelimiter = { Text(text = "-") },
-            contentPadding = contentPadding
         )
     }
 
@@ -365,7 +364,6 @@
      * date (i.e a [Text] with an "End date" string)
      * @param datesDelimiter a composable to be displayed as a headline delimiter between the
      * start and the end dates
-     * @param contentPadding [PaddingValues] to be applied for the headline row
      */
     @Composable
     private fun DateRangePickerHeadline(
@@ -377,7 +375,6 @@
         startDatePlaceholder: @Composable () -> Unit,
         endDatePlaceholder: @Composable () -> Unit,
         datesDelimiter: @Composable () -> Unit,
-        contentPadding: PaddingValues
     ) {
         with(state.stateData) {
             val defaultLocale = defaultLocale()
@@ -419,12 +416,10 @@
             val endHeadlineDescription = "$endDateText: $verboseEndDateDescription"
 
             Row(
-                modifier = modifier
-                    .padding(paddingValues = contentPadding)
-                    .clearAndSetSemantics {
-                        liveRegion = LiveRegionMode.Polite
-                        contentDescription = "$startHeadlineDescription, $endHeadlineDescription"
-                    },
+                modifier = modifier.clearAndSetSemantics {
+                    liveRegion = LiveRegionMode.Polite
+                    contentDescription = "$startHeadlineDescription, $endHeadlineDescription"
+                },
                 verticalAlignment = Alignment.CenterVertically,
                 horizontalArrangement = Arrangement.spacedBy(4.dp),
             ) {
@@ -495,7 +490,7 @@
     val onDateSelected = { dateInMillis: Long ->
         updateDateSelection(stateData, dateInMillis)
     }
-    Column(modifier = Modifier.padding(DatePickerHorizontalPadding)) {
+    Column(modifier = Modifier.padding(horizontal = DatePickerHorizontalPadding)) {
         WeekDays(colors, stateData.calendarModel)
         VerticalMonthsList(
             onDateSelected = onDateSelected,
@@ -636,7 +631,8 @@
         /**
          * Calculates the selection coordinates within the current month's grid. The returned [Pair]
          * holds the actual item x & y coordinates within the LazyVerticalGrid, and is later used to
-         * calculate the exact offset for drawing the selection rectangles when in range-selection mode.
+         * calculate the exact offset for drawing the selection rectangles when in range-selection
+         * mode.
          */
         @OptIn(ExperimentalMaterial3Api::class)
         fun calculateRangeInfo(
@@ -802,16 +798,9 @@
     )
 }
 
-// Base header paddings that are used for the header part (title + headline). Note that for the
-// range picker default title and headline we add additional start padding. The additional paddings
-// are added there to allow more flexibility when those composables are provided by a developer.
-private val DateRangePickerHeaderPadding = PaddingValues(
-    start = 12.dp,
-    bottom = 12.dp
-)
-
-// Additional start padding for the default headline and title parts.
-private val DateRangePickerHeaderStartPadding = 40.dp
+private val DateRangePickerTitlePadding = PaddingValues(start = 64.dp, end = 12.dp)
+private val DateRangePickerHeadlinePadding =
+    PaddingValues(start = 64.dp, end = 12.dp, bottom = 12.dp)
 
 // An offset that is applied to the token value for the RangeSelectionHeaderContainerHeight. The
 // implementation does not render a "Save" and "X" buttons by default, so we don't take those into
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.kt
new file mode 100644
index 0000000..256aae5
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DefaultPlatformTextStyle.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.ui.text.PlatformTextStyle
+
+/**
+ * Returns Default [PlatformTextStyle].
+ */
+internal expect fun defaultPlatformTextStyle(): PlatformTextStyle?
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
index 939ade7..0f76f4d 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/OutlinedTextField.kt
@@ -23,7 +23,9 @@
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
@@ -61,6 +63,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
 import androidx.compose.ui.unit.offset
 import androidx.compose.ui.util.lerp
 import kotlin.math.max
@@ -562,6 +565,8 @@
                 Box(
                     Modifier
                         .layoutId(PrefixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = PrefixSuffixTextPadding)
                 ) {
                     prefix()
@@ -571,36 +576,51 @@
                 Box(
                     Modifier
                         .layoutId(SuffixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = PrefixSuffixTextPadding, end = endPadding)
                 ) {
                     suffix()
                 }
             }
 
-            val textPadding = Modifier.padding(
-                start = if (prefix == null) startPadding else 0.dp,
-                end = if (suffix == null) endPadding else 0.dp,
-            )
+            val textPadding = Modifier
+                .heightIn(min = MinTextLineHeight)
+                .wrapContentHeight()
+                .padding(
+                    start = if (prefix == null) startPadding else 0.dp,
+                    end = if (suffix == null) endPadding else 0.dp,
+                )
 
             if (placeholder != null) {
-                placeholder(Modifier.layoutId(PlaceholderId).then(textPadding))
+                placeholder(Modifier
+                    .layoutId(PlaceholderId)
+                    .then(textPadding))
             }
 
             Box(
-                modifier = Modifier.layoutId(TextFieldId).then(textPadding),
+                modifier = Modifier
+                    .layoutId(TextFieldId)
+                    .then(textPadding),
                 propagateMinConstraints = true
             ) {
                 textField()
             }
 
             if (label != null) {
-                Box(modifier = Modifier.layoutId(LabelId)) { label() }
+                Box(Modifier
+                    .heightIn(min = lerp(
+                        MinTextLineHeight, MinFocusedLabelLineHeight, animationProgress))
+                    .wrapContentHeight()
+                    .layoutId(LabelId)) { label() }
             }
 
             if (supporting != null) {
                 @OptIn(ExperimentalMaterial3Api::class)
                 Box(Modifier
                     .layoutId(SupportingId)
+                    .heightIn(min = MinSupportingTextLineHeight)
+                    .wrapContentHeight()
                     .padding(TextFieldDefaults.supportingTextPadding())
                 ) { supporting() }
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
index e1bbb61..f66216c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Shapes.kt
@@ -145,7 +145,9 @@
     return copy(bottomStart = CornerSize(0.0.dp), bottomEnd = CornerSize(0.0.dp))
 }
 
-/** Helper function for component shape tokens. Used to grab the top values of a shape parameter. */
+/**
+ * Helper function for component shape tokens. Used to grab the bottom values of a shape parameter.
+ */
 internal fun CornerBasedShape.bottom(): CornerBasedShape {
     return copy(topStart = CornerSize(0.0.dp), topEnd = CornerSize(0.0.dp))
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
index bfdc8a6..c418cf7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Text.kt
@@ -18,6 +18,7 @@
 
 import androidx.compose.foundation.text.BasicText
 import androidx.compose.foundation.text.InlineTextContent
+import androidx.compose.material3.tokens.DefaultTextStyle
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.compositionLocalOf
@@ -343,7 +344,7 @@
  *
  * @see ProvideTextStyle
  */
-val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { DefaultTextStyle }
 
 // TODO(b/156598010): remove this and replace with fold definition on the backing CompositionLocal
 /**
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
index ddb51f2e..7010bcf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextField.kt
@@ -24,7 +24,9 @@
 import androidx.compose.foundation.layout.calculateEndPadding
 import androidx.compose.foundation.layout.calculateStartPadding
 import androidx.compose.foundation.layout.defaultMinSize
+import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.text.BasicTextField
 import androidx.compose.foundation.text.KeyboardActions
 import androidx.compose.foundation.text.KeyboardOptions
@@ -59,6 +61,7 @@
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.coerceAtLeast
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.lerp
 import androidx.compose.ui.unit.offset
 import kotlin.math.max
 import kotlin.math.roundToInt
@@ -558,6 +561,8 @@
                 Box(
                     Modifier
                         .layoutId(PrefixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = PrefixSuffixTextPadding)
                 ) {
                     prefix()
@@ -567,6 +572,8 @@
                 Box(
                     Modifier
                         .layoutId(SuffixId)
+                        .heightIn(min = MinTextLineHeight)
+                        .wrapContentHeight()
                         .padding(start = PrefixSuffixTextPadding, end = endPadding)
                 ) {
                     suffix()
@@ -577,19 +584,29 @@
                 Box(
                     Modifier
                         .layoutId(LabelId)
+                        .heightIn(min = lerp(
+                            MinTextLineHeight, MinFocusedLabelLineHeight, animationProgress))
+                        .wrapContentHeight()
                         .padding(start = startPadding, end = endPadding)) { label() }
             }
 
-            val textPadding = Modifier.padding(
-                start = if (prefix == null) startPadding else 0.dp,
-                end = if (suffix == null) endPadding else 0.dp,
-            )
+            val textPadding = Modifier
+                .heightIn(min = MinTextLineHeight)
+                .wrapContentHeight()
+                .padding(
+                    start = if (prefix == null) startPadding else 0.dp,
+                    end = if (suffix == null) endPadding else 0.dp,
+                )
 
             if (placeholder != null) {
-                placeholder(Modifier.layoutId(PlaceholderId).then(textPadding))
+                placeholder(Modifier
+                    .layoutId(PlaceholderId)
+                    .then(textPadding))
             }
             Box(
-                modifier = Modifier.layoutId(TextFieldId).then(textPadding),
+                modifier = Modifier
+                    .layoutId(TextFieldId)
+                    .then(textPadding),
                 propagateMinConstraints = true,
             ) {
                 textField()
@@ -599,6 +616,8 @@
                 @OptIn(ExperimentalMaterial3Api::class)
                 Box(Modifier
                     .layoutId(SupportingId)
+                    .heightIn(min = MinSupportingTextLineHeight)
+                    .wrapContentHeight()
                     .padding(TextFieldDefaults.supportingTextPadding())
                 ) { supporting() }
             }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
index e90d4c1..6cfb01f 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TextFieldImpl.kt
@@ -422,5 +422,8 @@
 internal val HorizontalIconPadding = 12.dp
 internal val SupportingTopPadding = 4.dp
 internal val PrefixSuffixTextPadding = 2.dp
+internal val MinTextLineHeight = 24.dp
+internal val MinFocusedLabelLineHeight = 16.dp
+internal val MinSupportingTextLineHeight = 16.dp
 
 internal val IconDefaultSizeModifier = Modifier.defaultMinSize(48.dp, 48.dp)
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
index 665d9fe..a7ccf61 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -116,14 +116,19 @@
 import androidx.compose.ui.layout.Measurable
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.MeasureScope
+import androidx.compose.ui.layout.boundsInParent
 import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.onSizeChanged
 import androidx.compose.ui.platform.InspectorInfo
 import androidx.compose.ui.platform.InspectorValueInfo
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.clearAndSetSemantics
 import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.isContainer
+import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.selectableGroup
 import androidx.compose.ui.semantics.selected
@@ -563,9 +568,8 @@
         DpOffset(offsetX, offsetY)
     }
 
-    internal val values by derivedStateOf {
-        if (selection == Selection.Minute) Minutes else Hours
-    }
+    internal var center by mutableStateOf(IntOffset.Zero)
+    internal val values get() = if (selection == Selection.Minute) Minutes else Hours
 
     internal var selection by mutableStateOf(Selection.Hour)
     internal var isAfternoonToggle by mutableStateOf(initialHour > 12 && !is24Hour)
@@ -588,6 +592,12 @@
         hourAngle = RadiansPerHour * hour % 12 - FullCircle / 4
     }
 
+    internal fun moveSelector(x: Float, y: Float, maxDist: Float) {
+        if (selection == Selection.Hour && is24hour) {
+            isInnerCircle = dist(x, y, center.x, center.y) < maxDist
+        }
+    }
+
     internal fun isSelected(value: Int): Boolean =
         if (selection == Selection.Minute) {
             value == minute
@@ -655,6 +665,17 @@
         currentAngle.animateTo(targetValue.second, tween(200))
     }
 
+    internal suspend fun onTap(x: Float, y: Float, maxDist: Float) {
+        update(atan(y - center.y, x - center.x), true)
+        moveSelector(x, y, maxDist)
+
+        if (selection == Selection.Hour) {
+            selection = Selection.Minute
+        } else {
+            settle()
+        }
+    }
+
     companion object {
         /**
          * The default [Saver] implementation for [TimePickerState].
@@ -811,7 +832,7 @@
     )
 
     Box(
-        modifier = modifier,
+        modifier = modifier.clearAndSetSemantics { },
         contentAlignment = Alignment.Center
     ) {
         Text(
@@ -861,13 +882,13 @@
         shape = TimeSelectorContainerShape.toShape(),
         color = containerColor,
     ) {
-        val valueContentDescription = getString(
+        val valueContentDescription =
             numberContentDescription(
                 selection = selection,
-                is24Hour = state.is24hour
-            ),
-            value
-        )
+                is24Hour = state.is24hour,
+                number = value
+            )
+
         Box(contentAlignment = Alignment.Center) {
             Text(
                 modifier = Modifier.semantics { contentDescription = valueContentDescription },
@@ -886,6 +907,7 @@
             .background(shape = CircleShape, color = colors.clockDialColor)
             .size(ClockDialContainerSize)
             .semantics {
+                isContainer = false
                 selectableGroup()
             },
         targetState = state.values,
@@ -902,13 +924,12 @@
                 LocalContentColor provides colors.clockDialContentColor(false)
             ) {
                 repeat(screen.size) {
-                    val outerValue = if (!state.is24hour) screen[it] else screen[it] % 12
-                    ClockText(
-                        is24Hour = state.is24hour,
-                        selection = state.selection,
-                        value = outerValue,
-                        selected = state.isSelected(it)
-                    )
+                    val outerValue = if (!state.is24hour || state.selection == Selection.Minute) {
+                        screen[it]
+                    } else {
+                        screen[it] % 12
+                    }
+                    ClockText(state = state, value = outerValue)
                 }
 
                 if (state.selection == Selection.Hour && state.is24hour) {
@@ -921,12 +942,7 @@
                     ) {
                         repeat(ExtraHours.size) {
                             val innerValue = ExtraHours[it]
-                            ClockText(
-                                is24Hour = true,
-                                selection = state.selection,
-                                value = innerValue,
-                                selected = state.isSelected(it % 11)
-                            )
+                            ClockText(state = state, value = innerValue)
                         }
                     }
                 }
@@ -1003,37 +1019,24 @@
 }) {
     var offsetX by remember { mutableStateOf(0f) }
     var offsetY by remember { mutableStateOf(0f) }
-    var center by remember { mutableStateOf(IntOffset.Zero) }
+    val center by remember { mutableStateOf(IntOffset.Zero) }
     val scope = rememberCoroutineScope()
     val maxDist = with(LocalDensity.current) { MaxDistance.toPx() }
-    fun moveSelector(x: Float, y: Float) {
-        if (state.selection == Selection.Hour && state.is24hour) {
-            state.isInnerCircle = dist(x, y, center.x, center.y) < maxDist
-        }
-    }
+
     Modifier
-        .onSizeChanged { center = it.center }
-        .pointerInput(state, maxDist, center) {
+        .onSizeChanged { state.center = it.center }
+        .pointerInput(state, center, maxDist) {
             detectTapGestures(
                 onPress = {
                     offsetX = it.x
                     offsetY = it.y
                 },
                 onTap = {
-                    scope.launch {
-                        state.update(atan(it.y - center.y, it.x - center.x), true)
-                        moveSelector(it.x, it.y)
-
-                        if (state.selection == Selection.Hour) {
-                            state.selection = Selection.Minute
-                        } else {
-                            state.settle()
-                        }
-                    }
+                    scope.launch { state.onTap(it.x, it.y, maxDist) }
                 },
             )
         }
-        .pointerInput(state, maxDist, center) {
+        .pointerInput(state, center, maxDist) {
             detectDragGestures(onDragEnd = {
                 scope.launch {
                     if (state.selection == Selection.Hour) {
@@ -1047,47 +1050,56 @@
                 scope.launch {
                     offsetX += dragAmount.x
                     offsetY += dragAmount.y
-                    state.update(atan(offsetY - center.y, offsetX - center.x))
+                    state.update(atan(offsetY - state.center.y, offsetX - state.center.x))
                 }
-                moveSelector(offsetX, offsetY)
+                state.moveSelector(offsetX, offsetY, maxDist)
             }
         }
 }
 
 @Composable
-private fun ClockText(
-    is24Hour: Boolean,
-    selected: Boolean,
-    selection: Selection,
-    value: Int
-) {
+private fun ClockText(state: TimePickerState, value: Int) {
     val style = MaterialTheme.typography.fromToken(ClockDialLabelTextFont).let {
-        remember(it) {
-            copyAndSetFontPadding(style = it, false)
-        }
+        copyAndSetFontPadding(style = it, false)
     }
 
-    val contentDescription = getString(
+    val maxDist = with(LocalDensity.current) { MaxDistance.toPx() }
+    var center by remember { mutableStateOf(Offset.Zero) }
+    val scope = rememberCoroutineScope()
+    val contentDescription =
         numberContentDescription(
-            selection = selection,
-            is24Hour = is24Hour
-        ),
-        value
-    )
+            selection = state.selection,
+            is24Hour = state.is24hour,
+            number = value
+        )
+
+    val text = value.toLocalString(minDigits = 1)
+    val selected = if (state.selection == Selection.Minute) {
+        state.minute.toLocalString(minDigits = 1) == text
+    } else {
+        state.hour.toLocalString(minDigits = 1) == text
+    }
 
     Box(
         contentAlignment = Alignment.Center,
         modifier = Modifier
             .minimumInteractiveComponentSize()
             .size(MinimumInteractiveSize)
+            .onGloballyPositioned { center = it.boundsInParent().center }
             .focusable()
-            .semantics {
+            .semantics(mergeDescendants = true) {
+                onClick {
+                    scope.launch { state.onTap(center.x, center.y, maxDist) }
+                    true
+                }
                 this.selected = selected
-                this.contentDescription = contentDescription
             }
     ) {
         Text(
-            text = value.toLocalString(minDigits = 1),
+            modifier = Modifier.clearAndSetSemantics {
+                this.contentDescription = contentDescription
+            },
+            text = text,
             style = style,
         )
     }
@@ -1288,16 +1300,20 @@
 
 @Composable
 @ReadOnlyComposable
-private fun numberContentDescription(selection: Selection, is24Hour: Boolean): Strings {
-    if (selection == Selection.Minute) {
-        return Strings.TimePickerMinuteSuffix
+internal fun numberContentDescription(
+    selection: Selection,
+    is24Hour: Boolean,
+    number: Int
+): String {
+    val id = if (selection == Selection.Minute) {
+        Strings.TimePickerMinuteSuffix
+    } else if (is24Hour) {
+        Strings.TimePicker24HourSuffix
+    } else {
+        Strings.TimePickerHourSuffix
     }
 
-    if (is24Hour) {
-        return Strings.TimePicker24HourSuffix
-    }
-
-    return Strings.TimePickerHourSuffix
+    return getString(id, number)
 }
 
 private fun valuesForAnimation(current: Float, new: Float): Pair<Float, Float> {
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
index 403ced0..089c598 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TypographyTokens.kt
@@ -18,11 +18,12 @@
 
 package androidx.compose.material3.tokens
 
+import androidx.compose.material3.defaultPlatformTextStyle
 import androidx.compose.ui.text.TextStyle
 
 internal object TypographyTokens {
     val BodyLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodyLargeFont,
             fontWeight = TypeScaleTokens.BodyLargeWeight,
             fontSize = TypeScaleTokens.BodyLargeSize,
@@ -30,7 +31,7 @@
             letterSpacing = TypeScaleTokens.BodyLargeTracking,
         )
     val BodyMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodyMediumFont,
             fontWeight = TypeScaleTokens.BodyMediumWeight,
             fontSize = TypeScaleTokens.BodyMediumSize,
@@ -38,7 +39,7 @@
             letterSpacing = TypeScaleTokens.BodyMediumTracking,
         )
     val BodySmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodySmallFont,
             fontWeight = TypeScaleTokens.BodySmallWeight,
             fontSize = TypeScaleTokens.BodySmallSize,
@@ -46,7 +47,7 @@
             letterSpacing = TypeScaleTokens.BodySmallTracking,
         )
     val DisplayLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplayLargeFont,
             fontWeight = TypeScaleTokens.DisplayLargeWeight,
             fontSize = TypeScaleTokens.DisplayLargeSize,
@@ -54,7 +55,7 @@
             letterSpacing = TypeScaleTokens.DisplayLargeTracking,
         )
     val DisplayMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplayMediumFont,
             fontWeight = TypeScaleTokens.DisplayMediumWeight,
             fontSize = TypeScaleTokens.DisplayMediumSize,
@@ -62,7 +63,7 @@
             letterSpacing = TypeScaleTokens.DisplayMediumTracking,
         )
     val DisplaySmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplaySmallFont,
             fontWeight = TypeScaleTokens.DisplaySmallWeight,
             fontSize = TypeScaleTokens.DisplaySmallSize,
@@ -70,7 +71,7 @@
             letterSpacing = TypeScaleTokens.DisplaySmallTracking,
         )
     val HeadlineLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineLargeFont,
             fontWeight = TypeScaleTokens.HeadlineLargeWeight,
             fontSize = TypeScaleTokens.HeadlineLargeSize,
@@ -78,7 +79,7 @@
             letterSpacing = TypeScaleTokens.HeadlineLargeTracking,
         )
     val HeadlineMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineMediumFont,
             fontWeight = TypeScaleTokens.HeadlineMediumWeight,
             fontSize = TypeScaleTokens.HeadlineMediumSize,
@@ -86,7 +87,7 @@
             letterSpacing = TypeScaleTokens.HeadlineMediumTracking,
         )
     val HeadlineSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineSmallFont,
             fontWeight = TypeScaleTokens.HeadlineSmallWeight,
             fontSize = TypeScaleTokens.HeadlineSmallSize,
@@ -94,7 +95,7 @@
             letterSpacing = TypeScaleTokens.HeadlineSmallTracking,
         )
     val LabelLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelLargeFont,
             fontWeight = TypeScaleTokens.LabelLargeWeight,
             fontSize = TypeScaleTokens.LabelLargeSize,
@@ -102,7 +103,7 @@
             letterSpacing = TypeScaleTokens.LabelLargeTracking,
         )
     val LabelMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelMediumFont,
             fontWeight = TypeScaleTokens.LabelMediumWeight,
             fontSize = TypeScaleTokens.LabelMediumSize,
@@ -110,7 +111,7 @@
             letterSpacing = TypeScaleTokens.LabelMediumTracking,
         )
     val LabelSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelSmallFont,
             fontWeight = TypeScaleTokens.LabelSmallWeight,
             fontSize = TypeScaleTokens.LabelSmallSize,
@@ -118,7 +119,7 @@
             letterSpacing = TypeScaleTokens.LabelSmallTracking,
         )
     val TitleLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleLargeFont,
             fontWeight = TypeScaleTokens.TitleLargeWeight,
             fontSize = TypeScaleTokens.TitleLargeSize,
@@ -126,7 +127,7 @@
             letterSpacing = TypeScaleTokens.TitleLargeTracking,
         )
     val TitleMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleMediumFont,
             fontWeight = TypeScaleTokens.TitleMediumWeight,
             fontSize = TypeScaleTokens.TitleMediumSize,
@@ -134,11 +135,13 @@
             letterSpacing = TypeScaleTokens.TitleMediumTracking,
         )
     val TitleSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleSmallFont,
             fontWeight = TypeScaleTokens.TitleSmallWeight,
             fontSize = TypeScaleTokens.TitleSmallSize,
             lineHeight = TypeScaleTokens.TitleSmallLineHeight,
             letterSpacing = TypeScaleTokens.TitleSmallTracking,
         )
-}
\ No newline at end of file
+}
+
+internal val DefaultTextStyle = TextStyle.Default.copy(platformStyle = defaultPlatformTextStyle())
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt
new file mode 100644
index 0000000..3e06cac
--- /dev/null
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/DefaultPlatformTextStyle.desktop.kt
@@ -0,0 +1,21 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.ui.text.PlatformTextStyle
+
+internal actual fun defaultPlatformTextStyle(): PlatformTextStyle? = null
\ No newline at end of file
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt
new file mode 100644
index 0000000..2ffde87
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt
@@ -0,0 +1,630 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:Suppress("NOTHING_TO_INLINE")
+@file:OptIn(ExperimentalUnsignedTypes::class)
+
+package androidx.compose.ui.graphics.vector
+
+/**
+ * The code below is adapted from:
+ *     https://github.com/fastfloat/fast_float
+ *     https://github.com/lemire/fast_double_parser/
+ * The original C++ implementations are licensed under Apache 2.0
+ */
+
+internal class FloatResult(var value: Float = Float.NaN, var isValid: Boolean = false)
+
+internal class FastFloatParser {
+    companion object {
+        private const val FloatMinExponent = -10
+        private const val FloatMaxExponent = 10
+        private const val FloatSmallestExponent = -325
+        private const val FloatMaxExponentNumber = 1024
+
+        private val PowersOfTen = floatArrayOf(
+            1e0f, 1e1f, 1e2f, 1e3f, 1e4f, 1e5f, 1e6f, 1e7f, 1e8f, 1e9f, 1e10f
+        )
+
+        /**
+         * Parses the next float in the char sequence [s], starting at offset [start], until at most
+         * the end offset [end]. The results are written in [result]. When a result is valid, the
+         * `result.isValid` is set to `true` and `result.value` contains the parsed float value. If
+         * parsing is unsuccessful, `result.isValid` is false and `result.value` is set to
+         * [Float.NaN].
+         *
+         * This function returns the offset inside the char sequence right after parsing stopped,
+         * successfully or unsuccessfully.
+         */
+        fun nextFloat(s: String, start: Int, end: Int, result: FloatResult): Int {
+            result.value = Float.NaN
+            result.isValid = false
+
+            if (start == end) return start
+
+            var index = start
+            var c = s[index]
+
+            // Check for leading negative sign
+            val isNegative = c == '-'
+            if (isNegative) {
+                index++
+                if (index == end) return index
+
+                // Safe access, we just checked the bounds
+                c = s[index]
+                if (!c.isDigit && c != '.') return index
+            }
+
+            // TODO: Should we use an unsigned long here?
+            var significand = 0L
+            val significandStartIndex = index
+
+            // Parse the integer part
+            while (index != end && c.isDigit) {
+                significand = 10L * significand + (c.code - '0'.code).toLong()
+                c = charAt(s, ++index)
+            }
+
+            val significandEndIndex = index
+            var digitCount = index - significandStartIndex
+
+            var exponent = 0
+            var exponentStartIndex = index
+            var exponentEndIndex = index
+
+            // Parse the fraction
+            if (index != end && c == '.') {
+                index++
+                exponentStartIndex = index
+
+                while (end - index >= 4) {
+                    val digits = parseFourDigits(s, index)
+                    if (digits < 0) break
+                    significand = 10_000L * significand + digits.toLong()
+                    index += 4
+                }
+
+                c = charAt(s, index)
+                while (index != end && c.isDigit) {
+                    significand = 10L * significand + (c.code - '0'.code).toLong()
+                    c = charAt(s, ++index)
+                }
+
+                exponent = exponentStartIndex - index
+                exponentEndIndex = index
+                digitCount -= exponent
+            }
+
+            if (digitCount == 0) return index
+
+            // Parse the exponent part of the float, if present
+            var exponentNumber = 0
+            if ((c.code or 0x20) == 'e'.code) {
+                c = charAt(s, ++index)
+
+                val isExponentNegative = c == '-'
+                if (isExponentNegative || c == '+') {
+                    index++
+                }
+
+                c = s[index]
+                while (index != end && c.isDigit) {
+                    if (exponentNumber < FloatMaxExponentNumber) {
+                        exponentNumber = 10 * exponentNumber + (c.code - '0'.code)
+                    }
+                    c = charAt(s, ++index)
+                }
+
+                if (isExponentNegative) exponentNumber = -exponentNumber
+                exponent += exponentNumber
+            }
+
+            // TODO: check for f/F suffix?
+
+            var tooManyDigits = false
+
+            // If we have too many digits we need to retry differently and avoid overflows
+            if (digitCount > 19) {
+                var retryIndex = significandStartIndex
+                c = s[retryIndex]
+
+                // First check for the case where the number is 0.0000000xxxx (could be all zeroes)
+                while (index != end && (c == '0' || c == '.')) {
+                    if (c == '0') digitCount--
+                    c = charAt(s, ++retryIndex)
+                }
+
+                if (digitCount > 19) {
+                    tooManyDigits = true
+
+                    significand = 0
+                    retryIndex = significandStartIndex
+                    c = s[retryIndex]
+
+                    while (
+                        retryIndex != significandEndIndex &&
+                        significand.toULong() < 1000000000000000000UL
+                    ) {
+                        significand = 10L * significand + (c.code - '0'.code).toLong()
+                        c = charAt(s, ++retryIndex)
+                    }
+
+                    if (significand.toULong() >= 1000000000000000000UL) {
+                        exponent = significandEndIndex - retryIndex + exponentNumber
+                    } else {
+                        retryIndex = exponentStartIndex
+                        c = s[retryIndex]
+
+                        while (
+                            retryIndex != exponentEndIndex &&
+                            significand.toULong() < 1000000000000000000UL
+                        ) {
+                            significand = 10L * significand + (c.code - '0'.code).toLong()
+                            c = charAt(s, ++retryIndex)
+                        }
+                        exponent = exponentStartIndex - retryIndex + exponentNumber
+                    }
+                }
+            }
+
+            // Fast path
+            if (exponent in FloatMinExponent..FloatMaxExponent &&
+                !tooManyDigits &&
+                significand.toULong() <= 1UL shl 24
+            ) {
+                var f = significand.toFloat()
+                if (exponent < 0) {
+                    f /= PowersOfTen[-exponent]
+                } else {
+                    f *= PowersOfTen[exponent]
+                }
+
+                result.isValid = true
+                result.value = if (isNegative) -f else f
+
+                return index
+            }
+
+            // Now we need to take the slow path, please refer to the original C++ code for a
+            // complete description of the algorithm
+
+            if (significand == 0L) {
+                result.isValid = true
+                result.value = if (isNegative) -0.0f else 0.0f
+                return index
+            }
+
+            if (exponent !in -126..127) {
+                try {
+                    result.value = s.substring(start, index).toFloat()
+                } finally {
+                    result.isValid = true
+                }
+                return index
+            }
+
+            val significandFactor = Mantissa64[exponent - FloatSmallestExponent].toLong()
+            var lz = significand.countLeadingZeroBits()
+            significand = significand shl lz
+
+            val upper = fullMultiplicationHighBits(significand, significandFactor)
+            val upperBit = (upper ushr 63).toInt()
+            var mantissa = upper ushr (upperBit + 9)
+            lz += 1 xor upperBit
+
+            if (upper and 0x1ff == 0x1ffL || upper and 0x1ff == 0L && mantissa and 3L == 1L) {
+                try {
+                    result.value = s.substring(start, index).toFloat()
+                } finally {
+                    result.isValid = true
+                }
+                return index
+            }
+
+            mantissa += 1
+            mantissa = mantissa ushr 1
+
+            if (mantissa >= 1L shl 53) {
+                mantissa = 1L shl 52
+                lz--
+            }
+
+            mantissa = mantissa and (1L shl 52).inv()
+
+            val adjustedExponent = (((152170L + 65536L) * exponent) shr 16) + 1024 + 63
+            val realExponent = adjustedExponent - lz
+            if (realExponent < 1 || realExponent > 2046) {
+                try {
+                    result.value = s.substring(start, index).toFloat()
+                } finally {
+                    result.isValid = true
+                }
+                return index
+            }
+
+            mantissa = mantissa or (realExponent shl 52)
+            mantissa = mantissa or if (isNegative) 1L shl 63 else 0L
+
+            result.isValid = true
+            result.value = Double.fromBits(mantissa).toFloat()
+
+            return index
+        }
+
+        private val Mantissa64 = ulongArrayOf(
+            0xa5ced43b7e3e9188UL, 0xcf42894a5dce35eaUL,
+            0x818995ce7aa0e1b2UL, 0xa1ebfb4219491a1fUL,
+            0xca66fa129f9b60a6UL, 0xfd00b897478238d0UL,
+            0x9e20735e8cb16382UL, 0xc5a890362fddbc62UL,
+            0xf712b443bbd52b7bUL, 0x9a6bb0aa55653b2dUL,
+            0xc1069cd4eabe89f8UL, 0xf148440a256e2c76UL,
+            0x96cd2a865764dbcaUL, 0xbc807527ed3e12bcUL,
+            0xeba09271e88d976bUL, 0x93445b8731587ea3UL,
+            0xb8157268fdae9e4cUL, 0xe61acf033d1a45dfUL,
+            0x8fd0c16206306babUL, 0xb3c4f1ba87bc8696UL,
+            0xe0b62e2929aba83cUL, 0x8c71dcd9ba0b4925UL,
+            0xaf8e5410288e1b6fUL, 0xdb71e91432b1a24aUL,
+            0x892731ac9faf056eUL, 0xab70fe17c79ac6caUL,
+            0xd64d3d9db981787dUL, 0x85f0468293f0eb4eUL,
+            0xa76c582338ed2621UL, 0xd1476e2c07286faaUL,
+            0x82cca4db847945caUL, 0xa37fce126597973cUL,
+            0xcc5fc196fefd7d0cUL, 0xff77b1fcbebcdc4fUL,
+            0x9faacf3df73609b1UL, 0xc795830d75038c1dUL,
+            0xf97ae3d0d2446f25UL, 0x9becce62836ac577UL,
+            0xc2e801fb244576d5UL, 0xf3a20279ed56d48aUL,
+            0x9845418c345644d6UL, 0xbe5691ef416bd60cUL,
+            0xedec366b11c6cb8fUL, 0x94b3a202eb1c3f39UL,
+            0xb9e08a83a5e34f07UL, 0xe858ad248f5c22c9UL,
+            0x91376c36d99995beUL, 0xb58547448ffffb2dUL,
+            0xe2e69915b3fff9f9UL, 0x8dd01fad907ffc3bUL,
+            0xb1442798f49ffb4aUL, 0xdd95317f31c7fa1dUL,
+            0x8a7d3eef7f1cfc52UL, 0xad1c8eab5ee43b66UL,
+            0xd863b256369d4a40UL, 0x873e4f75e2224e68UL,
+            0xa90de3535aaae202UL, 0xd3515c2831559a83UL,
+            0x8412d9991ed58091UL, 0xa5178fff668ae0b6UL,
+            0xce5d73ff402d98e3UL, 0x80fa687f881c7f8eUL,
+            0xa139029f6a239f72UL, 0xc987434744ac874eUL,
+            0xfbe9141915d7a922UL, 0x9d71ac8fada6c9b5UL,
+            0xc4ce17b399107c22UL, 0xf6019da07f549b2bUL,
+            0x99c102844f94e0fbUL, 0xc0314325637a1939UL,
+            0xf03d93eebc589f88UL, 0x96267c7535b763b5UL,
+            0xbbb01b9283253ca2UL, 0xea9c227723ee8bcbUL,
+            0x92a1958a7675175fUL, 0xb749faed14125d36UL,
+            0xe51c79a85916f484UL, 0x8f31cc0937ae58d2UL,
+            0xb2fe3f0b8599ef07UL, 0xdfbdcece67006ac9UL,
+            0x8bd6a141006042bdUL, 0xaecc49914078536dUL,
+            0xda7f5bf590966848UL, 0x888f99797a5e012dUL,
+            0xaab37fd7d8f58178UL, 0xd5605fcdcf32e1d6UL,
+            0x855c3be0a17fcd26UL, 0xa6b34ad8c9dfc06fUL,
+            0xd0601d8efc57b08bUL, 0x823c12795db6ce57UL,
+            0xa2cb1717b52481edUL, 0xcb7ddcdda26da268UL,
+            0xfe5d54150b090b02UL, 0x9efa548d26e5a6e1UL,
+            0xc6b8e9b0709f109aUL, 0xf867241c8cc6d4c0UL,
+            0x9b407691d7fc44f8UL, 0xc21094364dfb5636UL,
+            0xf294b943e17a2bc4UL, 0x979cf3ca6cec5b5aUL,
+            0xbd8430bd08277231UL, 0xece53cec4a314ebdUL,
+            0x940f4613ae5ed136UL, 0xb913179899f68584UL,
+            0xe757dd7ec07426e5UL, 0x9096ea6f3848984fUL,
+            0xb4bca50b065abe63UL, 0xe1ebce4dc7f16dfbUL,
+            0x8d3360f09cf6e4bdUL, 0xb080392cc4349decUL,
+            0xdca04777f541c567UL, 0x89e42caaf9491b60UL,
+            0xac5d37d5b79b6239UL, 0xd77485cb25823ac7UL,
+            0x86a8d39ef77164bcUL, 0xa8530886b54dbdebUL,
+            0xd267caa862a12d66UL, 0x8380dea93da4bc60UL,
+            0xa46116538d0deb78UL, 0xcd795be870516656UL,
+            0x806bd9714632dff6UL, 0xa086cfcd97bf97f3UL,
+            0xc8a883c0fdaf7df0UL, 0xfad2a4b13d1b5d6cUL,
+            0x9cc3a6eec6311a63UL, 0xc3f490aa77bd60fcUL,
+            0xf4f1b4d515acb93bUL, 0x991711052d8bf3c5UL,
+            0xbf5cd54678eef0b6UL, 0xef340a98172aace4UL,
+            0x9580869f0e7aac0eUL, 0xbae0a846d2195712UL,
+            0xe998d258869facd7UL, 0x91ff83775423cc06UL,
+            0xb67f6455292cbf08UL, 0xe41f3d6a7377eecaUL,
+            0x8e938662882af53eUL, 0xb23867fb2a35b28dUL,
+            0xdec681f9f4c31f31UL, 0x8b3c113c38f9f37eUL,
+            0xae0b158b4738705eUL, 0xd98ddaee19068c76UL,
+            0x87f8a8d4cfa417c9UL, 0xa9f6d30a038d1dbcUL,
+            0xd47487cc8470652bUL, 0x84c8d4dfd2c63f3bUL,
+            0xa5fb0a17c777cf09UL, 0xcf79cc9db955c2ccUL,
+            0x81ac1fe293d599bfUL, 0xa21727db38cb002fUL,
+            0xca9cf1d206fdc03bUL, 0xfd442e4688bd304aUL,
+            0x9e4a9cec15763e2eUL, 0xc5dd44271ad3cdbaUL,
+            0xf7549530e188c128UL, 0x9a94dd3e8cf578b9UL,
+            0xc13a148e3032d6e7UL, 0xf18899b1bc3f8ca1UL,
+            0x96f5600f15a7b7e5UL, 0xbcb2b812db11a5deUL,
+            0xebdf661791d60f56UL, 0x936b9fcebb25c995UL,
+            0xb84687c269ef3bfbUL, 0xe65829b3046b0afaUL,
+            0x8ff71a0fe2c2e6dcUL, 0xb3f4e093db73a093UL,
+            0xe0f218b8d25088b8UL, 0x8c974f7383725573UL,
+            0xafbd2350644eeacfUL, 0xdbac6c247d62a583UL,
+            0x894bc396ce5da772UL, 0xab9eb47c81f5114fUL,
+            0xd686619ba27255a2UL, 0x8613fd0145877585UL,
+            0xa798fc4196e952e7UL, 0xd17f3b51fca3a7a0UL,
+            0x82ef85133de648c4UL, 0xa3ab66580d5fdaf5UL,
+            0xcc963fee10b7d1b3UL, 0xffbbcfe994e5c61fUL,
+            0x9fd561f1fd0f9bd3UL, 0xc7caba6e7c5382c8UL,
+            0xf9bd690a1b68637bUL, 0x9c1661a651213e2dUL,
+            0xc31bfa0fe5698db8UL, 0xf3e2f893dec3f126UL,
+            0x986ddb5c6b3a76b7UL, 0xbe89523386091465UL,
+            0xee2ba6c0678b597fUL, 0x94db483840b717efUL,
+            0xba121a4650e4ddebUL, 0xe896a0d7e51e1566UL,
+            0x915e2486ef32cd60UL, 0xb5b5ada8aaff80b8UL,
+            0xe3231912d5bf60e6UL, 0x8df5efabc5979c8fUL,
+            0xb1736b96b6fd83b3UL, 0xddd0467c64bce4a0UL,
+            0x8aa22c0dbef60ee4UL, 0xad4ab7112eb3929dUL,
+            0xd89d64d57a607744UL, 0x87625f056c7c4a8bUL,
+            0xa93af6c6c79b5d2dUL, 0xd389b47879823479UL,
+            0x843610cb4bf160cbUL, 0xa54394fe1eedb8feUL,
+            0xce947a3da6a9273eUL, 0x811ccc668829b887UL,
+            0xa163ff802a3426a8UL, 0xc9bcff6034c13052UL,
+            0xfc2c3f3841f17c67UL, 0x9d9ba7832936edc0UL,
+            0xc5029163f384a931UL, 0xf64335bcf065d37dUL,
+            0x99ea0196163fa42eUL, 0xc06481fb9bcf8d39UL,
+            0xf07da27a82c37088UL, 0x964e858c91ba2655UL,
+            0xbbe226efb628afeaUL, 0xeadab0aba3b2dbe5UL,
+            0x92c8ae6b464fc96fUL, 0xb77ada0617e3bbcbUL,
+            0xe55990879ddcaabdUL, 0x8f57fa54c2a9eab6UL,
+            0xb32df8e9f3546564UL, 0xdff9772470297ebdUL,
+            0x8bfbea76c619ef36UL, 0xaefae51477a06b03UL,
+            0xdab99e59958885c4UL, 0x88b402f7fd75539bUL,
+            0xaae103b5fcd2a881UL, 0xd59944a37c0752a2UL,
+            0x857fcae62d8493a5UL, 0xa6dfbd9fb8e5b88eUL,
+            0xd097ad07a71f26b2UL, 0x825ecc24c873782fUL,
+            0xa2f67f2dfa90563bUL, 0xcbb41ef979346bcaUL,
+            0xfea126b7d78186bcUL, 0x9f24b832e6b0f436UL,
+            0xc6ede63fa05d3143UL, 0xf8a95fcf88747d94UL,
+            0x9b69dbe1b548ce7cUL, 0xc24452da229b021bUL,
+            0xf2d56790ab41c2a2UL, 0x97c560ba6b0919a5UL,
+            0xbdb6b8e905cb600fUL, 0xed246723473e3813UL,
+            0x9436c0760c86e30bUL, 0xb94470938fa89bceUL,
+            0xe7958cb87392c2c2UL, 0x90bd77f3483bb9b9UL,
+            0xb4ecd5f01a4aa828UL, 0xe2280b6c20dd5232UL,
+            0x8d590723948a535fUL, 0xb0af48ec79ace837UL,
+            0xdcdb1b2798182244UL, 0x8a08f0f8bf0f156bUL,
+            0xac8b2d36eed2dac5UL, 0xd7adf884aa879177UL,
+            0x86ccbb52ea94baeaUL, 0xa87fea27a539e9a5UL,
+            0xd29fe4b18e88640eUL, 0x83a3eeeef9153e89UL,
+            0xa48ceaaab75a8e2bUL, 0xcdb02555653131b6UL,
+            0x808e17555f3ebf11UL, 0xa0b19d2ab70e6ed6UL,
+            0xc8de047564d20a8bUL, 0xfb158592be068d2eUL,
+            0x9ced737bb6c4183dUL, 0xc428d05aa4751e4cUL,
+            0xf53304714d9265dfUL, 0x993fe2c6d07b7fabUL,
+            0xbf8fdb78849a5f96UL, 0xef73d256a5c0f77cUL,
+            0x95a8637627989aadUL, 0xbb127c53b17ec159UL,
+            0xe9d71b689dde71afUL, 0x9226712162ab070dUL,
+            0xb6b00d69bb55c8d1UL, 0xe45c10c42a2b3b05UL,
+            0x8eb98a7a9a5b04e3UL, 0xb267ed1940f1c61cUL,
+            0xdf01e85f912e37a3UL, 0x8b61313bbabce2c6UL,
+            0xae397d8aa96c1b77UL, 0xd9c7dced53c72255UL,
+            0x881cea14545c7575UL, 0xaa242499697392d2UL,
+            0xd4ad2dbfc3d07787UL, 0x84ec3c97da624ab4UL,
+            0xa6274bbdd0fadd61UL, 0xcfb11ead453994baUL,
+            0x81ceb32c4b43fcf4UL, 0xa2425ff75e14fc31UL,
+            0xcad2f7f5359a3b3eUL, 0xfd87b5f28300ca0dUL,
+            0x9e74d1b791e07e48UL, 0xc612062576589ddaUL,
+            0xf79687aed3eec551UL, 0x9abe14cd44753b52UL,
+            0xc16d9a0095928a27UL, 0xf1c90080baf72cb1UL,
+            0x971da05074da7beeUL, 0xbce5086492111aeaUL,
+            0xec1e4a7db69561a5UL, 0x9392ee8e921d5d07UL,
+            0xb877aa3236a4b449UL, 0xe69594bec44de15bUL,
+            0x901d7cf73ab0acd9UL, 0xb424dc35095cd80fUL,
+            0xe12e13424bb40e13UL, 0x8cbccc096f5088cbUL,
+            0xafebff0bcb24aafeUL, 0xdbe6fecebdedd5beUL,
+            0x89705f4136b4a597UL, 0xabcc77118461cefcUL,
+            0xd6bf94d5e57a42bcUL, 0x8637bd05af6c69b5UL,
+            0xa7c5ac471b478423UL, 0xd1b71758e219652bUL,
+            0x83126e978d4fdf3bUL, 0xa3d70a3d70a3d70aUL,
+            0xccccccccccccccccUL, 0x8000000000000000UL,
+            0xa000000000000000UL, 0xc800000000000000UL,
+            0xfa00000000000000UL, 0x9c40000000000000UL,
+            0xc350000000000000UL, 0xf424000000000000UL,
+            0x9896800000000000UL, 0xbebc200000000000UL,
+            0xee6b280000000000UL, 0x9502f90000000000UL,
+            0xba43b74000000000UL, 0xe8d4a51000000000UL,
+            0x9184e72a00000000UL, 0xb5e620f480000000UL,
+            0xe35fa931a0000000UL, 0x8e1bc9bf04000000UL,
+            0xb1a2bc2ec5000000UL, 0xde0b6b3a76400000UL,
+            0x8ac7230489e80000UL, 0xad78ebc5ac620000UL,
+            0xd8d726b7177a8000UL, 0x878678326eac9000UL,
+            0xa968163f0a57b400UL, 0xd3c21bcecceda100UL,
+            0x84595161401484a0UL, 0xa56fa5b99019a5c8UL,
+            0xcecb8f27f4200f3aUL, 0x813f3978f8940984UL,
+            0xa18f07d736b90be5UL, 0xc9f2c9cd04674edeUL,
+            0xfc6f7c4045812296UL, 0x9dc5ada82b70b59dUL,
+            0xc5371912364ce305UL, 0xf684df56c3e01bc6UL,
+            0x9a130b963a6c115cUL, 0xc097ce7bc90715b3UL,
+            0xf0bdc21abb48db20UL, 0x96769950b50d88f4UL,
+            0xbc143fa4e250eb31UL, 0xeb194f8e1ae525fdUL,
+            0x92efd1b8d0cf37beUL, 0xb7abc627050305adUL,
+            0xe596b7b0c643c719UL, 0x8f7e32ce7bea5c6fUL,
+            0xb35dbf821ae4f38bUL, 0xe0352f62a19e306eUL,
+            0x8c213d9da502de45UL, 0xaf298d050e4395d6UL,
+            0xdaf3f04651d47b4cUL, 0x88d8762bf324cd0fUL,
+            0xab0e93b6efee0053UL, 0xd5d238a4abe98068UL,
+            0x85a36366eb71f041UL, 0xa70c3c40a64e6c51UL,
+            0xd0cf4b50cfe20765UL, 0x82818f1281ed449fUL,
+            0xa321f2d7226895c7UL, 0xcbea6f8ceb02bb39UL,
+            0xfee50b7025c36a08UL, 0x9f4f2726179a2245UL,
+            0xc722f0ef9d80aad6UL, 0xf8ebad2b84e0d58bUL,
+            0x9b934c3b330c8577UL, 0xc2781f49ffcfa6d5UL,
+            0xf316271c7fc3908aUL, 0x97edd871cfda3a56UL,
+            0xbde94e8e43d0c8ecUL, 0xed63a231d4c4fb27UL,
+            0x945e455f24fb1cf8UL, 0xb975d6b6ee39e436UL,
+            0xe7d34c64a9c85d44UL, 0x90e40fbeea1d3a4aUL,
+            0xb51d13aea4a488ddUL, 0xe264589a4dcdab14UL,
+            0x8d7eb76070a08aecUL, 0xb0de65388cc8ada8UL,
+            0xdd15fe86affad912UL, 0x8a2dbf142dfcc7abUL,
+            0xacb92ed9397bf996UL, 0xd7e77a8f87daf7fbUL,
+            0x86f0ac99b4e8dafdUL, 0xa8acd7c0222311bcUL,
+            0xd2d80db02aabd62bUL, 0x83c7088e1aab65dbUL,
+            0xa4b8cab1a1563f52UL, 0xcde6fd5e09abcf26UL,
+            0x80b05e5ac60b6178UL, 0xa0dc75f1778e39d6UL,
+            0xc913936dd571c84cUL, 0xfb5878494ace3a5fUL,
+            0x9d174b2dcec0e47bUL, 0xc45d1df942711d9aUL,
+            0xf5746577930d6500UL, 0x9968bf6abbe85f20UL,
+            0xbfc2ef456ae276e8UL, 0xefb3ab16c59b14a2UL,
+            0x95d04aee3b80ece5UL, 0xbb445da9ca61281fUL,
+            0xea1575143cf97226UL, 0x924d692ca61be758UL,
+            0xb6e0c377cfa2e12eUL, 0xe498f455c38b997aUL,
+            0x8edf98b59a373fecUL, 0xb2977ee300c50fe7UL,
+            0xdf3d5e9bc0f653e1UL, 0x8b865b215899f46cUL,
+            0xae67f1e9aec07187UL, 0xda01ee641a708de9UL,
+            0x884134fe908658b2UL, 0xaa51823e34a7eedeUL,
+            0xd4e5e2cdc1d1ea96UL, 0x850fadc09923329eUL,
+            0xa6539930bf6bff45UL, 0xcfe87f7cef46ff16UL,
+            0x81f14fae158c5f6eUL, 0xa26da3999aef7749UL,
+            0xcb090c8001ab551cUL, 0xfdcb4fa002162a63UL,
+            0x9e9f11c4014dda7eUL, 0xc646d63501a1511dUL,
+            0xf7d88bc24209a565UL, 0x9ae757596946075fUL,
+            0xc1a12d2fc3978937UL, 0xf209787bb47d6b84UL,
+            0x9745eb4d50ce6332UL, 0xbd176620a501fbffUL,
+            0xec5d3fa8ce427affUL, 0x93ba47c980e98cdfUL,
+            0xb8a8d9bbe123f017UL, 0xe6d3102ad96cec1dUL,
+            0x9043ea1ac7e41392UL, 0xb454e4a179dd1877UL,
+            0xe16a1dc9d8545e94UL, 0x8ce2529e2734bb1dUL,
+            0xb01ae745b101e9e4UL, 0xdc21a1171d42645dUL,
+            0x899504ae72497ebaUL, 0xabfa45da0edbde69UL,
+            0xd6f8d7509292d603UL, 0x865b86925b9bc5c2UL,
+            0xa7f26836f282b732UL, 0xd1ef0244af2364ffUL,
+            0x8335616aed761f1fUL, 0xa402b9c5a8d3a6e7UL,
+            0xcd036837130890a1UL, 0x802221226be55a64UL,
+            0xa02aa96b06deb0fdUL, 0xc83553c5c8965d3dUL,
+            0xfa42a8b73abbf48cUL, 0x9c69a97284b578d7UL,
+            0xc38413cf25e2d70dUL, 0xf46518c2ef5b8cd1UL,
+            0x98bf2f79d5993802UL, 0xbeeefb584aff8603UL,
+            0xeeaaba2e5dbf6784UL, 0x952ab45cfa97a0b2UL,
+            0xba756174393d88dfUL, 0xe912b9d1478ceb17UL,
+            0x91abb422ccb812eeUL, 0xb616a12b7fe617aaUL,
+            0xe39c49765fdf9d94UL, 0x8e41ade9fbebc27dUL,
+            0xb1d219647ae6b31cUL, 0xde469fbd99a05fe3UL,
+            0x8aec23d680043beeUL, 0xada72ccc20054ae9UL,
+            0xd910f7ff28069da4UL, 0x87aa9aff79042286UL,
+            0xa99541bf57452b28UL, 0xd3fa922f2d1675f2UL,
+            0x847c9b5d7c2e09b7UL, 0xa59bc234db398c25UL,
+            0xcf02b2c21207ef2eUL, 0x8161afb94b44f57dUL,
+            0xa1ba1ba79e1632dcUL, 0xca28a291859bbf93UL,
+            0xfcb2cb35e702af78UL, 0x9defbf01b061adabUL,
+            0xc56baec21c7a1916UL, 0xf6c69a72a3989f5bUL,
+            0x9a3c2087a63f6399UL, 0xc0cb28a98fcf3c7fUL,
+            0xf0fdf2d3f3c30b9fUL, 0x969eb7c47859e743UL,
+            0xbc4665b596706114UL, 0xeb57ff22fc0c7959UL,
+            0x9316ff75dd87cbd8UL, 0xb7dcbf5354e9beceUL,
+            0xe5d3ef282a242e81UL, 0x8fa475791a569d10UL,
+            0xb38d92d760ec4455UL, 0xe070f78d3927556aUL,
+            0x8c469ab843b89562UL, 0xaf58416654a6babbUL,
+            0xdb2e51bfe9d0696aUL, 0x88fcf317f22241e2UL,
+            0xab3c2fddeeaad25aUL, 0xd60b3bd56a5586f1UL,
+            0x85c7056562757456UL, 0xa738c6bebb12d16cUL,
+            0xd106f86e69d785c7UL, 0x82a45b450226b39cUL,
+            0xa34d721642b06084UL, 0xcc20ce9bd35c78a5UL,
+            0xff290242c83396ceUL, 0x9f79a169bd203e41UL,
+            0xc75809c42c684dd1UL, 0xf92e0c3537826145UL,
+            0x9bbcc7a142b17ccbUL, 0xc2abf989935ddbfeUL,
+            0xf356f7ebf83552feUL, 0x98165af37b2153deUL,
+            0xbe1bf1b059e9a8d6UL, 0xeda2ee1c7064130cUL,
+            0x9485d4d1c63e8be7UL, 0xb9a74a0637ce2ee1UL,
+            0xe8111c87c5c1ba99UL, 0x910ab1d4db9914a0UL,
+            0xb54d5e4a127f59c8UL, 0xe2a0b5dc971f303aUL,
+            0x8da471a9de737e24UL, 0xb10d8e1456105dadUL,
+            0xdd50f1996b947518UL, 0x8a5296ffe33cc92fUL,
+            0xace73cbfdc0bfb7bUL, 0xd8210befd30efa5aUL,
+            0x8714a775e3e95c78UL, 0xa8d9d1535ce3b396UL,
+            0xd31045a8341ca07cUL, 0x83ea2b892091e44dUL,
+            0xa4e4b66b68b65d60UL, 0xce1de40642e3f4b9UL,
+            0x80d2ae83e9ce78f3UL, 0xa1075a24e4421730UL,
+            0xc94930ae1d529cfcUL, 0xfb9b7cd9a4a7443cUL,
+            0x9d412e0806e88aa5UL, 0xc491798a08a2ad4eUL,
+            0xf5b5d7ec8acb58a2UL, 0x9991a6f3d6bf1765UL,
+            0xbff610b0cc6edd3fUL, 0xeff394dcff8a948eUL,
+            0x95f83d0a1fb69cd9UL, 0xbb764c4ca7a4440fUL,
+            0xea53df5fd18d5513UL, 0x92746b9be2f8552cUL,
+            0xb7118682dbb66a77UL, 0xe4d5e82392a40515UL,
+            0x8f05b1163ba6832dUL, 0xb2c71d5bca9023f8UL,
+            0xdf78e4b2bd342cf6UL, 0x8bab8eefb6409c1aUL,
+            0xae9672aba3d0c320UL, 0xda3c0f568cc4f3e8UL,
+            0x8865899617fb1871UL, 0xaa7eebfb9df9de8dUL,
+            0xd51ea6fa85785631UL, 0x8533285c936b35deUL,
+            0xa67ff273b8460356UL, 0xd01fef10a657842cUL,
+            0x8213f56a67f6b29bUL, 0xa298f2c501f45f42UL,
+            0xcb3f2f7642717713UL, 0xfe0efb53d30dd4d7UL,
+            0x9ec95d1463e8a506UL, 0xc67bb4597ce2ce48UL,
+            0xf81aa16fdc1b81daUL, 0x9b10a4e5e9913128UL,
+            0xc1d4ce1f63f57d72UL, 0xf24a01a73cf2dccfUL,
+            0x976e41088617ca01UL, 0xbd49d14aa79dbc82UL,
+            0xec9c459d51852ba2UL, 0x93e1ab8252f33b45UL,
+            0xb8da1662e7b00a17UL, 0xe7109bfba19c0c9dUL,
+            0x906a617d450187e2UL, 0xb484f9dc9641e9daUL,
+            0xe1a63853bbd26451UL, 0x8d07e33455637eb2UL,
+            0xb049dc016abc5e5fUL, 0xdc5c5301c56b75f7UL,
+            0x89b9b3e11b6329baUL, 0xac2820d9623bf429UL,
+            0xd732290fbacaf133UL, 0x867f59a9d4bed6c0UL,
+            0xa81f301449ee8c70UL, 0xd226fc195c6a2f8cUL,
+            0x83585d8fd9c25db7UL, 0xa42e74f3d032f525UL,
+            0xcd3a1230c43fb26fUL, 0x80444b5e7aa7cf85UL,
+            0xa0555e361951c366UL, 0xc86ab5c39fa63440UL,
+            0xfa856334878fc150UL, 0x9c935e00d4b9d8d2UL,
+            0xc3b8358109e84f07UL, 0xf4a642e14c6262c8UL,
+            0x98e7e9cccfbd7dbdUL, 0xbf21e44003acdd2cUL,
+            0xeeea5d5004981478UL, 0x95527a5202df0ccbUL,
+            0xbaa718e68396cffdUL, 0xe950df20247c83fdUL,
+            0x91d28b7416cdd27eUL, 0xb6472e511c81471dUL,
+            0xe3d8f9e563a198e5UL, 0x8e679c2f5e44ff8fUL
+        )
+    }
+}
+
+private inline val Char.isDigit get() = (this - '0').toChar().code < 10
+
+private inline fun charAt(s: CharSequence, index: Int) = if (index < s.length) {
+    s[index]
+} else {
+    '\u0000'
+}
+
+private inline fun fullMultiplicationHighBits(x: Long, y: Long): Long {
+    val xLo = x and 0xffffffffL
+    val xHi = x ushr 32
+    val yLo = y and 0xffffffffL
+    val yHi = y ushr 32
+
+    val xTimesYHi = xHi * yHi
+    val xTimesYMid = xLo * yHi
+    val yTimesXMid = xHi * yLo
+    val xTimesYLo = xLo * yLo
+
+    val carry =
+        yTimesXMid +
+        (xTimesYLo ushr 32) +
+        (xTimesYMid and 0xffffffffL)
+
+    return xTimesYHi + (carry ushr 32) + (xTimesYMid ushr 32)
+}
+
+private inline fun parseFourDigits(str: CharSequence, offset: Int): Int {
+    val v = (str[offset].code.toLong()
+        or (str[offset + 1].code.toLong() shl 16)
+        or (str[offset + 2].code.toLong() shl 32)
+        or (str[offset + 3].code.toLong() shl 48))
+
+    val base = v - 0x0030003000300030L
+    val predicate = v + 0x0046004600460046L or base
+    return if (predicate and 0xff80_ff80_ff80_ff80UL.toLong() != 0L) {
+        -1
+    } else {
+        (base * 0x03e80064000a0001L ushr 48).toInt()
+    }
+}
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
index 97ecc6d..b9546e2 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathNode.kt
@@ -143,159 +143,225 @@
 }
 
 /**
- * Return the corresponding [PathNode] for the given character key if it exists.
+ * Adds the corresponding [PathNode] for the given character key, if it exists, to [nodes].
  * If the key is unknown then [IllegalArgumentException] is thrown
- * @return [PathNode] that matches the key
  * @throws IllegalArgumentException
  */
-internal fun Char.toPathNodes(args: FloatArray): List<PathNode> = when (this) {
-    RelativeCloseKey, CloseKey -> listOf(PathNode.Close)
-    RelativeMoveToKey -> pathNodesFromArgs(args, NUM_MOVE_TO_ARGS) { array ->
-        PathNode.RelativeMoveTo(dx = array[0], dy = array[1])
-    }
+internal fun Char.addPathNodes(nodes: MutableList<PathNode>, args: FloatArray, count: Int) {
+    when (this) {
+        RelativeCloseKey, CloseKey -> nodes.add(PathNode.Close)
 
-    MoveToKey -> pathNodesFromArgs(args, NUM_MOVE_TO_ARGS) { array ->
-        PathNode.MoveTo(x = array[0], y = array[1])
-    }
+        RelativeMoveToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_MOVE_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeMoveTo(dx = array[start], dy = array[start + 1])
+        }
 
-    RelativeLineToKey -> pathNodesFromArgs(args, NUM_LINE_TO_ARGS) { array ->
-        PathNode.RelativeLineTo(dx = array[0], dy = array[1])
-    }
+        MoveToKey -> pathNodesFromArgs(nodes, args, count, NUM_MOVE_TO_ARGS) { array, start ->
+            PathNode.MoveTo(x = array[start], y = array[start + 1])
+        }
 
-    LineToKey -> pathNodesFromArgs(args, NUM_LINE_TO_ARGS) { array ->
-        PathNode.LineTo(x = array[0], y = array[1])
-    }
+        RelativeLineToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_LINE_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeLineTo(dx = array[start], dy = array[start + 1])
+        }
 
-    RelativeHorizontalToKey -> pathNodesFromArgs(args, NUM_HORIZONTAL_TO_ARGS) { array ->
-        PathNode.RelativeHorizontalTo(dx = array[0])
-    }
+        LineToKey -> pathNodesFromArgs(nodes, args, count, NUM_LINE_TO_ARGS) { array, start ->
+            PathNode.LineTo(x = array[start], y = array[start + 1])
+        }
 
-    HorizontalToKey -> pathNodesFromArgs(args, NUM_HORIZONTAL_TO_ARGS) { array ->
-        PathNode.HorizontalTo(x = array[0])
-    }
+        RelativeHorizontalToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_HORIZONTAL_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeHorizontalTo(dx = array[start])
+        }
 
-    RelativeVerticalToKey -> pathNodesFromArgs(args, NUM_VERTICAL_TO_ARGS) { array ->
-        PathNode.RelativeVerticalTo(dy = array[0])
-    }
+        HorizontalToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_HORIZONTAL_TO_ARGS
+        ) { array, start ->
+            PathNode.HorizontalTo(x = array[start])
+        }
 
-    VerticalToKey -> pathNodesFromArgs(args, NUM_VERTICAL_TO_ARGS) { array ->
-        PathNode.VerticalTo(y = array[0])
-    }
+        RelativeVerticalToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_VERTICAL_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeVerticalTo(dy = array[start])
+        }
 
-    RelativeCurveToKey -> pathNodesFromArgs(args, NUM_CURVE_TO_ARGS) { array ->
-        PathNode.RelativeCurveTo(
-            dx1 = array[0],
-            dy1 = array[1],
-            dx2 = array[2],
-            dy2 = array[3],
-            dx3 = array[4],
-            dy3 = array[5]
-        )
-    }
+        VerticalToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_VERTICAL_TO_ARGS
+        ) { array, start ->
+            PathNode.VerticalTo(y = array[start])
+        }
 
-    CurveToKey -> pathNodesFromArgs(args, NUM_CURVE_TO_ARGS) { array ->
-        PathNode.CurveTo(
-            x1 = array[0],
-            y1 = array[1],
-            x2 = array[2],
-            y2 = array[3],
-            x3 = array[4],
-            y3 = array[5]
-        )
-    }
+        RelativeCurveToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_CURVE_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeCurveTo(
+                dx1 = array[start],
+                dy1 = array[start + 1],
+                dx2 = array[start + 2],
+                dy2 = array[start + 3],
+                dx3 = array[start + 4],
+                dy3 = array[start + 5]
+            )
+        }
 
-    RelativeReflectiveCurveToKey -> pathNodesFromArgs(args, NUM_REFLECTIVE_CURVE_TO_ARGS) { array ->
-        PathNode.RelativeReflectiveCurveTo(
-            dx1 = array[0],
-            dy1 = array[1],
-            dx2 = array[2],
-            dy2 = array[3]
-        )
-    }
+        CurveToKey -> pathNodesFromArgs(nodes, args, count, NUM_CURVE_TO_ARGS) { array, start ->
+            PathNode.CurveTo(
+                x1 = array[start],
+                y1 = array[start + 1],
+                x2 = array[start + 2],
+                y2 = array[start + 3],
+                x3 = array[start + 4],
+                y3 = array[start + 5]
+            )
+        }
 
-    ReflectiveCurveToKey -> pathNodesFromArgs(args, NUM_REFLECTIVE_CURVE_TO_ARGS) { array ->
-        PathNode.ReflectiveCurveTo(
-            x1 = array[0],
-            y1 = array[1],
-            x2 = array[2],
-            y2 = array[3]
-        )
-    }
+        RelativeReflectiveCurveToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_REFLECTIVE_CURVE_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeReflectiveCurveTo(
+                dx1 = array[start],
+                dy1 = array[start + 1],
+                dx2 = array[start + 2],
+                dy2 = array[start + 3]
+            )
+        }
 
-    RelativeQuadToKey -> pathNodesFromArgs(args, NUM_QUAD_TO_ARGS) { array ->
-        PathNode.RelativeQuadTo(
-            dx1 = array[0],
-            dy1 = array[1],
-            dx2 = array[2],
-            dy2 = array[3]
-        )
-    }
+        ReflectiveCurveToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_REFLECTIVE_CURVE_TO_ARGS
+        ) { array, start ->
+            PathNode.ReflectiveCurveTo(
+                x1 = array[start],
+                y1 = array[start + 1],
+                x2 = array[start + 2],
+                y2 = array[start + 3]
+            )
+        }
 
-    QuadToKey -> pathNodesFromArgs(args, NUM_QUAD_TO_ARGS) { array ->
-        PathNode.QuadTo(
-            x1 = array[0],
-            y1 = array[1],
-            x2 = array[2],
-            y2 = array[3]
-        )
-    }
+        RelativeQuadToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_QUAD_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeQuadTo(
+                dx1 = array[start],
+                dy1 = array[start + 1],
+                dx2 = array[start + 2],
+                dy2 = array[start + 3]
+            )
+        }
 
-    RelativeReflectiveQuadToKey -> pathNodesFromArgs(args, NUM_REFLECTIVE_QUAD_TO_ARGS) { array ->
-        PathNode.RelativeReflectiveQuadTo(dx = array[0], dy = array[1])
-    }
+        QuadToKey -> pathNodesFromArgs(nodes, args, count, NUM_QUAD_TO_ARGS) { array, start ->
+            PathNode.QuadTo(
+                x1 = array[start],
+                y1 = array[start + 1],
+                x2 = array[start + 2],
+                y2 = array[start + 3]
+            )
+        }
 
-    ReflectiveQuadToKey -> pathNodesFromArgs(args, NUM_REFLECTIVE_QUAD_TO_ARGS) { array ->
-        PathNode.ReflectiveQuadTo(x = array[0], y = array[1])
-    }
+        RelativeReflectiveQuadToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_REFLECTIVE_QUAD_TO_ARGS
+        ) { array, start ->
+            PathNode.RelativeReflectiveQuadTo(dx = array[start], dy = array[start + 1])
+        }
 
-    RelativeArcToKey -> pathNodesFromArgs(args, NUM_ARC_TO_ARGS) { array ->
-        PathNode.RelativeArcTo(
-            horizontalEllipseRadius = array[0],
-            verticalEllipseRadius = array[1],
-            theta = array[2],
-            isMoreThanHalf = array[3].compareTo(0.0f) != 0,
-            isPositiveArc = array[4].compareTo(0.0f) != 0,
-            arcStartDx = array[5],
-            arcStartDy = array[6]
-        )
-    }
+        ReflectiveQuadToKey -> pathNodesFromArgs(
+            nodes,
+            args,
+            count,
+            NUM_REFLECTIVE_QUAD_TO_ARGS
+        ) { array, start ->
+            PathNode.ReflectiveQuadTo(x = array[start], y = array[start + 1])
+        }
 
-    ArcToKey -> pathNodesFromArgs(args, NUM_ARC_TO_ARGS) { array ->
-        PathNode.ArcTo(
-            horizontalEllipseRadius = array[0],
-            verticalEllipseRadius = array[1],
-            theta = array[2],
-            isMoreThanHalf = array[3].compareTo(0.0f) != 0,
-            isPositiveArc = array[4].compareTo(0.0f) != 0,
-            arcStartX = array[5],
-            arcStartY = array[6]
-        )
-    }
+        RelativeArcToKey -> pathNodesFromArgs(nodes, args, count, NUM_ARC_TO_ARGS) { array, start ->
+            PathNode.RelativeArcTo(
+                horizontalEllipseRadius = array[start],
+                verticalEllipseRadius = array[start + 1],
+                theta = array[start + 2],
+                isMoreThanHalf = array[start + 3].compareTo(0.0f) != 0,
+                isPositiveArc = array[start + 4].compareTo(0.0f) != 0,
+                arcStartDx = array[start + 5],
+                arcStartDy = array[start + 6]
+            )
+        }
 
-    else -> throw IllegalArgumentException("Unknown command for: $this")
+        ArcToKey -> pathNodesFromArgs(nodes, args, count, NUM_ARC_TO_ARGS) { array, start ->
+            PathNode.ArcTo(
+                horizontalEllipseRadius = array[start],
+                verticalEllipseRadius = array[start + 1],
+                theta = array[start + 2],
+                isMoreThanHalf = array[start + 3].compareTo(0.0f) != 0,
+                isPositiveArc = array[start + 4].compareTo(0.0f) != 0,
+                arcStartX = array[start + 5],
+                arcStartY = array[start + 6]
+            )
+        }
+
+        else -> throw IllegalArgumentException("Unknown command for: $this")
+    }
 }
 
 private inline fun pathNodesFromArgs(
+    nodes: MutableList<PathNode>,
     args: FloatArray,
+    count: Int,
     numArgs: Int,
-    nodeFor: (subArray: FloatArray) -> PathNode
-): List<PathNode> {
-    return (0..args.size - numArgs step numArgs).map { index ->
-        val subArray = args.copyOfRange(index, index + numArgs)
-        val node = nodeFor(subArray)
-        when {
+    crossinline nodeFor: (subArray: FloatArray, start: Int) -> PathNode
+) {
+    val end = count - numArgs
+    var index = 0
+    while (index <= end) {
+        val node = nodeFor(args, index)
+        nodes.add(when {
             // According to the spec, if a MoveTo is followed by multiple pairs of coordinates,
             // the subsequent pairs are treated as implicit corresponding LineTo commands.
-            node is PathNode.MoveTo && index > 0 -> PathNode.LineTo(subArray[0], subArray[1])
+            node is PathNode.MoveTo && index > 0 -> PathNode.LineTo(args[index], args[index + 1])
             node is PathNode.RelativeMoveTo && index > 0 ->
-                PathNode.RelativeLineTo(subArray[0], subArray[1])
+                PathNode.RelativeLineTo(args[index], args[index + 1])
             else -> node
-        }
+        })
+        index += numArgs
     }
 }
 
 /**
- * Constants used by [Char.toPathNodes] for creating [PathNode]s from parsed paths.
+ * Constants used by [Char.addPathNodes] for creating [PathNode]s from parsed paths.
  */
 private const val RelativeCloseKey = 'z'
 private const val CloseKey = 'Z'
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
index 7f634e8..66f1e24e 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/vector/PathParser.kt
@@ -46,8 +46,9 @@
 import kotlin.math.sqrt
 import kotlin.math.tan
 
-class PathParser {
+internal val EmptyArray = FloatArray(0)
 
+class PathParser {
     private data class PathPoint(var x: Float = 0.0f, var y: Float = 0.0f) {
         fun reset() {
             x = 0.0f
@@ -66,34 +67,85 @@
     private val segmentPoint = PathPoint()
     private val reflectiveCtrlPoint = PathPoint()
 
+    private val floatResult = FloatResult()
+    private var nodeData = FloatArray(64)
+
     /**
      * Parses the path string to create a collection of PathNode instances with their corresponding
      * arguments
-     * throws an IllegalArgumentException or NumberFormatException if the parameters are invalid
      */
     fun parsePathString(pathData: String): PathParser {
         nodes.clear()
 
         var start = 0
-        var end = 1
-        while (end < pathData.length) {
-            end = nextStart(pathData, end)
-            val s = pathData.substring(start, end).trim { it <= ' ' }
-            if (s.isNotEmpty()) {
-                val args = getFloats(s)
-                addNode(s[0], args)
-            }
+        var end = pathData.length
 
-            start = end
-            end++
-        }
-        if (end - start == 1 && start < pathData.length) {
-            addNode(pathData[start], FloatArray(0))
+        // Holds the floats that describe the points for each command
+        var dataCount = 0
+
+        // Trim leading and trailing tabs and spaces
+        while (start < end && pathData[start] <= ' ') start++
+        while (end > start && pathData[end - 1] <= ' ') end--
+
+        var index = start
+        while (index < end) {
+            var c: Char
+            var command = '\u0000'
+
+            // Look for the next command:
+            //     A character that's a lower or upper case letter, but not e or E as those can be
+            //      part of a float literal (e.g. 1.23e-3).
+            do {
+                c = pathData[index++]
+                val lowerChar = c.code or 0x20
+                if ((lowerChar - 'a'.code) * (lowerChar - 'z'.code) <= 0 && lowerChar != 'e'.code) {
+                    command = c
+                    break
+                }
+            } while (index < end)
+
+            // We found a command
+            if (command != '\u0000') {
+                // If the command is a close command (z or Z), we don't need to extract floats,
+                // and can proceed to the next command instead
+                if ((command.code or 0x20) != 'z'.code) {
+                    dataCount = 0
+
+                    do {
+                        // Skip any whitespace
+                        while (index < end && pathData[index] <= ' ') index++
+
+                        // Find the next float and add it to the data array if we got a valid result
+                        // An invalid result could be a malformed float, or simply that we reached
+                        // the end of the list of floats
+                        index = FastFloatParser.nextFloat(pathData, index, end, floatResult)
+
+                        if (floatResult.isValid) {
+                            nodeData[dataCount++] = floatResult.value
+                            resizeNodeData(dataCount)
+                        }
+
+                        // Skip any commas
+                        while (index < end && pathData[index] == ',') index++
+                    } while (index < end && floatResult.isValid)
+                }
+
+                addNodes(command, nodeData, dataCount)
+            }
         }
 
         return this
     }
 
+    @Suppress("NOTHING_TO_INLINE")
+    private inline fun resizeNodeData(dataCount: Int) {
+        if (dataCount >= nodeData.size) {
+            val src = nodeData
+            nodeData = FloatArray(dataCount * 2)
+            src.copyInto(nodeData, 0, 0, src.size)
+        }
+    }
+
     fun addPathNodes(nodes: List<PathNode>): PathParser {
         this.nodes.addAll(nodes)
         return this
@@ -521,125 +573,10 @@
         }
     }
 
-    private fun addNode(cmd: Char, args: FloatArray) {
-        nodes.addAll(cmd.toPathNodes(args))
+    @Suppress("NOTHING_TO_INLINE")
+    private inline fun addNodes(cmd: Char, args: FloatArray, count: Int) {
+        cmd.addPathNodes(nodes, args, count)
     }
 
-    private fun nextStart(s: String, end: Int): Int {
-        var index = end
-        var c: Char
-
-        while (index < s.length) {
-            c = s[index]
-            // Note that 'e' or 'E' are not valid path commands, but could be
-            // used for floating point numbers' scientific notation.
-            // Therefore, when searching for next command, we should ignore 'e'
-            // and 'E'.
-            if (((c - 'A') * (c - 'Z') <= 0 || (c - 'a') * (c - 'z') <= 0) &&
-                c != 'e' && c != 'E'
-            ) {
-                return index
-            }
-            index++
-        }
-        return index
-    }
-
-    private fun getFloats(s: String): FloatArray {
-        if (s[0] == 'z' || s[0] == 'Z') {
-            return FloatArray(0)
-        }
-        val results = FloatArray(s.length)
-        var count = 0
-        var startPosition = 1
-        var endPosition: Int
-
-        val result = ExtractFloatResult()
-        val totalLength = s.length
-
-        // The startPosition should always be the first character of the
-        // current number, and endPosition is the character after the current
-        // number.
-        while (startPosition < totalLength) {
-            extract(s, startPosition, result)
-            endPosition = result.endPosition
-
-            if (startPosition < endPosition) {
-                results[count++] =
-                    s.substring(startPosition, endPosition).toFloat()
-            }
-
-            if (result.endWithNegativeOrDot) {
-                // Keep the '-' or '.' sign with next number.
-                startPosition = endPosition
-            } else {
-                startPosition = endPosition + 1
-            }
-        }
-        return copyOfRange(results, 0, count)
-    }
-
-    private fun copyOfRange(original: FloatArray, start: Int, end: Int): FloatArray {
-        if (start > end) {
-            throw IllegalArgumentException()
-        }
-        val originalLength = original.size
-        if (start < 0 || start > originalLength) {
-            throw IndexOutOfBoundsException()
-        }
-        val resultLength = end - start
-        val copyLength = minOf(resultLength, originalLength - start)
-        val result = FloatArray(resultLength)
-        original.copyInto(result, 0, start, start + copyLength)
-        return result
-    }
-
-    private fun extract(s: String, start: Int, result: ExtractFloatResult) {
-        // Now looking for ' ', ',', '.' or '-' from the start.
-        var currentIndex = start
-        var foundSeparator = false
-        result.endWithNegativeOrDot = false
-        var secondDot = false
-        var isExponential = false
-        while (currentIndex < s.length) {
-            val isPrevExponential = isExponential
-            isExponential = false
-            val currentChar = s[currentIndex]
-            when (currentChar) {
-                ' ', ',' -> foundSeparator = true
-                '-' ->
-                    // The negative sign following a 'e' or 'E' is not a separator.
-                    if (currentIndex != start && !isPrevExponential) {
-                        foundSeparator = true
-                        result.endWithNegativeOrDot = true
-                    }
-                '.' ->
-                    if (!secondDot) {
-                        secondDot = true
-                    } else {
-                        // This is the second dot, and it is considered as a separator.
-                        foundSeparator = true
-                        result.endWithNegativeOrDot = true
-                    }
-                'e', 'E' -> isExponential = true
-            }
-            if (foundSeparator) {
-                break
-            }
-            currentIndex++
-        }
-        // When there is nothing found, then we put the end position to the end
-        // of the string.
-        result.endPosition = currentIndex
-    }
-
-    private data class ExtractFloatResult(
-        // We need to return the position of the next separator and whether the
-        // next float starts with a '-' or a '.'.
-        var endPosition: Int = 0,
-        var endWithNegativeOrDot: Boolean = false
-    )
-
-    private fun Float.toRadians(): Float = this / 180f * PI.toFloat()
     private fun Double.toRadians(): Double = this / 180 * PI
-}
\ No newline at end of file
+}
diff --git a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt
new file mode 100644
index 0000000..d83802a
--- /dev/null
+++ b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/FastFloatParser.kt
@@ -0,0 +1,3376 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.graphics.vector
+
+import kotlin.math.abs
+import kotlin.test.Test
+
+class FastFloatParserTest {
+    @Test
+    fun parseFloats() {
+        val parser = PathParser()
+
+        FloatData.forEach { line ->
+            val (hex, number) = line.split(' ')
+            val bits = hex.toInt(16)
+
+            val nodes = parser.parsePathString("H$number").toNodes()
+
+            assert(nodes.isNotEmpty()) { line }
+
+            val x = (nodes[0] as PathNode.HorizontalTo).x
+
+            assert(abs(x.toBits() - bits) < 2) {
+                "Expected: 0x$bits\n" +
+                "Actual:   0x${x.toBits()}\n" +
+                "    in $line (toFloat() = ${number.toFloat()})"
+            }
+        }
+    }
+}
+
+// Data from https://github.com/fastfloat/supplemental_test_files
+// Under Apache 2.0 license
+private val FloatData = arrayOf(
+    "00000000 .0",
+    "00000000 0",
+    "00000000 0.0",
+    "00000000 0.0000",
+    "00000000 0e1",
+    "00000000 0e2",
+    "00000000 0e3",
+    "00000000 0e4395",
+    "00000000 0e47",
+    "00000000 0e4851",
+    "00000000 0e5",
+    "00000000 0e7",
+    "00000000 0e785",
+    "00000000 0e93",
+    "00000000 0e9999999999999999999999999999",
+    "00000000 1e-324",
+    "00000000 1e-500",
+    "00000000 3e-324",
+    "00000000 4.9406564584124653e-324",
+    "00000000 4.9406564584124654e-324",
+    "00000000 1.00000000000000188558920870223463870174566020691753515394643550663070558368373221" +
+        "972569761144603605635692374830246134201063722058e-309",
+    "00000000 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000002225073858507200889024586876085859" +
+        "887650423112240959465493524802562440009228235695178775888803759155264230978095043431208" +
+        "587738715835729182199302029437922422355981982750124204178896957131179108226104397197960" +
+        "400045489739193807919893608152561311337614984204327175103362739154978273159414382813627" +
+        "511383860409424946494228631669542910508020181592664213499660651780309507591305871984642" +
+        "390606863710200510872328278467884363194451586613504122347901479236958520832159762106637" +
+        "540161373658304419360371477835530668283453563400507407304013560296804637591858316312422" +
+        "452159926254649430083685186171942241764645513713542013221703137049658321015465406803539" +
+        "741790602258950302350193751977303094576317321085250729930508976158251915",
+    "00000000 2.2250738585072009e-308",
+    "00000000 2.2250738585072013e-308",
+    "00000000 2.2250738585072014e-308",
+    "00000000 0.00000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000000000000000000000000000000000000000" +
+        "000000000000000000000000000000000000000000000000000004450147717014402272114819593418263" +
+        "951869639092703291296046852219449644444042153891033059047816270175828298317826079242213" +
+        "740172877389189291055314414815641243486759976282126534658507104573762744298025962244902" +
+        "903779698114444614570510266311510031828794952795966823603998647925096578034214163701381" +
+        "261333311989876551545144031526125381326665295130600018491776632866075559583739224098994" +
+        "780755659409810102161219881460525874257917900007167599934414508608720568157791543592301" +
+        "891033496486942061405218289243144579760516365090360651414037721744226256159024466852576" +
+        "737244643007551333245007965068671949137768847800530996396770975896584413789443379662199" +
+        "396731693628045708486661320679701772891608002069867940855134372886767540",
+    "00000000 1.2e-307",
+    "00000000 1e-300",
+    "00000000 1e-256",
+    "00000000 9007199254740992.e-256",
+    "00000000 1e-128",
+    "00000000 42823146028335318693e-128",
+    "00000000 1e-64",
+    "00000000 7.0060e-46",
+    "00000001 7.0064923216240854e-46",
+    "00000001 1.1754943508e-45",
+    "00000001 1.4012984643e-45",
+    "00000001 0.00000000000000000000000000000000000000000000140129846432481707092372958328991613" +
+        "128026194187651577175706828388979108268586060148663818836212158203125",
+    "007FFFFF 0.00000000000000000000000000000000000001175494210692441075487029444849287348827052" +
+        "428745893333857174530571588870475618904265502351336181163787841796875",
+    "007FFFFF 1.1754942107e-38",
+    "00800000 1.1754943508e-38",
+    "00800000 0.00000000000000000000000000000000000001175494350822287507968736537222245677818665" +
+        "5567720875215087517062784172594547271728515625",
+    "00800003 1.1754947011469036e-38",
+    "00FFFFFF 0.00000000000000000000000000000000000002350988561514728583455765982071533026645717" +
+        "985517980855365926236850006129930346077117064851336181163787841796875",
+    "01000000 2.3509887016445750159374730744444913556373311135441750430175034126e-38",
+    "01800000 4.7019774032891500318749461488889827112746622270883500860350068251e-38",
+    "0A4FB11F 1e-32",
+    "24E69595 1e-16",
+    "29A2212D 7.2e-14",
+    "29E12E13 1e-13",
+    "39102534 0.00013746770127909258",
+    "39BECE41 0.00036393293703440577",
+    "3A6181A6 0.0008602388261351734",
+    "3AD0BAE5 0.0015924838953651488",
+    "3B0D2710 0.002153817447833717",
+    "3B8A536D 0.004221370676532388",
+    "3C467C71 0.012114629615098238",
+    "3CE5EF9A 0.028068351559340954",
+    "3D17CCF0 0.03706067614257336",
+    "3DBE3F17 0.09289376810193062",
+    "3DCCCCCD 0.1",
+    "3E200000 0.15625",
+    "3E200000 0.156250000000000000000000000000000000000000",
+    "3E345144 0.176091259055681",
+    "3E345144 0.1760912590558",
+    "3E5F23F5 0.21791061013936996",
+    "3E943D3B 0.289529654602168",
+    "3E9A209B 0.301029995663981",
+    "3E9A209B 0.30103",
+    "3E9C529D 0.30531780421733856",
+    "3EFFFFFD .4999999",
+    "3F000000 .5",
+    "3F000000 0.5",
+    "3F000002 .5000001",
+    "3F4322D6 0.7622503340244293",
+    "3F800000 1",
+    "3F800000 1e0",
+    "3F800032 1.000006",
+    "3F8147AE 1.01",
+    "3F91EB85 1.14",
+    "3F98089F 1.1877630352973938",
+    "3FA68B6E 1.30113",
+    "3FB0A3D7 1.38",
+    "3FB33333 1.4",
+    "3FC00000 1.5",
+    "3FD9999A 1.7",
+    "3FE66666 1.8",
+    "40000000 2",
+    "40000000 2.0",
+    "40000000 2e0",
+    "400646F1 2.09808",
+    "4019999A 2.4",
+    "402BFB5E 2.687217116355896",
+    "402DF854 2.718281828459045",
+    "402DF854 2.71828182845904523536028747135266249775724709369995",
+    "40400000 3",
+    "40470A3D 3.11",
+    "40490FDB 3.141592653589793",
+    "40490FDB 3.14159265358979323846264338327950288419716939937510",
+    "40490FDB 3.14159265358979323846264338327950288419716939937510582097494459230781640628620899" +
+        "86280348253421170679",
+    "40490FDB 3.14159265359",
+    "40490FF9 3.1416",
+    "404CCCCD 3.2",
+    "40733333 3.8",
+    "4079999A 3.9",
+    "40800000 4",
+    "4083D70A 4.12",
+    "40A00000 5",
+    "40C00000 6",
+    "40C00000 6e0",
+    "40E00000 7",
+    "40E00000 7e0",
+    "40F17C87 7.5464513301849365",
+    "41000000 8",
+    "41100000 9",
+    "41200000 10",
+    "41200000 1e0000001",
+    "41200000 1e01",
+    "41200000 1e1",
+    "41300000 11",
+    "41400000 12",
+    "41500000 13",
+    "415EADE2 13.91745138168335",
+    "41600000 14",
+    "41607AE1 14.03",
+    "41700000 15",
+    "41800000 16",
+    "41880000 17",
+    "418BE43C 17.486443519592285",
+    "41900000 18",
+    "419051EC 18.04",
+    "41980000 19",
+    "41A00000 20",
+    "41A051EC 20.04",
+    "41A80000 21",
+    "41B00000 22",
+    "41B80000 23",
+    "41C00000 24",
+    "41C00000 24e0",
+    "41C6E148 24.86",
+    "41C80000 25",
+    "41D00000 26",
+    "41D00000 26e0",
+    "41D80000 27",
+    "41E00000 28",
+    "41E80000 29",
+    "41F00000 30",
+    "41F00000 3e01",
+    "41F00000 3e1",
+    "41F80000 31",
+    "42000000 32",
+    "42040000 33",
+    "42080000 34",
+    "420C0000 35",
+    "42100000 36",
+    "42140000 37",
+    "42180000 38",
+    "421C0000 39",
+    "42200000 40",
+    "42200000 4e1",
+    "42240000 41",
+    "42280000 42",
+    "422C0000 43",
+    "42300000 44",
+    "42340000 45",
+    "42380000 46",
+    "423C0000 47",
+    "42400000 48",
+    "42440000 49",
+    "42480000 50",
+    "424A70A4 50.61",
+    "424B3F0E 50.811574935913086",
+    "424C0000 51",
+    "42500000 52",
+    "42540000 53",
+    "42580000 54",
+    "425C0000 55",
+    "42600000 56",
+    "42640000 57",
+    "42680000 58",
+    "426C0000 59",
+    "42700000 60",
+    "42700000 6e1",
+    "42740000 61",
+    "42780000 62",
+    "427C0000 63",
+    "42800000 64",
+    "42820000 65",
+    "42840000 66",
+    "42860000 67",
+    "42880000 68",
+    "42887333 68.225",
+    "428A0000 69",
+    "428C0000 70",
+    "428E0000 71",
+    "42900000 72",
+    "42920000 73",
+    "42940000 74",
+    "42960000 75",
+    "42980000 76",
+    "429A0000 77",
+    "429C0000 78",
+    "429E0000 79",
+    "429E0000 79e0",
+    "42A00000 80",
+    "42A00000 8e1",
+    "42A20000 81",
+    "42A40000 82",
+    "42A60000 83",
+    "42A80000 84",
+    "42A80000 84e0",
+    "42AA0000 85",
+    "42AC0000 86",
+    "42AD0F5C 86.53",
+    "42AE0000 87",
+    "42B00000 88",
+    "42B20000 89",
+    "42B40000 90",
+    "42B60000 91",
+    "42B80000 92",
+    "42B80000 92e0",
+    "42BA0000 93",
+    "42BC0000 94",
+    "42BC0000 94e0",
+    "42BE0000 95",
+    "42C00000 96",
+    "42C20000 97",
+    "42C40000 98",
+    "42C60000 99",
+    "42C80000 1.e2",
+    "42C80000 100",
+    "42C80000 10e1",
+    "42C80000 1e2",
+    "42CA0000 101",
+    "42CC0000 102",
+    "42CE0000 103",
+    "42D00000 104",
+    "42D20000 105",
+    "42D40000 106",
+    "42D60000 107",
+    "42D80000 108",
+    "42DA0000 109",
+    "42DC0000 110",
+    "42DE0000 111",
+    "42E00000 112",
+    "42E20000 113",
+    "42E40000 114",
+    "42E60000 115",
+    "42E80000 116",
+    "42EA0000 117",
+    "42EC0000 118",
+    "42EE0000 119",
+    "42F00000 120",
+    "42F20000 121",
+    "42F40000 122",
+    "42F60000 123",
+    "42F80000 124",
+    "42F8566C 124.16878890991211",
+    "42FA0000 125",
+    "42FC0000 126",
+    "42FE0000 127",
+    "43000000 128",
+    "43010000 129",
+    "43020000 130",
+    "43030000 131",
+    "43040000 132",
+    "43050000 133",
+    "43060000 134",
+    "43070000 135",
+    "43080000 136",
+    "43090000 137",
+    "430A0000 138",
+    "430B0000 139",
+    "430C0000 140",
+    "430D0000 141",
+    "430E0000 142",
+    "430F0000 143",
+    "43100000 144",
+    "43110000 145",
+    "43120000 146",
+    "43130000 147",
+    "43140000 148",
+    "43150000 149",
+    "43160000 150",
+    "43170000 151",
+    "43180000 152",
+    "43190000 153",
+    "431A0000 154",
+    "431B0000 155",
+    "431C0000 156",
+    "431D0000 157",
+    "431E0000 158",
+    "431F0000 159",
+    "43200000 160",
+    "43210000 161",
+    "43220000 162",
+    "43230000 163",
+    "43240000 164",
+    "43250000 165",
+    "43260000 166",
+    "43270000 167",
+    "43280000 168",
+    "43290000 169",
+    "432A0000 170",
+    "432B0000 171",
+    "432C0000 172",
+    "432D0000 173",
+    "432E0000 174",
+    "432F0000 175",
+    "43300000 176",
+    "43310000 177",
+    "43320000 178",
+    "43330000 179",
+    "43340000 180",
+    "43350000 181",
+    "43360000 182",
+    "43370000 183",
+    "43380000 184",
+    "43390000 185",
+    "433A0000 186",
+    "433B0000 187",
+    "433C0000 188",
+    "433D0000 189",
+    "433E0000 190",
+    "433F0000 191",
+    "43400000 192",
+    "43410000 193",
+    "43420000 194",
+    "43430000 195",
+    "43440000 196",
+    "43450000 197",
+    "43460000 198",
+    "43470000 199",
+    "43480000 200",
+    "43490000 201",
+    "434A0000 202",
+    "434B0000 203",
+    "434C0000 204",
+    "434D0000 205",
+    "434E0000 206",
+    "434E80CC 206.50310516357422",
+    "434F0000 207",
+    "43500000 208",
+    "43510000 209",
+    "43520000 210",
+    "43530000 211",
+    "43540000 212",
+    "43550000 213",
+    "43560000 214",
+    "43570000 215",
+    "43580000 216",
+    "43590000 217",
+    "435A0000 218",
+    "435B0000 219",
+    "435C0000 220",
+    "435D0000 221",
+    "435E0000 222",
+    "435F0000 223",
+    "43600000 224",
+    "43610000 225",
+    "43620000 226",
+    "43630000 227",
+    "43640000 228",
+    "43650000 229",
+    "43660000 230",
+    "43670000 231",
+    "43680000 232",
+    "43690000 233",
+    "436A0000 234",
+    "436B0000 235",
+    "436C0000 236",
+    "436D0000 237",
+    "436E0000 238",
+    "436F0000 239",
+    "43700000 240",
+    "43710000 241",
+    "43720000 242",
+    "43730000 243",
+    "43740000 244",
+    "43750000 245",
+    "43760000 246",
+    "43770000 247",
+    "43780000 248",
+    "43790000 249",
+    "437A0000 250",
+    "437B0000 251",
+    "437C0000 252",
+    "437D0000 253",
+    "437E0000 254",
+    "437F0000 255",
+    "43800000 256",
+    "43808000 257",
+    "43810000 258",
+    "43818000 259",
+    "43820000 260",
+    "43828000 261",
+    "43830000 262",
+    "43838000 263",
+    "43840000 264",
+    "43848000 265",
+    "43850000 266",
+    "43858000 267",
+    "43860000 268",
+    "43868000 269",
+    "43870000 270",
+    "43878000 271",
+    "43880000 272",
+    "43888000 273",
+    "43890000 274",
+    "43898000 275",
+    "438A0000 276",
+    "438A8000 277",
+    "438B0000 278",
+    "438B8000 279",
+    "438C0000 280",
+    "438C8000 281",
+    "438D0000 282",
+    "438D8000 283",
+    "438E0000 284",
+    "438E8000 285",
+    "438F0000 286",
+    "438F8000 287",
+    "43900000 288",
+    "43908000 289",
+    "43910000 290",
+    "43918000 291",
+    "43920000 292",
+    "43928000 293",
+    "43930000 294",
+    "43932A3D 294.33",
+    "43938000 295",
+    "43940000 296",
+    "43948000 297",
+    "43950000 298",
+    "43958000 299",
+    "43960000 300",
+    "43960000 3e2",
+    "43968000 301",
+    "43970000 302",
+    "43978000 303",
+    "43980000 304",
+    "43988000 305",
+    "43990000 306",
+    "43998000 307",
+    "439A0000 308",
+    "439A8000 309",
+    "439B0000 310",
+    "439B8000 311",
+    "439C0000 312",
+    "439C8000 313",
+    "439D0000 314",
+    "439D8000 315",
+    "439E0000 316",
+    "439E8000 317",
+    "439F0000 318",
+    "439F8000 319",
+    "43A00000 320",
+    "43A00000 32e1",
+    "43A08000 321",
+    "43A10000 322",
+    "43A18000 323",
+    "43A20000 324",
+    "43A28000 325",
+    "43A30000 326",
+    "43A38000 327",
+    "43A40000 328",
+    "43A48000 329",
+    "43A50000 330",
+    "43A58000 331",
+    "43A60000 332",
+    "43A68000 333",
+    "43A70000 334",
+    "43A78000 335",
+    "43A80000 336",
+    "43A88000 337",
+    "43A90000 338",
+    "43A98000 339",
+    "43AA0000 340",
+    "43AA8000 341",
+    "43AB0000 342",
+    "43AB8000 343",
+    "43AC0000 344",
+    "43AC8000 345",
+    "43AD0000 346",
+    "43AD8000 347",
+    "43AE0000 348",
+    "43AE8000 349",
+    "43AF0000 350",
+    "43AF8000 351",
+    "43B00000 352",
+    "43B08000 353",
+    "43B10000 354",
+    "43B18000 355",
+    "43B20000 356",
+    "43B28000 357",
+    "43B30000 358",
+    "43B38000 359",
+    "43B40000 360",
+    "43B48000 361",
+    "43B50000 362",
+    "43B58000 363",
+    "43B60000 364",
+    "43B68000 365",
+    "43B70000 366",
+    "43B78000 367",
+    "43B80000 368",
+    "43B88000 369",
+    "43B90000 370",
+    "43B98000 371",
+    "43BA0000 372",
+    "43BA8000 373",
+    "43BB0000 374",
+    "43BB8000 375",
+    "43BC0000 376",
+    "43BC8000 377",
+    "43BD0000 378",
+    "43BD8000 379",
+    "43BE0000 380",
+    "43BE8000 381",
+    "43BF0000 382",
+    "43BF8000 383",
+    "43C00000 384",
+    "43C08000 385",
+    "43C10000 386",
+    "43C18000 387",
+    "43C20000 388",
+    "43C28000 389",
+    "43C30000 390",
+    "43C38000 391",
+    "43C40000 392",
+    "43C48000 393",
+    "43C50000 394",
+    "43C58000 395",
+    "43C60000 396",
+    "43C68000 397",
+    "43C70000 398",
+    "43C78000 399",
+    "43C80000 400",
+    "43C88000 401",
+    "43C90000 402",
+    "43C98000 403",
+    "43CA0000 404",
+    "43CA8000 405",
+    "43CB0000 406",
+    "43CB8000 407",
+    "43CC0000 408",
+    "43CC8000 409",
+    "43CD0000 410",
+    "43CD8000 411",
+    "43CDF184 411.88682556152344",
+    "43CE0000 412",
+    "43CE8000 413",
+    "43CF0000 414",
+    "43CF8000 415",
+    "43D00000 416",
+    "43D08000 417",
+    "43D10000 418",
+    "43D18000 419",
+    "43D20000 420",
+    "43D28000 421",
+    "43D30000 422",
+    "43D38000 423",
+    "43D40000 424",
+    "43D48000 425",
+    "43D50000 426",
+    "43D58000 427",
+    "43D60000 428",
+    "43D68000 429",
+    "43D70000 430",
+    "43D78000 431",
+    "43D80000 432",
+    "43D88000 433",
+    "43D90000 434",
+    "43D98000 435",
+    "43DA0000 436",
+    "43DA8000 437",
+    "43DB0000 438",
+    "43DB8000 439",
+    "43DC0000 440",
+    "43DC8000 441",
+    "43DD0000 442",
+    "43DD8000 443",
+    "43DE0000 444",
+    "43DE8000 445",
+    "43DF0000 446",
+    "43DF8000 447",
+    "43E00000 448",
+    "43E08000 449",
+    "43E10000 450",
+    "43E18000 451",
+    "43E20000 452",
+    "43E28000 453",
+    "43E30000 454",
+    "43E38000 455",
+    "43E40000 456",
+    "43E48000 457",
+    "43E50000 458",
+    "43E58000 459",
+    "43E60000 460",
+    "43E68000 461",
+    "43E70000 462",
+    "43E78000 463",
+    "43E80000 464",
+    "43E88000 465",
+    "43E90000 466",
+    "43E98000 467",
+    "43EA0000 468",
+    "43EA8000 469",
+    "43EB0000 470",
+    "43EB8000 471",
+    "43EC0000 472",
+    "43EC8000 473",
+    "43ED0000 474",
+    "43ED8000 475",
+    "43EE0000 476",
+    "43EE8000 477",
+    "43EF0000 478",
+    "43EF8000 479",
+    "43F00000 480",
+    "43F08000 481",
+    "43F10000 482",
+    "43F18000 483",
+    "43F20000 484",
+    "43F28000 485",
+    "43F30000 486",
+    "43F38000 487",
+    "43F40000 488",
+    "43F48000 489",
+    "43F50000 490",
+    "43F58000 491",
+    "43F60000 492",
+    "43F68000 493",
+    "43F70000 494",
+    "43F78000 495",
+    "43F80000 496",
+    "43F88000 497",
+    "43F90000 498",
+    "43F98000 499",
+    "43FA0000 500",
+    "43FA8000 501",
+    "43FB0000 502",
+    "43FB8000 503",
+    "43FC0000 504",
+    "43FC8000 505",
+    "43FD0000 506",
+    "43FD8000 507",
+    "43FE0000 508",
+    "43FE8000 509",
+    "43FF0000 510",
+    "43FF8000 511",
+    "44000000 512",
+    "44004000 513",
+    "44008000 514",
+    "4400C000 515",
+    "44010000 516",
+    "44014000 517",
+    "44018000 518",
+    "4401C000 519",
+    "44020000 520",
+    "44024000 521",
+    "44026A3D 521.66",
+    "44028000 522",
+    "4402C000 523",
+    "44030000 524",
+    "44034000 525",
+    "44038000 526",
+    "4403C000 527",
+    "44040000 528",
+    "44044000 529",
+    "44048000 530",
+    "4404C000 531",
+    "44050000 532",
+    "44054000 533",
+    "44058000 534",
+    "4405C000 535",
+    "44060000 536",
+    "44064000 537",
+    "44068000 538",
+    "4406C000 539",
+    "44070000 540",
+    "44074000 541",
+    "44078000 542",
+    "4407C000 543",
+    "44080000 544",
+    "44084000 545",
+    "44088000 546",
+    "4408C000 547",
+    "44090000 548",
+    "44094000 549",
+    "44098000 550",
+    "4409C000 551",
+    "440A0000 552",
+    "440A4000 553",
+    "440A8000 554",
+    "440AC000 555",
+    "440B0000 556",
+    "440B4000 557",
+    "440B8000 558",
+    "440BC000 559",
+    "440C0000 560",
+    "440C4000 561",
+    "440C8000 562",
+    "440CC000 563",
+    "440D0000 564",
+    "440D4000 565",
+    "440D8000 566",
+    "440DC000 567",
+    "440E0000 568",
+    "440E4000 569",
+    "440E8000 570",
+    "440EC000 571",
+    "440F0000 572",
+    "440F4000 573",
+    "440F8000 574",
+    "440FC000 575",
+    "44100000 576",
+    "44104000 577",
+    "44108000 578",
+    "4410C000 579",
+    "44110000 580",
+    "44114000 581",
+    "44118000 582",
+    "4411C000 583",
+    "44120000 584",
+    "44124000 585",
+    "44128000 586",
+    "4412C000 587",
+    "44130000 588",
+    "44134000 589",
+    "44138000 590",
+    "4413C000 591",
+    "44140000 592",
+    "44144000 593",
+    "44148000 594",
+    "4414C000 595",
+    "44150000 596",
+    "44154000 597",
+    "44158000 598",
+    "4415C000 599",
+    "44160000 600",
+    "44160000 6e2",
+    "44164000 601",
+    "44168000 602",
+    "4416C000 603",
+    "44170000 604",
+    "44174000 605",
+    "44178000 606",
+    "4417C000 607",
+    "44180000 608",
+    "44184000 609",
+    "44188000 610",
+    "4418C000 611",
+    "44190000 612",
+    "44194000 613",
+    "44198000 614",
+    "4419C000 615",
+    "441A0000 616",
+    "441A4000 617",
+    "441A8000 618",
+    "441AC000 619",
+    "441B0000 620",
+    "441B4000 621",
+    "441B8000 622",
+    "441BC000 623",
+    "441C0000 624",
+    "441C4000 625",
+    "441C8000 626",
+    "441CC000 627",
+    "441D0000 628",
+    "441D4000 629",
+    "441D8000 630",
+    "441DC000 631",
+    "441E0000 632",
+    "441E4000 633",
+    "441E8000 634",
+    "441EC000 635",
+    "441F0000 636",
+    "441F4000 637",
+    "441F8000 638",
+    "441FC000 639",
+    "44200000 640",
+    "44204000 641",
+    "44208000 642",
+    "4420C000 643",
+    "44210000 644",
+    "44214000 645",
+    "44218000 646",
+    "4421C000 647",
+    "44220000 648",
+    "44224000 649",
+    "44228000 650",
+    "4422C000 651",
+    "44230000 652",
+    "44238000 654",
+    "4423C000 655",
+    "44240000 656",
+    "44244000 657",
+    "44248000 658",
+    "44250000 660",
+    "44254000 661",
+    "44258000 662",
+    "4425C000 663",
+    "44260000 664",
+    "44264000 665",
+    "44268000 666",
+    "4426C000 667",
+    "44270000 668",
+    "44278000 670",
+    "4427C000 671",
+    "44280000 672",
+    "44284000 673",
+    "44288000 674",
+    "4428C000 675",
+    "44294000 677",
+    "44298000 678",
+    "442A0000 680",
+    "442A4000 681",
+    "442B0000 684",
+    "442B4000 685",
+    "442BC000 687",
+    "442C0000 688",
+    "442C4000 689",
+    "442C8000 690",
+    "442CC000 691",
+    "442D0000 692",
+    "442D8000 694",
+    "442DC000 695",
+    "442E0000 696",
+    "442E4000 697",
+    "442E8000 698",
+    "442F0000 700",
+    "442F0000 7e2",
+    "442F4000 701",
+    "442F8000 702",
+    "44300000 704",
+    "44304000 705",
+    "44308000 706",
+    "4430C000 707",
+    "44310000 708",
+    "44314000 709",
+    "44318000 710",
+    "44318000 71e1",
+    "4431C000 711",
+    "44320000 712",
+    "44324000 713",
+    "44328000 714",
+    "4432C000 715",
+    "44330000 716",
+    "44334000 717",
+    "44338000 718",
+    "4433C000 719",
+    "44340000 720",
+    "44344000 721",
+    "44348000 722",
+    "4434C000 723",
+    "44350000 724",
+    "44354000 725",
+    "44358000 726",
+    "4435C000 727",
+    "44360000 728",
+    "44368000 730",
+    "4436C000 731",
+    "44370000 732",
+    "44374000 733",
+    "44378000 734",
+    "4437C000 735",
+    "44380000 736",
+    "44384000 737",
+    "44388000 738",
+    "44390000 740",
+    "44394000 741",
+    "44398000 742",
+    "443A0000 744",
+    "443A4000 745",
+    "443A8000 746",
+    "443AC000 747",
+    "443B0000 748",
+    "443B4000 749",
+    "443B8000 750",
+    "443B8000 75e01",
+    "443BC000 751",
+    "443C0000 752",
+    "443C8000 754",
+    "443CC000 755",
+    "443D0000 756",
+    "443D4000 757",
+    "443D8000 758",
+    "443E0000 760",
+    "443E4000 761",
+    "443EC000 763",
+    "443F0000 764",
+    "443F4000 765",
+    "443F8000 766",
+    "443FC000 767",
+    "44400000 768",
+    "44408000 770",
+    "4440C000 771",
+    "44410000 772",
+    "44414000 773",
+    "44418000 774",
+    "4441C000 775",
+    "44424000 777",
+    "44428000 778",
+    "44430000 780",
+    "44434000 781",
+    "44438000 782",
+    "4443C000 783",
+    "44440000 784",
+    "44444000 785",
+    "44448000 786",
+    "4444C000 787",
+    "44450000 788",
+    "44458000 790",
+    "4445C000 791",
+    "4445C000 791e0",
+    "44464000 793",
+    "44468000 794",
+    "4446C000 795",
+    "44470000 796",
+    "44474000 797",
+    "44478000 798",
+    "44480000 800",
+    "44480000 8e2",
+    "44484000 801",
+    "44488000 802",
+    "4448C000 803",
+    "44490000 804",
+    "44498000 806",
+    "4449C000 807",
+    "444A0000 808",
+    "444A8000 810",
+    "444AC000 811",
+    "444B0000 812",
+    "444B4000 813",
+    "444B8000 814",
+    "444C4000 817",
+    "444C8000 818",
+    "444D0000 820",
+    "444D4000 821",
+    "444DC000 823",
+    "444E0000 824",
+    "444E4000 825",
+    "444E8000 826",
+    "444EC000 827",
+    "444F0000 828",
+    "444F8000 830",
+    "444FC000 831",
+    "44500000 832",
+    "44504000 833",
+    "44508000 834",
+    "4450C000 835",
+    "44510000 836",
+    "44514000 837",
+    "44518000 838",
+    "44520000 840",
+    "44524000 841",
+    "44528000 842",
+    "4452C000 843",
+    "44530000 844",
+    "44538000 846",
+    "4453C000 847",
+    "44540000 848",
+    "44544000 849",
+    "44548000 850",
+    "4454C000 851",
+    "44554000 853",
+    "44558000 854",
+    "4455C000 855",
+    "44564000 857",
+    "44568000 858",
+    "4456C000 859",
+    "44570000 860",
+    "44574000 861",
+    "44578000 862",
+    "4457C000 863",
+    "44580000 864",
+    "44584000 865",
+    "44588000 866",
+    "4458C000 867",
+    "44590000 868",
+    "44594000 869",
+    "44598000 870",
+    "4459C000 871",
+    "445A0000 872",
+    "445A4000 873",
+    "445A8000 874",
+    "445B0000 876",
+    "445B4000 877",
+    "445B8000 878",
+    "445BC000 879",
+    "445C0000 880",
+    "445C4000 881",
+    "445C8000 882",
+    "445CC000 883",
+    "445D0000 884",
+    "445D8000 886",
+    "445DC000 887",
+    "445E0000 888",
+    "445E4000 889",
+    "445E8000 890",
+    "445EC000 891",
+    "445F0000 892",
+    "445F4000 893",
+    "445F8000 894",
+    "44600000 896",
+    "44604000 897",
+    "44608000 898",
+    "4460C000 899",
+    "44610000 900",
+    "44614000 901",
+    "4461C000 903",
+    "44620000 904",
+    "44624000 905",
+    "44628000 906",
+    "4462C000 907",
+    "44630000 908",
+    "44638000 910",
+    "4463C000 911",
+    "44640000 912",
+    "44644000 913",
+    "44648000 914",
+    "44650000 916",
+    "44654000 917",
+    "4465C000 919",
+    "44660000 920",
+    "44664000 921",
+    "44668000 922",
+    "4466C000 923",
+    "44670000 924",
+    "44678000 926",
+    "4467C000 927",
+    "44684000 929",
+    "44688000 930",
+    "44688000 93e1",
+    "4468C000 931",
+    "44694000 933",
+    "44698000 934",
+    "4469C000 935",
+    "4469C000 935e00",
+    "446A0000 936",
+    "446A17B2 936.3702087402344",
+    "446A4000 937",
+    "446B0000 940",
+    "446B4000 941",
+    "446B8000 942",
+    "446BC000 943",
+    "446C0000 944",
+    "446C4000 945",
+    "446C8000 946",
+    "446CC000 947",
+    "446D0000 948",
+    "446D4000 949",
+    "446D8000 950",
+    "446DC000 951",
+    "446E0000 952",
+    "446E4000 953",
+    "446E8000 954",
+    "446EC000 955",
+    "446F0000 956",
+    "446F4000 957",
+    "44700000 960",
+    "44704000 961",
+    "4470C000 963",
+    "44710000 964",
+    "44718000 966",
+    "4471C000 967",
+    "44724000 969",
+    "44728000 970",
+    "4472C000 971",
+    "44730000 972",
+    "44734000 973",
+    "44738000 974",
+    "4473C000 975",
+    "4473C000 975e0",
+    "44740000 976",
+    "44744000 977",
+    "4474C000 979",
+    "44750000 980",
+    "44754000 981",
+    "4475C000 983",
+    "44760000 984",
+    "44768000 986",
+    "4476C000 987",
+    "44774000 989",
+    "44778000 990",
+    "44784000 993",
+    "44788000 994",
+    "4478C000 995",
+    "44790000 996",
+    "44794000 997",
+    "44798000 998",
+    "4479C000 999",
+    "447A0000 1000",
+    "447A0000 1e3",
+    "447AC000 1003",
+    "447B0000 1004",
+    "447B8000 1006",
+    "447BC000 1007",
+    "447C4000 1009",
+    "447C8000 1010",
+    "447D4000 1013",
+    "447D8000 1014",
+    "447E0000 1016",
+    "447E4000 1017",
+    "447EC000 1019",
+    "447F0000 1020",
+    "447F8000 1022",
+    "447FC000 1023",
+    "44800000 1024",
+    "44804000 1026",
+    "44806000 1027",
+    "4480A000 1029",
+    "4480C000 1030",
+    "44812000 1033",
+    "44814000 1034",
+    "44818000 1036",
+    "4481A000 1037",
+    "4481E000 1039",
+    "44820000 1040",
+    "44826000 1043",
+    "44828000 1044",
+    "4482C000 1046",
+    "4482E000 1047",
+    "44832000 1049",
+    "44834000 1050",
+    "4483A000 1053",
+    "4483C000 1054",
+    "44840000 1056",
+    "44842000 1057",
+    "44846000 1059",
+    "44848000 1060",
+    "4484BB85 1061.86",
+    "4484E000 1063",
+    "44850000 1064",
+    "44854000 1066",
+    "44856000 1067",
+    "4485A000 1069",
+    "4485C000 1070",
+    "44860000 1072",
+    "44862000 1073",
+    "44864000 1074",
+    "44866000 1075",
+    "44868000 1076",
+    "4486A000 1077",
+    "4486E000 1079",
+    "44874000 1082",
+    "4487C000 1086",
+    "44882000 1089",
+    "44886000 1091",
+    "44888000 1092",
+    "44890000 1096",
+    "44896000 1099",
+    "4489C000 1102",
+    "448A4000 1106",
+    "448AA000 1109",
+    "448AC000 1110",
+    "448AE000 1111",
+    "448B0000 1112",
+    "448B8000 1116",
+    "448BE000 1119",
+    "448C4000 1122",
+    "448CC000 1126",
+    "448D2000 1129",
+    "448D8000 1132",
+    "448E0000 1136",
+    "44914000 1162",
+    "44916000 1163",
+    "44926000 1171",
+    "4496E000 1207",
+    "44986000 1219",
+    "4499C000 1230",
+    "449FE000 1279",
+    "44A02000 1281",
+    "44AB5DA6 1370.9265747070312",
+    "44ADA000 1389",
+    "44B12000 1417",
+    "44B84000 1474",
+    "44B8C000 1478",
+    "44BCE000 1511",
+    "44BFE000 1535",
+    "44C2C000 1558",
+    "44C36000 1563",
+    "44C3C000 1566",
+    "44C64000 1586",
+    "44C8E000 1607",
+    "44CD2000 1641",
+    "44CFA000 1661",
+    "44CFC000 1662",
+    "44D08000 1668",
+    "44D16000 1675",
+    "44D6A000 1717",
+    "44D90000 1736",
+    "44DCA000 1765",
+    "44DE6000 1779",
+    "44E02000 1793",
+    "44E28000 1812",
+    "44E32000 1817",
+    "44E5E000 1839",
+    "44E6A000 1845",
+    "44E84000 1858",
+    "44E9E000 1871",
+    "44EAA000 1877",
+    "44EF8000 1916",
+    "44F12000 1929",
+    "44F26000 1939",
+    "44F28000 1940",
+    "44F3E000 1951",
+    "44F42000 1953",
+    "44F4C000 1958",
+    "44F8E000 1991",
+    "44F98000 1996",
+    "44F9A000 1997",
+    "44F9C000 1998",
+    "44F9E000 1999",
+    "44FA0000 2000",
+    "44FA2000 2001",
+    "44FA8000 2004",
+    "44FC4148 2018.04",
+    "44FC6000 2019",
+    "44FC8000 2020",
+    "44FC8148 2020.04",
+    "44FD0000 2024",
+    "44FE2000 2033",
+    "44FEA000 2037",
+    "44FFE000 2047",
+    "45015000 2069",
+    "4501E000 2078",
+    "45027000 2087",
+    "45032000 2098",
+    "45069000 2153",
+    "45072000 2162",
+    "45077000 2167",
+    "4508D000 2189",
+    "450AE000 2222",
+    "450C5000 2245",
+    "450D6000 2262",
+    "450E5000 2277",
+    "450E8000 2280",
+    "45100000 2304",
+    "45113000 2323",
+    "4512E000 2350",
+    "4513C000 2364",
+    "45158000 2392",
+    "4515E000 2398",
+    "45171000 2417",
+    "45179000 2425",
+    "4518E000 2446",
+    "451A9000 2473",
+    "451AA000 2474",
+    "451C4000 25e2",
+    "451C7000 2503",
+    "451DD48C 2525.2840576171875",
+    "451E1000 2529",
+    "451FB000 2555",
+    "45201000 2561",
+    "45206000 2566",
+    "4520C000 2572",
+    "4523D000 2621",
+    "45242000 2626",
+    "4525F000 2655",
+    "4528C000 27e2",
+    "452A6000 2726",
+    "452AE000 2734",
+    "452B5000 2741",
+    "452DD000 2781",
+    "452EA000 2794",
+    "452F0000 2800",
+    "452F2000 2802",
+    "452FA000 281e1",
+    "45304000 2820",
+    "45310000 2832",
+    "4531D000 2845",
+    "4531E000 2846",
+    "45322000 2850",
+    "4532E000 2862",
+    "45331000 2865",
+    "4533B000 2875",
+    "45340000 288e1",
+    "4534F000 2895",
+    "4535A000 2906",
+    "45378000 2936",
+    "4537B000 2939",
+    "45392000 2962",
+    "45397000 2967",
+    "4539E000 2974",
+    "453A1000 2977",
+    "453C6000 3014",
+    "453D8000 3032",
+    "453E6000 3046",
+    "453EF000 3055",
+    "453FA000 3066",
+    "453FB000 3067",
+    "45400000 3072",
+    "45401000 3073",
+    "45408000 3080",
+    "4541E000 3102",
+    "45427000 3111",
+    "45428000 3112",
+    "45435000 3125",
+    "45447000 3143",
+    "45487000 3207",
+    "454A4000 3236",
+    "454DD000 3293",
+    "45505000 3333",
+    "45512000 3346",
+    "45520000 3360",
+    "45548000 3400",
+    "4555F000 3423",
+    "45562000 3426",
+    "45582000 3458",
+    "45588000 3464",
+    "4559B000 3483",
+    "455BB000 3515",
+    "455CC000 3532",
+    "455CF000 3535",
+    "45613000 3603",
+    "45618000 3608",
+    "45675000 3701",
+    "4568F000 3727",
+    "456D0000 3792",
+    "45701000 3841",
+    "45723000 3875",
+    "45744000 3908",
+    "45759000 3929",
+    "4578A000 3978",
+    "45795000 3989",
+    "4579F000 3999",
+    "457A0000 4000",
+    "457A0000 4e3",
+    "457AE000 4014",
+    "457BD000 4029",
+    "457E1000 4065",
+    "457E2000 4066",
+    "457F0000 4080",
+    "457FF000 4095",
+    "45800000 4096",
+    "4580E000 4124",
+    "45814000 4136",
+    "4582B800 4183",
+    "4582C000 4184",
+    "4582E800 4189",
+    "4582F800 4191",
+    "45834000 4200",
+    "45838800 4209",
+    "45841000 4226",
+    "4584B800 4247",
+    "45864000 4296",
+    "45875000 4330",
+    "4587E800 4349",
+    "45890000 4384",
+    "45898000 4400",
+    "458A5800 4427",
+    "458AC000 4440",
+    "458AE000 4444",
+    "458B3800 4455",
+    "458D7000 4526",
+    "458ED800 4571",
+    "458FA800 4597",
+    "458FB000 4598",
+    "45902800 4613",
+    "45904800 4617",
+    "4590C000 4632",
+    "45915800 4651",
+    "4591A800 4661",
+    "4591C800 4665",
+    "45921000 4674",
+    "45925000 4682",
+    "4592E000 4700",
+    "45934800 4713",
+    "45939800 4723",
+    "4593A000 4724",
+    "45948800 4753",
+    "45954800 4777",
+    "45965800 4811",
+    "4596A000 4820",
+    "45973000 4838",
+    "45993000 4902",
+    "45999800 4915",
+    "4599E800 4925",
+    "4599F000 4926",
+    "459A0800 4929",
+    "459B6000 4972",
+    "459B8000 4976",
+    "459BF800 4991",
+    "459CB000 5014",
+    "459CE800 5021",
+    "459E9000 5074",
+    "459F0000 5088",
+    "459FC000 5112",
+    "459FD000 5114",
+    "45A10800 5153",
+    "45A18800 5169",
+    "45A1D000 5178",
+    "45A1E000 5180",
+    "45A29000 5202",
+    "45A38000 5232",
+    "45A3A000 5236",
+    "45A58000 5296",
+    "45A5A800 5301",
+    "45A5E000 5308",
+    "45A66000 5324",
+    "45A6B000 5334",
+    "45A77800 5359",
+    "45A83000 5382",
+    "45A8C000 54e2",
+    "45A93800 5415",
+    "45A97800 5423",
+    "45AA0800 5441",
+    "45AC4800 5513",
+    "45ACF800 5535",
+    "45AD9800 5555",
+    "45ADC000 5560",
+    "45AE7800 5583",
+    "45AE9000 5586",
+    "45AF0000 5600",
+    "45AF0800 5601",
+    "45AF2800 5605",
+    "45B02000 5636",
+    "45B06800 5645",
+    "45B1D800 5691",
+    "45B21000 5698",
+    "45B42000 5764",
+    "45B5B000 5814",
+    "45B5F800 5823",
+    "45B6F000 5854",
+    "45B7C000 5880",
+    "45B85800 5899",
+    "45B8E000 5916",
+    "45BA4000 5960",
+    "45BAD800 5979",
+    "45BB7000 5998",
+    "45BBC800 6009",
+    "45BC1800 6019",
+    "45BC9000 6034",
+    "45BCD000 6042",
+    "45BD2800 6053",
+    "45BE2000 6084",
+    "45BF5000 6122",
+    "45C03000 6150",
+    "45C11000 6178",
+    "45C17800 6191",
+    "45C20800 6209",
+    "45C25000 6218",
+    "45C2F800 6239",
+    "45C3B000 6262",
+    "45C41000 6274",
+    "45C51000 6306",
+    "45C53800 6311",
+    "45C574A4 6318.580322265625",
+    "45C5C800 6329",
+    "45C5E000 6332",
+    "45C6C000 6360",
+    "45C7E800 6397",
+    "45C7F000 6398",
+    "45C7F800 6399",
+    "45C82800 6405",
+    "45C84800 6409",
+    "45C89800 6419",
+    "45C8A000 642e1",
+    "45C9B800 6455",
+    "45C9F000 6462",
+    "45CA8800 6481",
+    "45CAF000 6494",
+    "45CB2000 6500",
+    "45CBC800 6521",
+    "45CC1000 653e1",
+    "45CC5000 6538",
+    "45CD2800 6565",
+    "45CEC800 6617",
+    "45D01000 6658",
+    "45D05000 6666",
+    "45D10000 6688",
+    "45D20000 6720",
+    "45D2B000 6742",
+    "45D40000 6784",
+    "45D45000 6794",
+    "45D48000 6800",
+    "45D58000 6832",
+    "45D6C000 6872",
+    "45D85800 6923",
+    "45D97000 6958",
+    "45DAA800 6997",
+    "45DAC000 7e3",
+    "45DAF800 7007",
+    "45DB8800 7025",
+    "45DC6000 7052",
+    "45DC6800 7053",
+    "45DD8000 7088",
+    "45DE2800 7109",
+    "45DE4000 7112",
+    "45DF2800 7141",
+    "45E04800 7177",
+    "45E09800 7187",
+    "45E18000 7216",
+    "45E39000 7282",
+    "45E3B000 7286",
+    "45E57800 7343",
+    "45E5C000 7352",
+    "45E68800 7377",
+    "45E70000 7392",
+    "45E74000 7400",
+    "45E74800 7401",
+    "45E7B800 7415",
+    "45E7C000 7416",
+    "45E7F000 7422",
+    "45E80800 7425",
+    "45E89800 7443",
+    "45E8A000 7444",
+    "45E9B000 7478",
+    "45EA6800 7501",
+    "45EA7800 7503",
+    "45EB7800 7535",
+    "45EBE000 7548",
+    "45EC8800 7569",
+    "45ECB800 7575",
+    "45EDA000 7604",
+    "45EE2800 7621",
+    "45EE5000 7626",
+    "45EEC800 7641",
+    "45EEF800 7647",
+    "45EF0000 7648",
+    "45F0E800 7709",
+    "45F0F800 7711",
+    "45F20000 7744",
+    "45F22800 7749",
+    "45F2C000 7768",
+    "45F30800 7777",
+    "45F35800 7787",
+    "45F39800 7795",
+    "45F41000 7810",
+    "45F45000 7818",
+    "45F5C000 7864",
+    "45F64000 7880",
+    "45F8B000 7958",
+    "45F8B800 7959",
+    "45F96000 7980",
+    "45FA0000 8000",
+    "45FA0800 8001",
+    "45FB3000 8038",
+    "45FBC800 8057",
+    "45FC6800 8077",
+    "45FCB800 8087",
+    "45FD4800 8105",
+    "45FD7800 8111",
+    "45FE0000 8128",
+    "45FEF000 8158",
+    "45FF0800 8161",
+    "46004800 8210",
+    "46005400 8213",
+    "4600F000 8252",
+    "4601B000 8300",
+    "46021800 8326",
+    "46025400 8341",
+    "46026800 8346",
+    "46027400 8349",
+    "4602C800 8370",
+    "4602E400 8377",
+    "4602F000 8380",
+    "46032400 8393",
+    "46032800 8394",
+    "46033000 8396",
+    "46035800 8406",
+    "46037000 8412",
+    "4603B800 8430",
+    "46043400 8461",
+    "4604B400 8493",
+    "4604B800 8494",
+    "46050400 8513",
+    "4605A000 8552",
+    "46060400 8577",
+    "46065C00 8599",
+    "46066C00 8603",
+    "46069000 8612",
+    "46069400 8613",
+    "4606A400 8617",
+    "4606F400 8637",
+    "46071000 8644",
+    "4607D000 8692",
+    "4607E000 8696",
+    "46082800 8714",
+    "4608E800 8762",
+    "46092C00 8779",
+    "4609F400 8829",
+    "460AE000 8888",
+    "460AFC00 8895",
+    "460B6000 8920",
+    "460B6400 8921",
+    "460C1400 8965",
+    "460C2C00 8971",
+    "460CA000 9000",
+    "460CA000 9e3",
+    "460CEC00 9019",
+    "460CFC00 9023",
+    "460D4C00 9043",
+    "460E2000 9096",
+    "460F9000 9188",
+    "46107800 9246",
+    "4610C400 9265",
+    "4610DC00 9271",
+    "4610E400 9273",
+    "46113000 9292",
+    "46119000 9316",
+    "4612C000 9392",
+    "4612D400 9397",
+    "46135800 9430",
+    "46137000 9436",
+    "46140000 9472",
+    "46141400 9477",
+    "46142000 9480",
+    "46143400 9485",
+    "46143800 9486",
+    "46144C00 9491",
+    "46145C00 9495",
+    "46147800 9502",
+    "46148800 9506",
+    "46155800 9558",
+    "46165C00 9623",
+    "46166000 9624",
+    "46171800 9670",
+    "46172000 9672",
+    "46181000 9732",
+    "46184400 9745",
+    "4618F400 9789",
+    "461A9000 9892",
+    "461AE800 9914",
+    "461B5800 9942",
+    "461BC000 9968",
+    "461BEC00 9979",
+    "461C1C00 9991",
+    "461C3C00 9999",
+    "461C4000 10000",
+    "461C4000 1e4",
+    "461E6400 10137",
+    "4620B000 10284",
+    "46258800 10594",
+    "462D9C00 11111",
+    "46333C00 11471",
+    "4634FC00 11583",
+    "463F2000 12232",
+    "46426800 12442",
+    "4647EC00 12795",
+    "464BF000 13052",
+    "46506000 13336",
+    "46592000 13896",
+    "465CB400 14125",
+    "46634400 14545",
+    "4663F000 14588",
+    "466E9C00 15271",
+    "46722974 15498.36376953125",
+    "46742400 15625",
+    "4677D800 15862",
+    "467D3800 16206",
+    "467FF800 16382",
+    "46802FE4 16407.9462890625",
+    "46816200 16561",
+    "4681C200 16609",
+    "4684D000 17e3",
+    "4688174C 17419.6494140625",
+    "468AA400 17746",
+    "46912800 18580",
+    "4693A600 18899",
+    "4694F800 19068",
+    "4695B200 19161",
+    "46984600 19491",
+    "469C3E00 19999",
+    "469CAC00 20054",
+    "469CE400 20082",
+    "469E2E00 20247",
+    "469E6E00 20279",
+    "46A23A00 20765",
+    "46A5AE00 21207",
+    "46A9BE00 21727",
+    "46AB9800 21964",
+    "46AD9C00 22222",
+    "46BA7600 23867",
+    "46BD2200 24209",
+    "46BF0800 24452",
+    "46C40000 25088",
+    "46C80000 256e2",
+    "46C9BE00 25823",
+    "46CA1400 25866",
+    "46CAD200 25961",
+    "46CC8E00 26183",
+    "46CEA600 26451",
+    "46D15A00 26797",
+    "46D1A800 26836",
+    "46D4EE00 27255",
+    "46D54600 27299",
+    "46DAAA00 27989",
+    "46DB4A00 28069",
+    "46DD1800 28300",
+    "46E53600 29339",
+    "46EBA600 30163",
+    "46EBDC00 30190",
+    "46EC162A 30219.0830078125",
+    "46F28A00 31045",
+    "46F68E00 31559",
+    "46F6C600 31587",
+    "46F78000 31680",
+    "47010C00 33036",
+    "4701C500 33221",
+    "47023500 33333",
+    "4703B900 33721",
+    "4703D100 33745",
+    "47074300 34627",
+    "4707FA00 34810",
+    "47091700 35095",
+    "470A6B00 35435",
+    "470D1100 36113",
+    "470D8000 36224",
+    "470E0E00 36366",
+    "470E7F00 36479",
+    "470FE500 36837",
+    "47109600 37014",
+    "4710E000 37088",
+    "4713A900 37801",
+    "4713A900 37801e0",
+    "47160D00 38413",
+    "471ABD00 39613",
+    "471AF900 39673",
+    "471C4000 40000",
+    "471C4000 4e4",
+    "471C8200 40066",
+    "471E4300 40515",
+    "471F8000 40832",
+    "47218200 41346",
+    "4722D600 41686",
+    "47233300 41779",
+    "47241000 42e3",
+    "47269500 42645",
+    "4726D700 42711",
+    "47278900 42889",
+    "47278E00 42894",
+    "47291700 43287",
+    "47291E00 43294",
+    "472B330C 43827.048828125",
+    "472CEF00 44271",
+    "472D9C00 44444",
+    "472ED100 44753",
+    "4732F500 45813",
+    "4735B600 46518",
+    "47370B00 46859",
+    "47382A00 47146",
+    "47395500 47445",
+    "47397F00 47487",
+    "4739CC00 47564",
+    "4739DB00 47579",
+    "473F9500 49045",
+    "47426500 49765",
+    "47435000 5e4",
+    "47448100 50305",
+    "4746B000 50864",
+    "47484300 51267",
+    "4748B900 51385",
+    "47491500 51477",
+    "47495300 51539",
+    "474A6F00 51823",
+    "474A8C00 51852",
+    "474B6000 52064",
+    "474BE000 52192",
+    "474BF200 52210",
+    "474C8B00 52363",
+    "474D0100 52481",
+    "47538600 54150",
+    "47547A00 54394",
+    "47559600 54678",
+    "4755F000 54768",
+    "47571B00 55067",
+    "47583300 55347",
+    "47584800 55368",
+    "4758A800 55464",
+    "47590300 55555",
+    "47595500 55637",
+    "47596500 55653",
+    "475AC800 56008",
+    "475B2900 56105",
+    "475B9C00 56220",
+    "475D8800 56712",
+    "475DD600 56790",
+    "475F9C00 57244",
+    "475FF000 57328",
+    "47606C00 57452",
+    "47615F00 57695",
+    "4762EB00 58091",
+    "47634200 58178",
+    "47634400 58180",
+    "47635800 582e2",
+    "47652400 58660",
+    "476A2800 59944",
+    "476A6000 6e4",
+    "476C4F00 60495",
+    "476D1400 60692",
+    "476E4800 61E3",
+    "476F6100 61281",
+    "476F8100 61313",
+    "47735400 62292",
+    "47749800 62616",
+    "47757400 62836",
+    "4775AE00 62894",
+    "4777C100 63425",
+    "4777D000 63440",
+    "47780D00 63501",
+    "47796D00 63853",
+    "477AB100 64177",
+    "477AE600 6423e1",
+    "477B1B00 64283",
+    "477B4F00 64335",
+    "477CC800 64712",
+    "477D4800 64840",
+    "477F6C00 65388",
+    "47800000 65536",
+    "47809280 65829",
+    "47810E00 66076",
+    "47820A00 66580",
+    "47822500 66634",
+    "47823500 66666",
+    "4782DF00 67006",
+    "4785F400 68584",
+    "47860E80 68637",
+    "4787DC80 69561",
+    "4787ED00 69594",
+    "4788BE80 70013",
+    "47894C80 70297",
+    "4789D280 70565",
+    "478A6500 70858",
+    "478A7000 7088e1",
+    "478B8400 71432",
+    "478C2700 71758",
+    "478D1F80 72255",
+    "478D3800 72304",
+    "478D9880 72497",
+    "478DA200 72516",
+    "478FC480 73609",
+    "478FF400 73704",
+    "47903400 73832",
+    "4790F980 74227",
+    "47928F00 75038",
+    "47938980 75539",
+    "4793F780 75759",
+    "47941080 75809",
+    "47949300 76070",
+    "4794E900 76242",
+    "47959680 76589",
+    "4796B600 77164",
+    "47975680 77485",
+    "4797CB80 77719",
+    "4797E880 77777",
+    "47980600 77836",
+    "47989680 78125",
+    "4798B500 78186",
+    "479A0080 78849",
+    "479A6F80 79071",
+    "479AF900 79346",
+    "479BA380 79687",
+    "479BB400 79720",
+    "479C4000 80000",
+    "479C4000 8e4",
+    "479C5580 80043",
+    "479D0400 80392",
+    "479D1E00 80444",
+    "479D8E00 80668",
+    "479F1F80 81471",
+    "479FD900 81842",
+    "47A1C100 82818",
+    "47A20A00 82964",
+    "47A25B00 83126",
+    "47A29A80 83253",
+    "47A2E200 83396",
+    "47A33000 83552",
+    "47A33080 83553",
+    "47A33E80 83581",
+    "47A34080 83585",
+    "47A53000 84576",
+    "47A56780 84687",
+    "47A5FD80 84987",
+    "47A605A0 85003.24609375",
+    "47A64680 85133",
+    "47A7C900 85906",
+    "47A7CE00 85916",
+    "47A85680 86189",
+    "47A8B400 86376",
+    "47A9C680 86925",
+    "47AA4E00 87196",
+    "47AA7580 87275",
+    "47AAB000 87392",
+    "47AB2480 87625",
+    "47AD5580 88747",
+    "47AD7680 88813",
+    "47AD9C00 88888",
+    "47ADBF00 88958",
+    "47ADE600 89036",
+    "47AEED00 89562",
+    "47AF3480 89705",
+    "47AF7680 89837",
+    "47AFC800 9e4",
+    "47AFF000 90080",
+    "47B0E180 90563",
+    "47B12D80 90715",
+    "47B15D80 90811",
+    "47B18000 90880",
+    "47B18900 90898",
+    "47B27800 91376",
+    "47B28D80 91419",
+    "47B2B780 91503",
+    "47B3E780 92111",
+    "47B4A380 92487",
+    "47B52500 92746",
+    "47B68280 93445",
+    "47B78200 93956",
+    "47B8ED00 94682",
+    "47B96900 94930",
+    "47BA2200 953e2",
+    "47BA2A80 95317",
+    "47BA4200 95364",
+    "47BA9380 95527",
+    "47BB5C00 95928",
+    "47BC0580 96267",
+    "47BC0800 96272",
+    "47BDAB80 97111",
+    "47BDE500 97226",
+    "47BE0200 97284",
+    "47BEDD80 97723",
+    "47BEDE00 97724",
+    "47BEEF00 97758",
+    "47BF6800 98e3",
+    "47BF8A00 98068",
+    "47BFBA80 98165",
+    "47BFBE00 98172",
+    "47C0DD00 98746",
+    "47C16580 99019",
+    "47C19180 99107",
+    "47C26A80 99541",
+    "47C2EA80 99797",
+    "47C34D80 99995",
+    "47C34F80 99999",
+    "47C35000 100000",
+    "47C35000 1e5",
+    "47C65E00 101564",
+    "47C8DE00 102844",
+    "47D2E280 107973",
+    "47D90380 111111",
+    "47DEA800 114000",
+    "47FA0300 128006",
+    "47FFA500 130890",
+    "4807C540 139029",
+    "480F2B80 146606",
+    "48102400 1476e2",
+    "4810F600 148440",
+    "48149A80 152170",
+    "48191B84 156782.0703125",
+    "4819D280 157514",
+    "481AE000 158592",
+    "481B0100 158724",
+    "4825DC00 169840",
+    "482C7B00 176620",
+    "482C9C00 176752",
+    "482DC6C0 177947",
+    "482E3800 178400",
+    "483B2080 191618",
+    "483EC680 195354",
+    "483F90C0 196163",
+    "48435000 200000",
+    "48435000 20e04",
+    "48435000 2e5",
+    "48451A40 201833",
+    "48464BC0 203055",
+    "484CDEC0 209787",
+    "4852D6C0 215899",
+    "48567FC0 219647",
+    "48568100 219652",
+    "48590380 222222",
+    "485919C0 222311",
+    "485CED40 226229",
+    "485D93C0 226895",
+    "485E62C0 227723",
+    "486003C0 229391",
+    "486805C0 237591",
+    "486CD0C0 242499",
+    "486ED800 244576",
+    "48701040 245825",
+    "4870F0C0 246723",
+    "487319C0 248935",
+    "48796E00 255416",
+    "487A5C40 256369",
+    "487C9A40 258665",
+    "487CCD40 258869",
+    "488131A0 264589",
+    "48850A60 272467",
+    "48885FC0 279294",
+    "4888B800 280000",
+    "488D6300 289560",
+    "488DB840 290242",
+    "488E8260 291859",
+    "4891A980 298316",
+    "48927B60 299995",
+    "48933120 301449",
+    "4895D6A0 306869",
+    "4896C580 308780",
+    "48997AA0 314325",
+    "489A6DE0 316271",
+    "489E61A0 324365",
+    "489F7480 326564",
+    "48A057B0 328381.484375",
+    "48A0AE80 329076",
+    "48A269A0 332621",
+    "48A2C2A0 333333",
+    "48A60400 340000",
+    "48A8C580 345644",
+    "48BEBC20 390625",
+    "48C036C0 393654",
+    "48C1D5A0 396973",
+    "48C2E060 399107",
+    "48C35000 400000",
+    "48C35000 4e5",
+    "48C3A380 400668",
+    "48C40980 401484",
+    "48C71160 407691",
+    "48C83200 410000",
+    "48D00460 426019",
+    "48D228C0 430406",
+    "48D38360 433179",
+    "48D48960 435275",
+    "48D635E0 438703",
+    "48D90380 444444",
+    "48D948E0 444999",
+    "48D96B60 445275",
+    "48DBD640 450226",
+    "48DDAD40 453994",
+    "48E0D200 460432",
+    "48E4A8A0 468293",
+    "48E82E40 475506",
+    "48E851E0 475791",
+    "48E983C0 478238",
+    "48E99AE0 478423",
+    "48EAEE40 481138",
+    "48EBC260 482835",
+    "48EC4000 483840",
+    "48ED3860 485827",
+    "48F022C0 491798",
+    "48F42400 50e4",
+    "48F42400 5e5",
+    "48F98480 511012",
+    "48FA53C0 512670",
+    "49037740 538484",
+    "490433D0 541501",
+    "49056290 546345",
+    "49063B70 549815",
+    "4907A230 555555",
+    "490BF660 573286",
+    "490C0000 573440",
+    "490CDE90 577001",
+    "490E2C20 582338",
+    "490E9E60 584166",
+    "490EDB30 585139",
+    "490F8490 587849",
+    "490FDAE0 589230",
+    "49103830 590723",
+    "49115190 595225",
+    "49117750 595829",
+    "49131000 602368",
+    "49146000 607744",
+    "49159960 612758",
+    "491653C0 615740",
+    "4916A280 617e3",
+    "49175E00 620000",
+    "49181480 622920",
+    "491A9CD0 633293",
+    "491AB520 633682",
+    "491AEC40 634564",
+    "491B21E0 635422",
+    "491F74B0 653131",
+    "49209B20 657842",
+    "4920D8C0 658828",
+    "4920DCC0 658892",
+    "49215420 660802",
+    "492191F0 661791",
+    "4922AE70 666343",
+    "4922C2A0 666666",
+    "492349D0 668829",
+    "4925BD00 678864",
+    "492606B0 680043",
+    "4927A1B0 686619",
+    "4927F750 687989",
+    "49298D70 694487",
+    "492A0290 696361",
+    "492A1C60 696774",
+    "492A2830 696963",
+    "492A4300 697392",
+    "492AE600 700000",
+    "492AE600 7e5",
+    "492C5C80 705992",
+    "492DB5B0 711515",
+    "49302EA0 721642",
+    "4932C820 732290",
+    "493380C0 735244",
+    "4935A440 744004",
+    "4936FAD0 749485",
+    "49389CE0 756174",
+    "493A2360 762422",
+    "493AA4A0 764490",
+    "493B55F0 767327",
+    "493B8740 768116",
+    "493B8E20 768226",
+    "493C8F30 772339",
+    "493D3580 775e3",
+    "493DE310 777777",
+    "493E6E00 78e4",
+    "493EFB64 782262.28125",
+    "493F7D10 784337",
+    "493FEE70 786151",
+    "494185B0 792667",
+    "4941BD30 793555",
+    "49423260 795430",
+    "49424B60 795830",
+    "49435000 800000",
+    "49435000 8e5",
+    "4944DD50 806357",
+    "49452670 807527",
+    "4947F330 818995",
+    "49483180 819992",
+    "494A5030 828675",
+    "494AB4B0 830283",
+    "494AC540 830548",
+    "494CAD10 838353",
+    "494DF5A0 843610",
+    "494EDCB0 847307",
+    "494F0490 847945",
+    "495110E0 856334",
+    "4951AA60 858790",
+    "49531EE0 864750",
+    "49535E40 865764",
+    "4953BA90 867241",
+    "49552280 873e03",
+    "49555360 873782",
+    "4956A490 879177",
+    "4957DA60 884134",
+    "4958C540 887892",
+    "4958D480 888136",
+    "49590380 888888",
+    "49595FA0 890362",
+    "49596790 890489",
+    "4959F3B0 892731",
+    "495A9010 895233",
+    "495B05C0 897116",
+    "495B2FD0 897789",
+    "495B9B00 899504",
+    "495BBA00 9e5",
+    "495DD720 908658",
+    "495DE0A0 908810",
+    "495E7330 911155",
+    "495F2100 913936",
+    "495FE390 917049",
+    "4961CFB0 924923",
+    "4962A260 928294",
+    "49641970 934295",
+    "496491B0 936219",
+    "49649330 936243",
+    "49662770 942711",
+    "496719E0 946590",
+    "49672880 946824",
+    "496753E0 947518",
+    "4967A220 948770",
+    "496981F0 956447",
+    "496A1A50 958885",
+    "496B43E0 963646e0",
+    "496C5E30 968163",
+    "496D16F0 971119",
+    "496F1920 979346",
+    "496FB1B0 981787",
+    "49708740 985204",
+    "4970B3B0 985915",
+    "4971AEF0 989935",
+    "49733480 996168",
+    "4973EB20 999090",
+    "497423F0 999999",
+    "49742400 1000000",
+    "49742400 1e6",
+    "497484E0 1001550",
+    "49784FE0 1017086",
+    "49800000 1048576",
+    "49824680 1067216",
+    "49834C80 1075600",
+    "49849078 1085967",
+    "4987A238 1111111",
+    "498A6AB8 1133911",
+    "498AA678 1135823",
+    "49909E68 1184717",
+    "49918F98 1192435",
+    "49920760 1196268",
+    "4996AD40 1234344",
+    "49999C58 1258379",
+    "499B6E60 1273292",
+    "49AA9EF0 1397726",
+    "49ACE2E0 1416284",
+    "49AFEB48 1441129",
+    "49B01F70 1442798",
+    "49B87262 1510988.3125",
+    "49B95070 1518094",
+    "49BEC880 1562896",
+    "49C04738 1575143",
+    "49C06B60 15763e2",
+    "49C9B478 1652367",
+    "49D05330 1706598",
+    "49D17398 1715827",
+    "49E21D50 1852330",
+    "49E5E080 1883152",
+    "49E622C0 1885272",
+    "49E97628 1912517",
+    "49EB0F78 1925615",
+    "49ECF008 1940993",
+    "49EE6B28 1953125",
+    "49F07B58 1970027",
+    "49F42400 2000000",
+    "49F42400 2e6",
+    "49F83168 2033197",
+    "49F9A7F0 2045182",
+    "4A00BFF0 2109436",
+    "4A026540 2136400",
+    "4A033A6C 2150043",
+    "4A04B4A0 2174248",
+    "4A060400 2195712",
+    "4A07A238 2222222",
+    "4A07BF90 22241e2",
+    "4A07DC94 2225957",
+    "4A086E50 2235284",
+    "4A0BBCA0 2289448",
+    "4A0C6D5C 2300759",
+    "4A0C8A60 2302616",
+    "4A0CE124 2308169",
+    "4A0DFAA8 2326186",
+    "4A0EBF40 2338768",
+    "4A0F78D0 2350644",
+    "4A11B7F8 2387454",
+    "4A122A1C 2394759",
+    "4A133D40 2412368",
+    "4A1801B4 2490477",
+    "4A1847A8 2494954",
+    "4A1CBA34 2567821",
+    "4A1DF0A0 2587688",
+    "4A1E41C8 2592882",
+    "4A1EB100 26e5",
+    "4A205244 2626705",
+    "4A21E044 2652177",
+    "4A26648C 2726179",
+    "4A2AC054 2797589",
+    "4A2CB0BC 2829359",
+    "4A2CD31C 2831559",
+    "4A31197C 2901599",
+    "4A348270 2957468",
+    "4A34BF94 2961381",
+    "4A35E3BC 2980079",
+    "4A360ACC 2982579",
+    "4A371B00 3e6",
+    "4A3A8664 3056025",
+    "4A3A9FBC 3057647",
+    "4A3DC308 3109058",
+    "4A3FD4D8 3142966",
+    "4A4542A0 3231912",
+    "4A455580 3233120",
+    "4A4588C0 3236400",
+    "4A4789C8 3269234",
+    "4A4A4C30 3314444",
+    "4A4B7354 3333333",
+    "4A4E0AB0 3375788",
+    "4A4FB144 3402833",
+    "4A559F80 3500000",
+    "4A57F910 35385e2",
+    "4A587710 3546564",
+    "4A59A12C 3565643",
+    "4A5B02BC 3588271",
+    "4A5CA22C 3614859",
+    "4A61D204 3699841",
+    "4A643160 3738712",
+    "4A64E50C 3750211",
+    "4A69C664 3830169",
+    "4A6AEC60 3848984",
+    "4A6BC828 3863050",
+    "4A6C4990 3871332",
+    "4A6D1F44 3885009",
+    "4A6D446C 3887387",
+    "4A6E0D58 3900245.875",
+    "4A6E7C80 3907360",
+    "4A6EE6D0 3914164",
+    "4A6FB810 3927556",
+    "4A71C760 3961304",
+    "4A72DAE4 3978937",
+    "4A73BFD0 3993588",
+    "4A742400 4000000",
+    "4A742400 40e5",
+    "4A764350 4034772",
+    "4A76A054 4040725",
+    "4A76F100 4045888",
+    "4A77067C 4047263",
+    "4A78EF20 4078536",
+    "4A7977F0 4087292",
+    "4A7A4070 4100124",
+    "4A7D6CAC 4152107",
+    "4A7EA8E4 4172345",
+    "4A7F9270 4187292",
+    "4A7FA2A8 4188330",
+    "4A7FBBC0 4189936",
+    "4A809182 4212929",
+    "4A80C4C6 4219491",
+    "4A816500 4240000",
+    "4A81B3C2 4250081",
+    "4A83981E 4312079",
+    "4A83EE56 4323115",
+    "4A8475E6 4340467",
+    "4A852C38 4363804",
+    "4A8535E2 4365041",
+    "4A869D78 4411068",
+    "4A86F0C4 4421730",
+    "4A879868 4443188",
+    "4A87A238 4444444",
+    "4A87A5D4 4444906",
+    "4A8A8CC0 454e4",
+    "4A8AA648 4543268",
+    "4A8C4492 4596297",
+    "4A8C6180 46e5",
+    "4A8C7308 4602244",
+    "4A8CBC8A 4611653",
+    "4A8CE696 4617035",
+    "4A8EDADE 4681071",
+    "4A909D22 4738705",
+    "4A93186E 4820023",
+    "4A95AD2C 4904598",
+    "4A95C034 4907034",
+    "4A96488A 4924485",
+    "4A967D0A 4931205",
+    "4A96D020 4941840",
+    "4A97A78C 4969414",
+    "4A9805CC 4981478",
+    "4A989680 5000000",
+    "4A989680 5e6",
+    "4A98A126 5001363",
+    "4A997A56 5029163",
+    "4A9C0BE2 5113329",
+    "4A9C4A28 51213e2",
+    "4A9D2884 5149762",
+    "4A9D3C3E 5152287",
+    "4A9E6AC2 5191009",
+    "4A9EC846 5202979",
+    "4A9F9B60 523e4",
+    "4AA26E2E 5322519.25",
+    "4AA2AC4E 5330471",
+    "4AA3F010 5371912",
+    "4AA4342C 5380630",
+    "4AA47D50 5389992",
+    "4AA567C0 542e4",
+    "4AA5BE9A 5431117",
+    "4AA851B4 5515482",
+    "4AA98AC6 5555555",
+    "4AAA8446 5587491",
+    "4AAADEDE 5599087",
+    "4AAB717A 5617853",
+    "4AABA8FC 5624958",
+    "4AABAEB6 5625691",
+    "4AAC1C5C 5639726",
+    "4AAC5638 5647132",
+    "4AAC60CC 5648486",
+    "4AADA0CC 5689446",
+    "4AAEBEC2 5726049",
+    "4AAF5F22 5746577",
+    "4AB1200E 5804039",
+    "4AB2AC30 5854744",
+    "4AB365BC 5878494",
+    "4AB42BD6 5903851",
+    "4AB4AA00 592e4",
+    "4AB5D47A 5958205",
+    "4AB5F5C0 5962464",
+    "4AB624EA 5968501",
+    "4AB67498 59787e2",
+    "4AB67EC0 598e4",
+    "4AB6EA94 5993802",
+    "4AB72C96 6002251",
+    "4AB8778E 6044615",
+    "4AB8A82A 6050837",
+    "4AB8F47C 6060606",
+    "4ABAC962 6120625",
+    "4ABAF6A4 6126418",
+    "4ABE7654 6241066",
+    "4ABEED48 6256292",
+    "4AC0B474 6314554",
+    "4AC3864A 6406949",
+    "4AC4FFF8 6455292",
+    "4AC53036 6461467",
+    "4AC6ED2E 6518423",
+    "4AC79534 6539930",
+    "4AC7B6D8 6544236",
+    "4AC80002 6553601",
+    "4AC8C9A6 6579411",
+    "4AC95AAA 6597973",
+    "4ACAD5E2 6646513",
+    "4ACB7354 6666666",
+    "4ACD8D7A 6735549",
+    "4ACDB482 6740545",
+    "4ACE8B80 6768064",
+    "4ACECEAE 6776663",
+    "4ACF4680 6792e3",
+    "4AD21726 6884243",
+    "4AD35CA6 6925907",
+    "4AD391DA 6932717",
+    "4AD3FA36 6946075",
+    "4AD449CA 6956261",
+    "4AD4689E 6960207",
+    "4AD5562C 6990614",
+    "4AD59F80 7e6",
+    "4AD5AA46 7001379",
+    "4AD79484 7064130",
+    "4AD7F70C 7076742",
+    "4AD93EB4 7118682",
+    "4ADA6FE0 7157744",
+    "4ADAEC78 7173692",
+    "4ADB2E88 7182148",
+    "4ADC8C1E 7226895",
+    "4ADE9354 7293354",
+    "4ADEECEE 7304823",
+    "4ADF6380 7320000",
+    "4AE0F386 7371203",
+    "4AE13F14 7380874",
+    "4AE21AA8 7408980",
+    "4AE270D6 7420011",
+    "4AE35622 7449361",
+    "4AE3E6E2 7467889",
+    "4AE44FB4 7481306",
+    "4AE52A58 7509292",
+    "4AE58934 7521434",
+    "4AE664B4 7549530",
+    "4AE88AB0 7619928",
+    "4AE93032 7641113",
+    "4AEA3A4E 7675175",
+    "4AED5BE2 7777777",
+    "4AEE1C9E 7802447",
+    "4AEF0AD0 7832936",
+    "4AF1EA78 7927100",
+    "4AF1FA8C 7929158",
+    "4AF29330 7948696",
+    "4AF315B8 7965404",
+    "4AF3478A 7971781",
+    "4AF42400 8000000",
+    "4AF42400 80e5",
+    "4AF42400 8e6",
+    "4AF5FD28 8060564",
+    "4AF6C0A4 8085586",
+    "4AF6FB7E 8093119",
+    "4AF8F0A8 8157268",
+    "4AF8F8D6 8158315",
+    "4AF93C48 8166948",
+    "4AFC99DE 8277231",
+    "4AFD5B0C 8301958",
+    "4AFE6200 8335616",
+    "4AFFBF4A 8380325",
+    "4AFFEEB6 8386395",
+    "4B000006 8388614.5",
+    "4B00828C 8422028",
+    "4B011844 8460356",
+    "4B01407C 8470652",
+    "4B02273A 8529722",
+    "4B022BC6 8530886",
+    "4B023525 8533285",
+    "4B0250EA 8540394",
+    "4B031558 8590680",
+    "4B03AE76 8629878",
+    "4B045588 8672648",
+    "4B053590 873e4",
+    "4B053BC3 8731587",
+    "4B06CC74 8834164",
+    "4B0746D8 8865496",
+    "4B0767DC 8873948",
+    "4B07A238 8888888",
+    "4B07B00F 8892431",
+    "4B086DB8 8940984",
+    "4B089CC7 8953031",
+    "4B08C382 8962946",
+    "4B090452 8979538",
+    "4B092DFD 8990205",
+    "4B094C1B 8997915",
+    "4B095440 90e5",
+    "4B0A74EA 9073898",
+    "4B0AD089 9097353",
+    "4B0B24CD 9118925",
+    "4B0B5716 9131798",
+    "4B0B7E9B 9141915",
+    "4B0CE24D 9232973",
+    "4B0DA6B5 9283253",
+    "4B0F1B27 9378599",
+    "4B0F27F1 9381873",
+    "4B0F6EC0 94e5",
+    "4B1026B5 9447093",
+    "4B1039CC 9451980",
+    "4B10AECF 9481935",
+    "4B1122D6 9511638",
+    "4B116CC6 9530566",
+    "4B11B8B0 955e4",
+    "4B11F652 9565778",
+    "4B123145 9580869",
+    "4B138D70 967e4",
+    "4B139EFD 9674493",
+    "4B13F52A 9696554",
+    "4B143BC8 9714632",
+    "4B1454CF 9721039",
+    "4B14A8ED 9742573",
+    "4B1502F9 9765625",
+    "4B154D15 9784597",
+    "4B15A07C 9805948",
+    "4B15BCBA 9813178",
+    "4B15DE34 9821748",
+    "4B163AAA 9845418",
+    "4B16ABAB 9874347",
+    "4B16DE4B 9887307",
+    "4B16F230 9892400",
+    "4B1702D3 9896659",
+    "4B170F0D 9899789",
+    "4B174328 9913128",
+    "4B175BA0 9919392",
+    "4B1767F3 9922547",
+    "4B176B01 9923329",
+    "4B17EB48 9956168",
+    "4B1877F8 9992184",
+    "4B18967F 9999999",
+    "4B189680 1e7",
+    "4B1A1A17 10099223",
+    "4B24CA65 10799717",
+    "4B298AC7 11111111",
+    "4B30A3FF 11576319",
+    "4B317911 11630865",
+    "4B3840E2 12075234",
+    "4B40A3B2 12624818",
+    "4B40F04B 12644427",
+    "4B41CE98 12701336",
+    "4B437F39 12812089",
+    "4B4698B8 13015224",
+    "4B472556 13051222",
+    "4B47D6E3 13096675",
+    "4B4891A0 13144480",
+    "4B519C31 13737009",
+    "4B528654 13796948",
+    "4B59132F 14226223",
+    "4B7749E2 16206306",
+    "4B77FFFA 16252921.5",
+    "4B821910 17052193",
+    "4B868F0E 17636892",
+    "4B876520 17746497",
+    "4B87D6B8 17804655",
+    "4B8958EE 18002395",
+    "4B8C3138 18375281",
+    "4B8E6E42 18668677",
+    "4B90E1D8 1899e4",
+    "4B970FE0 198e5",
+    "4B998342 20121220",
+    "4B99AFB0 20143968",
+    "4B9EFAE6 20837836",
+    "4BA0EFEE 21094364",
+    "4BA17304 21161480",
+    "4BA4B683 21589254",
+    "4BA5C352 21726885",
+    "4BA98AC7 22222222",
+    "4BAA977F 22359806",
+    "4BAB74BB 22473078",
+    "4BAC9FDC 22626233",
+    "4BAFF5C0 23063423",
+    "4BB03EC4 23100809",
+    "4BB550B8 23765361",
+    "4BB809B4 24122215",
+    "4BBB7268 24569041",
+    "4BC360D5 25608618",
+    "4BC876A0 26275135",
+    "4BC8865D 26283194",
+    "4BD04730 27299423",
+    "4BD47306 27846156",
+    "4BDC7D50 289e5",
+    "4BDE68C9 29151634",
+    "4BE05D9F 29408062",
+    "4BE4E1C0 3e7",
+    "4BE6DDA7 30260046",
+    "4BE92801 30560258",
+    "4BE9EDCF 30661534",
+    "4BEDEB96 31184683",
+    "4BF36011 31899682",
+    "4BF36954 31904423",
+    "4BF6409F 32276798",
+    "4BF9A486 32721165",
+    "4BFD82E8 33228241",
+    "4BFD8644 33229960",
+    "4BFE502A 33333333",
+    "4C00A562 33723784",
+    "4C033DE4 34404238",
+    "4C051CFE 34894840",
+    "4C0583B0 35e6",
+    "4C083F9D 35716724",
+    "4C09AD09 36090916",
+    "4C09C2FF 36113404",
+    "4C0A499F 36251260",
+    "4C0A8C89 36319780",
+    "4C0DDCCB 37188395",
+    "4C104B98 37826145",
+    "4C111D1D 38040692",
+    "4C13092F 38544571",
+    "4C131A57 38562139",
+    "4C133DE1 38598533",
+    "4C140640 38803710",
+    "4C1421EA 38832042",
+    "4C1793D6 39735126",
+    "4C1798D1 39740229",
+    "4C188B06 39988247",
+    "4C189680 40000000",
+    "4C1930F7 40158172",
+    "4C19402C 40173744",
+    "4C1A22A3 40405643",
+    "4C1A49B5 40445652",
+    "4C1B0974 40642e3",
+    "4C1BA3C0 40800000",
+    "4C1C6710 41000000",
+    "4C1CB407 41078812",
+    "4C1E5D2F 41514172",
+    "4C208432 42078409",
+    "4C2091AA 42092201",
+    "4C21D52C 42423473",
+    "4C22C32A 42667174",
+    "4C22C502 42669063",
+    "4C22F484 42717713",
+    "4C2521EE 43288506",
+    "4C2554BF 43340539",
+    "4C260B66 43527578",
+    "4C267A3A 43641062",
+    "4C27D8C0 44e6",
+    "4C28F9C1 44295939",
+    "4C2974CC 44421934",
+    "4C298AC7 44444444",
+    "4C2BBB93 450187e2",
+    "4C2BCC77 45035996.273704985",
+    "4C2BCC77 45035996.273704995",
+    "4C2EC292 45812296",
+    "4C2F0254 45877585",
+    "4C2FEBAE 46116538",
+    "4C316228 4650e4",
+    "4C3241CA 46729e03",
+    "4C33B3D5 47107924",
+    "4C34C276 47385048",
+    "4C353C7C 4751e4",
+    "4C357ED7 47577948",
+    "4C36E03C 47939822",
+    "4C3776F1 48094147",
+    "4C37879A 48111209",
+    "4C378A28 48113823",
+    "4C38100C 48250926",
+    "4C387BA6 48361110",
+    "4C39852B 48633003",
+    "4C39C698 487e5",
+    "4C3A43B7 48828125",
+    "4C3BA935 49194195",
+    "4C3EBC20 50000000",
+    "4C3EBC20 5e7",
+    "4C3F64A7 50172572",
+    "4C42A86B 51028396",
+    "4C43B3EF 51302332",
+    "4C4ABCB6 53146328",
+    "4C4B5762 53304714",
+    "4C4DFE60 54e6",
+    "4C4FC6F4 54467535",
+    "4C502CDC 54571890",
+    "4C507F98 54656606",
+    "4C50F2F4 54774735",
+    "4C51CEF0 55e6",
+    "4C521B40 55078143",
+    "4C53ED79 55555555",
+    "4C546716 55680090",
+    "4C559698 55990879",
+    "4C559FBD 56000244",
+    "4C57B8EA 56550310",
+    "4C5A5FF1 57245637",
+    "4C5A9928 57304222",
+    "4C5C21BE 57706234",
+    "4C5C633E 57773302",
+    "4C5DE5AC 58169007",
+    "4C5DF914 58188878",
+    "4C5ED784 58416654",
+    "4C5F573E 58547448",
+    "4C614E78 59062754",
+    "4C632038 59539680",
+    "4C644B5C 59846e3",
+    "4C64E1C0 60e6",
+    "4C64E1C0 6e7",
+    "4C689082 60965384",
+    "4C6BDF8A 61832742",
+    "4C6E06FF 62397437",
+    "4C6F6694 62757456",
+    "4C6FCEA0 62864002",
+    "4C701CD2 62944073",
+    "4C705370 63e6",
+    "4C70F7D8 63168350",
+    "4C72CDD2 63649610",
+    "4C737ED7 63830875",
+    "4C742400 64e6",
+    "4C7574BD 64344821",
+    "4C75BDCE 64419642",
+    "4C75D374 64441808",
+    "4C760E08 64501793",
+    "4C77CB28 64957599",
+    "4C77F490 650e5",
+    "4C786B05 651213e2",
+    "4C7C0FBC 66076400",
+    "4C7E502A 66666666",
+    "4C7E94DA 66737e3",
+    "4C805E2A 67301713",
+    "4C81E6AE 68105580",
+    "4C820CD6 68183731",
+    "4C8245D1 68300424",
+    "4C82AFB9 68517318",
+    "4C835DDE 68873971",
+    "4C84774B 69450330",
+    "4C84AA79 69555144",
+    "4C84EA2D 69685606",
+    "4C84EAB0 69686659",
+    "4C8500B2 69731732",
+    "4C856E45 69956135",
+    "4C8583B0 70e6",
+    "4C867FF6 70516656",
+    "4C875EB4 70972830",
+    "4C876BF8 71e6",
+    "4C881818 71352512",
+    "4C8A0C4B 72376922",
+    "4C8B06DC 72890080",
+    "4C8B8DEE 73166703",
+    "4C8C1066 73433906",
+    "4C8D47E6 74071854",
+    "4C8D53A6 74095920",
+    "4C8DE669 74396490",
+    "4C8F7E94 75232413",
+    "4C9000C1 75499016",
+    "4C9086E4 75773725",
+    "4C909486 75801648",
+    "4C916E5A 76247763",
+    "4C91B8B0 76400000",
+    "4C924EDB 76707543",
+    "4C92560D 76722281",
+    "4C9266CE 76756590",
+    "4C931780 77118461",
+    "4C93E007 77529147",
+    "4C94596E 77777777",
+    "4C96C2DE 79042286",
+    "4C977803 79413272",
+    "4C983B64 79813406",
+    "4C98404F 79823479",
+    "4C989680 80000000",
+    "4C989680 80e6",
+    "4C989680 8e7",
+    "4C9902F5 80222122",
+    "4C9C7003 82018330",
+    "4C9DD0C9 82740809",
+    "4C9DD3A0 82746620",
+    "4C9E0069 82838342",
+    "4C9EC178 83233730",
+    "4C9FAA89 83711047",
+    "4C9FB1A1 83725573",
+    "4C9FC9F8 83775423",
+    "4CA049E9 84037448",
+    "4CA15A3B 84595161",
+    "4CA2494E 85084788",
+    "4CA2C726 85342511",
+    "4CA39B68 85777219",
+    "4CA39F84 85785631",
+    "4CA3D982 85904404",
+    "4CA434D9 86091465",
+    "4CA46494 86189216",
+    "4CA4F4BB 86484437",
+    "4CA583B6 86777268",
+    "4CA5DE2A 86962508",
+    "4CA75842 87736852",
+    "4CA772C7 87791158",
+    "4CA79837 87867832",
+    "4CA91A86 88658996",
+    "4CA98AC7 88888888",
+    "4CA9AE6E 88961903",
+    "4CAA0527 89139509",
+    "4CAAF463 89629465",
+    "4CAB2E71 89748360",
+    "4CABAFA0 90012929",
+    "4CAC5B2C 90364256",
+    "4CAD8168 90966848",
+    "4CAE418F 91360377",
+    "4CAE5E60 91419389",
+    "4CAF8C7D 92038121",
+    "4CAFB717 92125366",
+    "4CAFEEC6 92239407",
+    "4CAFFC4E 92267121",
+    "4CB2023C 93327838",
+    "4CB43063 94470938",
+    "4CB54CCD 95053418",
+    "4CB55FC0 95092224",
+    "4CB59EA6 95221044",
+    "4CB6083B 95437272",
+    "4CB6C1FD 95817703",
+    "4CB811DB 96505557",
+    "4CB873C8 96706114",
+    "4CB892F4 96769950",
+    "4CB8CA18 96882881",
+    "4CB8D1AB 96898389",
+    "4CB97520 97233152",
+    "4CBA389F 97633526",
+    "4CBB448C 98182244",
+    "4CBBE9B3 98520472",
+    "4CBCBE20 98955522",
+    "4CBCC438 98968000",
+    "4CBD2764 99171105",
+    "4CBD7B8E 99343471",
+    "4CBD9728 994e5",
+    "4CBEBC20 99999999",
+    "4CBEBC20 100000000",
+    "4CBEBC20 10e7",
+    "4CBEBC20 1e8",
+    "4CD3ED79 111111111",
+    "4CEB79A3 123456789",
+    "4CF1776B 126597973",
+    "4D06794E 141006042",
+    "4D0B1EA5 145877585",
+    "4D0B5A4E 146121952",
+    "4D4EB950 216765690",
+    "4D53ED79 222222222",
+    "4D68D4A5 244140625",
+    "4D81EC22 272467e3",
+    "4D8F0D18 3e8",
+    "4D95E1D4 314325637",
+    "4D9EF21B 333333333",
+    "4DB56464 380406926",
+    "4DBEBC20 4e8",
+    "4DD3ED79 444444444",
+    "4DE4E1C0 480000000",
+    "4DEE6B28 500000000",
+    "4E04746C 555555555",
+    "4E0CD6EE 590723948",
+    "4E0CE5C1 590966848",
+    "4E0E440D 596706114",
+    "4E104361 605083704",
+    "4E15801D 627050305",
+    "4E16FD29 633293366",
+    "4E1EF21B 666666666",
+    "4E26E49C 7e8",
+    "4E2B2F3E 718e6",
+    "4E2BA950 72e7",
+    "4E2DC893 728900802",
+    "4E310CB5 7426e5",
+    "4E344939 756174393",
+    "4E354C05 760414536",
+    "4E396FCA 777777777",
+    "4E3EBC20 800000000",
+    "4E3F43B3 802221226",
+    "4E401940 805720085",
+    "4E465D40 832e6",
+    "4E4A4690 848405530",
+    "4E4C2A88 856334878",
+    "4E4F8C1F 870516656",
+    "4E517E45 878678326",
+    "4E53ED79 888888888",
+    "4E5479AC 891185938",
+    "4E55F9B9 897478238",
+    "4E5693A4 9e08",
+    "4E5693A4 9e8",
+    "4E57C9B2 905079926",
+    "4E58F5FE 91e07",
+    "4E59B814 913179899",
+    "4E6313B1 952429603",
+    "4E64E1C0 96e7",
+    "4E69A674 98e7",
+    "4E6B6C3C 987434744",
+    "4E6C713D 991711052",
+    "4E6E6B28 999999999",
+    "4E6E6B28 1e9",
+    "4E6E6B29 1.00000006e+09",
+    "4E9184E7 1220703125",
+    "4E932C06 1234567890",
+    "4EEE6B28 2e09",
+    "4EFFFFFD 2147483314",
+    "4EFFFFFD 2147483315",
+    "4EFFFFFE 2147483351",
+    "4EFFFFFE 2147483352",
+    "4EFFFFFE 2147483388",
+    "4EFFFFFE 2147483389",
+    "4EFFFFFE 2147483425",
+    "4EFFFFFE 2147483426",
+    "4EFFFFFF 2147483462",
+    "4EFFFFFF 2147483463",
+    "4EFFFFFF 2147483499",
+    "4EFFFFFF 2147483500",
+    "4EFFFFFF 2147483536",
+    "4EFFFFFF 2147483537",
+    "4EFFFFFF 2147483573",
+    "4EFFFFFF 2147483574",
+    "4F000000 2147483610",
+    "4F000000 2147483611",
+    "4F000000 2147483647",
+    "4F000000 2147483648",
+    "4F1502F9 25e8",
+    "4F18542C 2555653131",
+    "4F26C8DF 2798182244",
+    "4F3EBD30 3200069671",
+    "4F41562C 3243650005",
+    "4F44EA11 3303674053",
+    "4F52DEF1 3537826145",
+    "4F6E6B28 4e9",
+    "4F712632 4045812296",
+    "4F820D2F 4363804324",
+    "4F920719 4899877186",
+    "4F9502F9 5e09",
+    "4F9502F9 5e9",
+    "4F9528FA 5004981478",
+    "4F9796DB 5086492111",
+    "4F9923CB 5138519684",
+    "4FA01870 5371912364",
+    "4FA7F185 5635246428",
+    "4FAB42E9 5746577930",
+    "4FB5E621 6103515625",
+    "4FBBC130 63e8",
+    "4FD09DC3 70e8",
+    "4FDC0D5B 7383725573",
+    "4FE3C54F 7642717713",
+    "4FEBBC83 791e07",
+    "4FEE6B28 80e8",
+    "4FEE6B28 8e9",
+    "5000B5F0 8637627989",
+    "50041CB9 8865899617",
+    "5005BC48 8974836059",
+    "50061C46 9e9",
+    "50097D1D 9226712162",
+    "500C1228 94e8",
+    "500C600D 942042e4",
+    "501022C7 9672793580",
+    "50119F04 9772470297",
+    "501502F9 1e10",
+    "5041B710 1.3e10",
+    "5077A845 1662e7",
+    "50B7BD75 24661173473",
+    "50CDA2D2 276e8",
+    "50D59BAF 2867e7",
+    "50E35FA9 30517578125",
+    "50E6F7CF 31e9",
+    "50F52E46 32907604691",
+    "51093AA0 36837130890",
+    "511502F9 40000000000",
+    "51197B62 412e08",
+    "51325DC2 47879823479",
+    "5139F1C6 49914078536",
+    "513DFD64 51000000000",
+    "51492A6A 54e9",
+    "51559F80 573440e5",
+    "515BCAC9 59e9",
+    "51861C46 72E9",
+    "5189D5F3 74000000000",
+    "519A997C 83e9",
+    "51A7A358 90000000000",
+    "51BA43B7 100000000000",
+    "51BA43B7 1e11",
+    "51BC208E 101e9",
+    "520840A3 1463e8",
+    "520E1BCA 152587890625",
+    "523A43B7 200000000000",
+    "5261D86F 242499697392",
+    "5282629A 280000000000",
+    "528BB2C9 3e11",
+    "52BA43B7 400000000000",
+    "52FAFDCF 539e9",
+    "530E81C6 612062576589",
+    "530EA44D 612641865679",
+    "5330644C 757596946075",
+    "5331A2BC 762939453125",
+    "533EEB57 819992132456",
+    "5368D4A5 1e12",
+    "53E8D4A5 2e12",
+    "542E9F7C 3e12",
+    "545E0B6B 3814697265625",
+    "549184E7 5e012",
+    "549BD269 5354e9",
+    "54AC2BC4 59157491e5",
+    "54CD5F7D 7056562757456",
+    "55024FFA 8955e9",
+    "550715AE 92829494e5",
+    "550C4B86 9641e9",
+    "551184E7 10000000000000",
+    "551184E7 1e13",
+    "558AC723 19073486328125",
+    "559184E7 20000000000000",
+    "55CBBA10 28000000000000",
+    "561184E7 40000000000000",
+    "561C4000 42949672960001",
+    "5699E0B1 84595161401484",
+    "56A2D793 89523386091465",
+    "56AD78EC 95367431640625",
+    "56B5E621 1e14",
+    "5783EEC3 290123e9",
+    "579F295D 350000000000000",
+    "57B5E621 400000000000000",
+    "57B5E621 40e13",
+    "57C0D019 424000000000000",
+    "57D4DED7 468107100525890",
+    "57D8D727 476837158203125",
+    "57E35FA9 5e14",
+    "5809CD5F 606060606060606",
+    "58267002 732000000000000",
+    "5835E621 800000000000000",
+    "5835E621 8e14",
+    "58635FA9 1000000000000000",
+    "58635FA9 1e15",
+    "58800000 1125899906842624.125",
+    "58800000 1125899906842901.875",
+    "58956F79 1314448000000000",
+    "59000000 2251799813685248.25",
+    "59000000 2251799813685803.75",
+    "59078678 2384185791015625",
+    "593D7A62 3333333333333333",
+    "59635FA9 4000000000000000",
+    "59800000 4503599627370496.5",
+    "59800000 4503599627370497.5",
+    "59800000 4503599627475352.5",
+    "59800000 4503599627475353.5",
+    "598E1BCA 5000000000000000",
+    "59D529AF 75e14",
+    "59E35FA9 8000000000000000",
+    "5A000000 9007199254740992",
+    "5A000000 9007199254740993",
+    "5A000000 9007199254740994",
+    "5A000000 9007199254740995",
+    "5A09D865 97e14",
+    "5A0CA459 9896800000000000",
+    "5A0E1BCA 1e16",
+    "5A296816 11920928955078125",
+    "5B53C21C 59604644775390625",
+    "5B64158F 642e14",
+    "5BB1A2BC 1e17",
+    "5C845951 298023223876953125",
+    "5CCE8061 465e15",
+    "5D5E0B6B 1e18",
+    "5DA56FA6 1490116119384765625",
+    "5E056279 2402844368454405395.2",
+    "5ECECB8F 7450580596923828125",
+    "5EF9CCD9 9e18",
+    "5F053A0D 96e017",
+    "5F08DFC5 9.862818194192001e18",
+    "5F0AC723 1e19",
+    "60805E9A 74e18",
+    "609C2007 9e19",
+    "60A3959D 943e17",
+    "60AD78EC 1e20",
+    "6258D727 1e21",
+    "64078678 1e22",
+    "657BBCFA 743e20",
+    "659CFBB2 92666518056446206563E3",
+    "65A96816 1e23",
+    "65A96816 9.999999999999999e22",
+    "6723E072 773886e18",
+    "6753C21C 1e24",
+    "6C186875 737e24",
+    "6D1B18AB 3e27",
+    "715C3BD0 1090544144181609348835077142190",
+    "717A6A7F 124e28",
+    "729C8290 62e29",
+    "73FC6F7C 4e31",
+    "745642CC 67902e27",
+    "7487AF20 86e30",
+    "749DC5AE 1e32",
+    "760A0CF8 7e32",
+    "771209B2 29620e29",
+    "777684DF 5e33",
+    "791A130C 5e34",
+    "7E967699 1e38",
+    "7F000000 1.7014118346046923e+38",
+    "7F7FFFFF 3.4028234664e38",
+    "7F7FFFFF 3.4028234665e38",
+    "7F7FFFFF 3.4028234666e38",
+    "7F800000 3.5028234666e38",
+    "7F800000 51823e34",
+    "7F800000 555e36",
+    "7F800000 912e37",
+    "7F800000 92487298e33",
+    "7F800000 778e39",
+    "7F800000 8e41",
+    "7F800000 81e40",
+    "7F800000 90e40",
+    "7F800000 1.7339253062092163730578609458683877051596800000000000000000000000e+42",
+    "7F800000 1778e39",
+    "7F800000 598e40",
+    "7F800000 808e40",
+    "7F800000 203e41",
+    "7F800000 89e42",
+    "7F800000 2e44",
+    "7F800000 5e44",
+    "7F800000 2.1470977154320536489471030463761883783915110400000000000000000000e+45",
+    "7F800000 6e45",
+    "7F800000 2091e44",
+    "7F800000 368e45",
+    "7F800000 967e45",
+    "7F800000 442e46",
+    "7F800000 7442e45",
+    "7F800000 4e49",
+    "7F800000 892091e44",
+    "7F800000 5e50",
+    "7F800000 7e50",
+    "7F800000 2e51",
+    "7F800000 3e51",
+    "7F800000 39e50",
+    "7F800000 7e51",
+    "7F800000 1.9189205311132686907264385602245237137907390376574976000000000000e+52",
+    "7F800000 2.0972622234386619214559824785284023792871122537545728000000000000e+52",
+    "7F800000 1e53",
+    "7F800000 1.7664960224650106892054063261344555646357024359107788800000000000e+53",
+    "7F800000 335e51",
+    "7F800000 2e54",
+    "7F800000 2.8184483231688951563253238886553506793085187889855201280000000000e+54",
+    "7F800000 6472e51",
+    "7F800000 2e55",
+    "7F800000 5e55",
+    "7F800000 5667844e49",
+    "7F800000 62e055",
+    "7F800000 8e056",
+    "7F800000 1.0001803374372191849407179462120053338028379051879898808320000000e+57",
+    "7F800000 5e57",
+    "7F800000 1.8607245283054342363818436991534856973992070520151142825984000000e+58",
+    "7F800000 7.0420557077594588669468784357561207962098443483187940792729600000e+59",
+    "7F800000 9e59",
+    "7F800000 99e59",
+    "7F800000 13e60",
+    "7F800000 4.4900312744003159009338275160799498340862630046359789166919680000e+61",
+    "7F800000 1e64",
+    "7F800000 88e65",
+    "7F800000 3e69",
+    "7F800000 59e68",
+    "7F800000 86e69",
+    "7F800000 2224e68",
+    "7F800000 9e74",
+    "7F800000 42e74",
+    "7F800000 7.2370055773322621e+75",
+    "7F800000 9184e72",
+    "7F800000 93e74",
+    "7F800000 40041e073",
+    "7F800000 7e77",
+    "7F800000 44e80",
+    "7F800000 9e82",
+    "7F800000 242e81",
+    "7F800000 527e81",
+    "7F800000 9e83",
+    "7F800000 9e84",
+    "7F800000 1e85",
+    "7F800000 7e85",
+    "7F800000 1e86",
+    "7F800000 503e085",
+    "7F800000 112e86",
+    "7F800000 6e88",
+    "7F800000 5e89",
+    "7F800000 8358109e84",
+    "7F800000 90054602635948575728E72",
+    "7F800000 9271e88",
+    "7F800000 9153e89",
+    "7F800000 4e093",
+    "7F800000 3e95",
+    "7F800000 3608e92",
+    "7F800000 791e093",
+    "7F800000 8545e94",
+    "7F800000 80e98",
+    "7F800000 980e98",
+    "7F800000 83126e97",
+    "7F800000 7e105",
+    "7F800000 538e122",
+    "7F800000 7e127",
+    "7F800000 1e128",
+    "7F800000 7795e136",
+    "7F800000 7549530e188",
+    "7F800000 71e223",
+    "7F800000 4e250",
+    "7F800000 1e256",
+    "7F800000 1.01e256",
+    "7F800000 67902e276",
+    "7F800000 9748e282",
+    "7F800000 1e300",
+    "7F800000 19e306",
+    "7F800000 1.797693134862315700000000000000001e308",
+    "7F800000 1.7976931348623157e308",
+    "7F800000 1.7976931348623158e308",
+    "7F800000 0.1e310",
+    "7F800000 1.832312213213213232132132143451234453123412321321312e308",
+    "7F800000 1.8e308",
+    "7F800000 1.9e308",
+    "7F800000 1234456789012345678901234567890e9999999999999999999999999999",
+    "7F800000 12e1342",
+    "7F800000 12e13424",
+    "7F800000 148e3032",
+    "7F800000 18e88640",
+    "7F800000 1e1000",
+    "7F800000 1e1853",
+    "7F800000 1e414218",
+    "7F800000 2139879401095466344511101915470454744.9813888656856943E+272",
+    "7F800000 21e440",
+    "7F800000 21e44003",
+    "7F800000 246723473e3813",
+    "7F800000 24e4421730",
+    "7F800000 2529e2734",
+    "7F800000 26e077774",
+    "7F800000 28e2557",
+    "7F800000 2e08987",
+    "7F800000 2e0898765",
+    "7F800000 2e3000",
+    "7F800000 2e30000000000000000",
+    "7F800000 2e69915",
+    "7F800000 2e801",
+    "7F800000 322e62600000",
+    "7F800000 35e702",
+    "7F800000 39e436",
+    "7F800000 3e60868",
+    "7F800000 3e84959",
+    "7F800000 3e8495912",
+    "7F800000 412e0806",
+    "7F800000 4196e952",
+    "7F800000 43e40076",
+    "7F800000 442e4688",
+    "7F800000 44e864",
+    "7F800000 464e3945",
+    "7F800000 473e3813",
+    "7F800000 47859e743",
+    "7F800000 486e494",
+    "7F800000 49e807",
+    "7F800000 50e4395",
+    "7F800000 51e1566",
+    "7F800000 5400e987",
+    "7F800000 549e57273",
+    "7F800000 555e361951",
+    "7F800000 5e330",
+    "7F800000 5e5728",
+    "7F800000 5e620",
+    "7F800000 5e7873",
+    "7F800000 5e823",
+    "7F800000 5e82392",
+    "7F800000 626e974",
+    "7F800000 62e2929",
+    "7F800000 6472e511",
+    "7F800000 696e840",
+    "7F800000 6e188853",
+    "7F800000 6e356932",
+    "7F800000 6e3569326",
+    "7F800000 6e804",
+    "7F800000 6e984",
+    "7F800000 71758e219652",
+    "7F800000 718e68396",
+    "7F800000 71e914",
+    "7F800000 71e91432",
+    "7F800000 7230489e80000",
+    "7F800000 74e608",
+    "7F800000 75e2224",
+    "7F800000 7859e743",
+    "7F800000 7868e94050",
+    "7F800000 788035e61382",
+    "7F800000 79e1632",
+    "7F800000 7e05401",
+    "7F800000 7e12780",
+    "7F800000 7e2000",
+    "7F800000 7e334",
+    "7F800000 7e33455637",
+    "7F800000 7e41392",
+    "7F800000 7e670471",
+    "7F800000 7e67047175",
+    "7F800000 7e998",
+    "7F800000 808e1755",
+    "7F800000 808e17555",
+    "7F800000 83126e978",
+    "7F800000 83e872",
+    "7F800000 8955e946",
+    "7F800000 89e80000",
+    "7F800000 8e1456105",
+    "7F800000 8e43145",
+    "7F800000 8e431456",
+    "7F800000 8e5087",
+    "7F800000 8e5410",
+    "7F800000 8e5410288",
+    "7F800000 8e679",
+    "7F800000 8e88640",
+    "7F800000 8e905",
+    "7F800000 8e921",
+    "7F800000 8e938662",
+    "7F800000 8e938662882",
+    "7F800000 8e952",
+    "7F800000 915e2486",
+    "7F800000 945e455",
+    "7F800000 953e862",
+    "7F800000 953e8624",
+    "7F800000 963e6685",
+    "7F800000 963e66858",
+    "7F800000 964e858",
+    "7F800000 96e952",
+    "7F800000 9748e2826",
+    "7F800000 976e4108",
+    "7F800000 976e41088617",
+    "7F800000 9837e699095",
+    "7F800000 98e94712",
+    "7F800000 98e947129",
+    "7F800000 99e59958885",
+    "7F800000 99e619",
+    "7F800000 9e1632",
+    "7F800000 9e20735",
+    "7F800000 9e40000000",
+    "7F800000 9e563",
+    "7F800000 9e795",
+    "7F800000 9e904",
+)
diff --git a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/PathParserTest.kt b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/PathParserTest.kt
index 9a310b1..1c3db34 100644
--- a/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/PathParserTest.kt
+++ b/compose/ui/ui-graphics/src/commonTest/kotlin/androidx/compose/ui/graphics/vector/PathParserTest.kt
@@ -26,6 +26,40 @@
 import kotlin.test.Test
 
 class PathParserTest {
+    @Test
+    fun negativeExponent() {
+        val linePath = object : TestPath() {
+            var lineToPoints = ArrayList<Offset>()
+
+            override fun lineTo(x: Float, y: Float) {
+                lineToPoints.add(Offset(x, y))
+            }
+        }
+
+        val parser = PathParser()
+        parser.parsePathString("H1e-5").toPath(linePath)
+
+        assertEquals(1, linePath.lineToPoints.size)
+        assertEquals(1e-5f, linePath.lineToPoints[0].x)
+    }
+
+    @Test
+    fun dotDot() {
+        val linePath = object : TestPath() {
+            var lineToPoints = ArrayList<Offset>()
+
+            override fun relativeLineTo(dx: Float, dy: Float) {
+                lineToPoints.add(Offset(dx, dy))
+            }
+        }
+
+        val parser = PathParser()
+        parser.parsePathString("m0 0l2..5").toPath(linePath)
+
+        assertEquals(1, linePath.lineToPoints.size)
+        assertEquals(2.0f, linePath.lineToPoints[0].x)
+        assertEquals(0.5f, linePath.lineToPoints[0].y)
+    }
 
     @Test
     fun relativeQuadToTest() {
diff --git a/compose/ui/ui-inspection/OWNERS b/compose/ui/ui-inspection/OWNERS
index 78dae4b..5548ceb 100644
--- a/compose/ui/ui-inspection/OWNERS
+++ b/compose/ui/ui-inspection/OWNERS
@@ -1,4 +1,4 @@
-davidherman@google.com
+# Bug component: 1333602
 jbakermalone@google.com
 jlauridsen@google.com
 sergeyv@google.com
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index 422f06f..e218a96 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -444,6 +444,12 @@
     property public final long size;
   }
 
+  @androidx.compose.runtime.Immutable public final class TextMeasurer {
+    ctor public TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver fallbackFontFamilyResolver, androidx.compose.ui.unit.Density fallbackDensity, androidx.compose.ui.unit.LayoutDirection fallbackLayoutDirection, optional int cacheSize);
+    method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
+    method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(String text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
+  }
+
   public final class TextPainter {
     method public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.text.TextLayoutResult textLayoutResult);
     field public static final androidx.compose.ui.text.TextPainter INSTANCE;
diff --git a/compose/ui/ui-text/api/public_plus_experimental_current.txt b/compose/ui/ui-text/api/public_plus_experimental_current.txt
index 54a842a..da4f33e 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -475,7 +475,7 @@
     property public final long size;
   }
 
-  @androidx.compose.runtime.Immutable @androidx.compose.ui.text.ExperimentalTextApi public final class TextMeasurer {
+  @androidx.compose.runtime.Immutable public final class TextMeasurer {
     ctor public TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver fallbackFontFamilyResolver, androidx.compose.ui.unit.Density fallbackDensity, androidx.compose.ui.unit.LayoutDirection fallbackLayoutDirection, optional int cacheSize);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
     method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(String text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index 422f06f..e218a96 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -444,6 +444,12 @@
     property public final long size;
   }
 
+  @androidx.compose.runtime.Immutable public final class TextMeasurer {
+    ctor public TextMeasurer(androidx.compose.ui.text.font.FontFamily.Resolver fallbackFontFamilyResolver, androidx.compose.ui.unit.Density fallbackDensity, androidx.compose.ui.unit.LayoutDirection fallbackLayoutDirection, optional int cacheSize);
+    method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(androidx.compose.ui.text.AnnotatedString text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional java.util.List<androidx.compose.ui.text.AnnotatedString.Range<androidx.compose.ui.text.Placeholder>> placeholders, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
+    method @androidx.compose.runtime.Stable public androidx.compose.ui.text.TextLayoutResult measure(String text, optional androidx.compose.ui.text.TextStyle style, optional int overflow, optional boolean softWrap, optional int maxLines, optional long constraints, optional androidx.compose.ui.unit.LayoutDirection layoutDirection, optional androidx.compose.ui.unit.Density density, optional androidx.compose.ui.text.font.FontFamily.Resolver fontFamilyResolver, optional boolean skipCache);
+  }
+
   public final class TextPainter {
     method public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.text.TextLayoutResult textLayoutResult);
     field public static final androidx.compose.ui.text.TextPainter INSTANCE;
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
index 9ca36a4..53effe0 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurer.kt
@@ -79,7 +79,6 @@
  * other layout affecting input, cache can be skipped because most repeated measure calls would miss
  * the cache.
  */
-@ExperimentalTextApi
 @Immutable
 class TextMeasurer constructor(
     private val fallbackFontFamilyResolver: FontFamily.Resolver,
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditCommand.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditCommand.kt
index eeb8ba6..1b03ee1 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditCommand.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/EditCommand.kt
@@ -263,15 +263,15 @@
     }
 
     override fun applyTo(buffer: EditingBuffer) {
-        buffer.delete(
-            buffer.selectionEnd,
-            minOf(buffer.selectionEnd + lengthAfterCursor, buffer.length)
-        )
+        // calculate the end with safe addition since lengthAfterCursor can be set to e.g. Int.MAX
+        // by the input
+        val end = buffer.selectionEnd.addExactOrElse(lengthAfterCursor) { buffer.length }
+        buffer.delete(buffer.selectionEnd, minOf(end, buffer.length))
 
-        buffer.delete(
-            maxOf(0, buffer.selectionStart - lengthBeforeCursor),
-            buffer.selectionStart
-        )
+        // calculate the start with safe subtraction since lengthBeforeCursor can be set to e.g.
+        // Int.MAX by the input
+        val start = buffer.selectionStart.subtractExactOrElse(lengthBeforeCursor) { 0 }
+        buffer.delete(maxOf(0, start), buffer.selectionStart)
     }
 
     override fun equals(other: Any?): Boolean {
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/MathUtils.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/MathUtils.kt
new file mode 100644
index 0000000..68dbd15
--- /dev/null
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/input/MathUtils.kt
@@ -0,0 +1,36 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.ui.text.input
+
+/**
+ * Adds [this] and [right], and if an overflow occurs returns result of [defaultValue].
+ */
+internal inline fun Int.addExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this + right
+    // HD 2-12 Overflow iff both arguments have the opposite sign of the result
+    return if (this xor result and (right xor result) < 0) defaultValue() else result
+}
+
+/**
+ * Subtracts [right] from [this], and if an overflow occurs returns result of [defaultValue].
+ */
+internal fun Int.subtractExactOrElse(right: Int, defaultValue: () -> Int): Int {
+    val result = this - right
+    // HD 2-12 Overflow iff the arguments have different signs and
+    // the sign of the result is different from the sign of x
+    return if (this xor right and (this xor result) < 0) defaultValue() else result
+}
diff --git a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
index 64b4ea9..b772c2f 100644
--- a/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
+++ b/compose/ui/ui-text/src/desktopTest/kotlin/androidx/compose/ui/text/DesktopParagraphTest.kt
@@ -77,6 +77,7 @@
     }
 
     @Test
+    @Ignore("b/271123970 Fails in AOSP. Will be fixed after upstreaming Compose for Desktop")
     fun getBoundingBox_multicodepoints() {
         assumeTrue(isLinux)
         with(defaultDensity) {
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextCommandTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextCommandTest.kt
index 713c016..fe3eea8 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextCommandTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextCommandTest.kt
@@ -235,4 +235,66 @@
         }
         assertThat(error).hasMessageThat().contains("-42")
     }
+
+    @Test
+    fun deletes_whenLengthAfterCursorOverflows_withMaxValue() {
+        val text = "abcde"
+        val textAfterDelete = "abcd"
+        val selection = TextRange(textAfterDelete.length)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextCommand(
+            lengthBeforeCursor = 0,
+            lengthAfterCursor = Int.MAX_VALUE
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo(textAfterDelete)
+        assertThat(eb.cursor).isEqualTo(textAfterDelete.length)
+    }
+
+    @Test
+    fun deletes_whenLengthBeforeCursorOverflows_withMaxValue() {
+        val text = "abcde"
+        val selection = TextRange(1)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextCommand(
+            lengthBeforeCursor = Int.MAX_VALUE,
+            lengthAfterCursor = 0
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo("bcde")
+        assertThat(eb.cursor).isEqualTo(0)
+    }
+
+    @Test
+    fun deletes_whenLengthAfterCursorOverflows() {
+        val text = "abcde"
+        val textAfterDelete = "abcd"
+        val selection = TextRange(textAfterDelete.length)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextCommand(
+            lengthBeforeCursor = 0,
+            lengthAfterCursor = Int.MAX_VALUE - 1
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo(textAfterDelete)
+        assertThat(eb.cursor).isEqualTo(textAfterDelete.length)
+    }
+
+    @Test
+    fun deletes_whenLengthBeforeCursorOverflows() {
+        val text = "abcde"
+        val selection = TextRange(1)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextCommand(
+            lengthBeforeCursor = Int.MAX_VALUE - 1,
+            lengthAfterCursor = 0
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo("bcde")
+        assertThat(eb.cursor).isEqualTo(0)
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextInCodePointsCommandTest.kt b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextInCodePointsCommandTest.kt
index 0f4d16a..72d1ccf4 100644
--- a/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextInCodePointsCommandTest.kt
+++ b/compose/ui/ui-text/src/test/java/androidx/compose/ui/text/input/DeleteSurroundingTextInCodePointsCommandTest.kt
@@ -246,4 +246,66 @@
         }
         assertThat(error).hasMessageThat().contains("-42")
     }
+
+    @Test
+    fun deletes_whenLengthAfterCursorOverflows_withMaxValue() {
+        val text = "abcde"
+        val textAfterDelete = "abcd"
+        val selection = TextRange(textAfterDelete.length)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextInCodePointsCommand(
+            lengthBeforeCursor = 0,
+            lengthAfterCursor = Int.MAX_VALUE
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo(textAfterDelete)
+        assertThat(eb.cursor).isEqualTo(textAfterDelete.length)
+    }
+
+    @Test
+    fun deletes_whenLengthBeforeCursorOverflows_withMaxValue() {
+        val text = "abcde"
+        val selection = TextRange(1)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextInCodePointsCommand(
+            lengthBeforeCursor = Int.MAX_VALUE,
+            lengthAfterCursor = 0
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo("bcde")
+        assertThat(eb.cursor).isEqualTo(0)
+    }
+
+    @Test
+    fun deletes_whenLengthAfterCursorOverflows() {
+        val text = "abcde"
+        val textAfterDelete = "abcd"
+        val selection = TextRange(textAfterDelete.length)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextInCodePointsCommand(
+            lengthBeforeCursor = 0,
+            lengthAfterCursor = Int.MAX_VALUE - 1
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo(textAfterDelete)
+        assertThat(eb.cursor).isEqualTo(textAfterDelete.length)
+    }
+
+    @Test
+    fun deletes_whenLengthBeforeCursorOverflows() {
+        val text = "abcde"
+        val selection = TextRange(1)
+        val eb = EditingBuffer(text, selection)
+
+        DeleteSurroundingTextInCodePointsCommand(
+            lengthBeforeCursor = Int.MAX_VALUE - 1,
+            lengthAfterCursor = 0
+        ).applyTo(eb)
+
+        assertThat(eb.toString()).isEqualTo("bcde")
+        assertThat(eb.cursor).isEqualTo(0)
+    }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 60324d1..507d5fcf 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -38,10 +38,10 @@
         api(project(":compose:ui:ui"))
         api(project(":compose:ui:ui-tooling-preview"))
         api(project(":compose:ui:ui-tooling-data"))
-        implementation("androidx.savedstate:savedstate-ktx:1.2.0")
+        implementation("androidx.savedstate:savedstate-ktx:1.2.1")
         implementation("androidx.compose.material:material:1.0.0")
         implementation("androidx.activity:activity-compose:1.7.0")
-        implementation("androidx.lifecycle:lifecycle-common:2.6.0")
+        implementation("androidx.lifecycle:lifecycle-common:2.6.1")
 
         // kotlin-reflect and animation-tooling-internal are provided by Studio at runtime
         compileOnly(project(":compose:animation:animation-tooling-internal"))
@@ -58,7 +58,7 @@
         androidTestImplementation(libs.truth)
         androidTestImplementation(libs.kotlinReflect)
         androidTestImplementation(project(":compose:animation:animation-tooling-internal"))
-        androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0")
+        androidTestImplementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
         androidTestImplementation(project(":compose:runtime:runtime-livedata"))
     }
 }
@@ -85,10 +85,10 @@
             androidMain.dependencies {
                 api("androidx.annotation:annotation:1.1.0")
                 implementation(project(":compose:animation:animation"))
-                implementation("androidx.savedstate:savedstate-ktx:1.2.0")
+                implementation("androidx.savedstate:savedstate-ktx:1.2.1")
                 implementation(project(":compose:material:material"))
                 implementation("androidx.activity:activity-compose:1.7.0")
-                implementation("androidx.lifecycle:lifecycle-common:2.6.0")
+                implementation("androidx.lifecycle:lifecycle-common:2.6.1")
 
                 // kotlin-reflect and tooling-animation-internal are provided by Studio at runtime
                 compileOnly(project(":compose:animation:animation-tooling-internal"))
@@ -113,7 +113,7 @@
                 implementation(libs.truth)
                 implementation(libs.kotlinReflect)
                 implementation(project(":compose:animation:animation-tooling-internal"))
-                implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0")
+                implementation("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
                 implementation(project(":compose:runtime:runtime-livedata"))
             }
         }
diff --git a/compose/ui/ui/api/current.txt b/compose/ui/ui/api/current.txt
index ab3b2dc..74cc50f 100644
--- a/compose/ui/ui/api/current.txt
+++ b/compose/ui/ui/api/current.txt
@@ -2231,6 +2231,13 @@
 
 package androidx.compose.ui.node {
 
+  public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
+  }
+
+  public final class CompositionLocalConsumerModifierNodeKt {
+    method public static <T> T! currentValueOf(androidx.compose.ui.node.CompositionLocalConsumerModifierNode, androidx.compose.runtime.CompositionLocal<T> local);
+  }
+
   public interface DelegatableNode {
     method public androidx.compose.ui.Modifier.Node getNode();
     property public abstract androidx.compose.ui.Modifier.Node node;
@@ -3085,10 +3092,19 @@
 
 }
 
+package androidx.compose.ui.text {
+
+  public final class TextMeasurerHelperKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.text.TextMeasurer rememberTextMeasurer(optional int cacheSize);
+  }
+
+}
+
 package androidx.compose.ui.viewinterop {
 
   public final class AndroidView_androidKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onReset, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onRelease, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit> update);
     method public static kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> getNoOpUpdate();
     property public static final kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> NoOpUpdate;
   }
diff --git a/compose/ui/ui/api/public_plus_experimental_current.txt b/compose/ui/ui/api/public_plus_experimental_current.txt
index bb28823..f9090fb 100644
--- a/compose/ui/ui/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui/api/public_plus_experimental_current.txt
@@ -2436,11 +2436,11 @@
 
 package androidx.compose.ui.node {
 
-  @androidx.compose.ui.ExperimentalComposeUiApi public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
+  public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
   }
 
   public final class CompositionLocalConsumerModifierNodeKt {
-    method @androidx.compose.ui.ExperimentalComposeUiApi public static <T> T! currentValueOf(androidx.compose.ui.node.CompositionLocalConsumerModifierNode, androidx.compose.runtime.CompositionLocal<T> local);
+    method public static <T> T! currentValueOf(androidx.compose.ui.node.CompositionLocalConsumerModifierNode, androidx.compose.runtime.CompositionLocal<T> local);
   }
 
   public interface DelegatableNode {
@@ -3359,7 +3359,7 @@
 package androidx.compose.ui.text {
 
   public final class TextMeasurerHelperKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.text.ExperimentalTextApi public static androidx.compose.ui.text.TextMeasurer rememberTextMeasurer(optional int cacheSize);
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.text.TextMeasurer rememberTextMeasurer(optional int cacheSize);
   }
 
 }
@@ -3367,8 +3367,8 @@
 package androidx.compose.ui.viewinterop {
 
   public final class AndroidView_androidKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.ExperimentalComposeUiApi @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onReset, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onRelease);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onReset, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onRelease, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit> update);
     method public static kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> getNoOpUpdate();
     property public static final kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> NoOpUpdate;
   }
diff --git a/compose/ui/ui/api/restricted_current.txt b/compose/ui/ui/api/restricted_current.txt
index 29c9c55..d34024c 100644
--- a/compose/ui/ui/api/restricted_current.txt
+++ b/compose/ui/ui/api/restricted_current.txt
@@ -2278,6 +2278,13 @@
     property public final kotlin.jvm.functions.Function0<androidx.compose.ui.node.ComposeUiNode> VirtualConstructor;
   }
 
+  public interface CompositionLocalConsumerModifierNode extends androidx.compose.ui.node.DelegatableNode {
+  }
+
+  public final class CompositionLocalConsumerModifierNodeKt {
+    method public static <T> T! currentValueOf(androidx.compose.ui.node.CompositionLocalConsumerModifierNode, androidx.compose.runtime.CompositionLocal<T> local);
+  }
+
   public interface DelegatableNode {
     method public androidx.compose.ui.Modifier.Node getNode();
     property public abstract androidx.compose.ui.Modifier.Node node;
@@ -3133,10 +3140,19 @@
 
 }
 
+package androidx.compose.ui.text {
+
+  public final class TextMeasurerHelperKt {
+    method @androidx.compose.runtime.Composable public static androidx.compose.ui.text.TextMeasurer rememberTextMeasurer(optional int cacheSize);
+  }
+
+}
+
 package androidx.compose.ui.viewinterop {
 
   public final class AndroidView_androidKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
+    method @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit>? onReset, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> onRelease, optional kotlin.jvm.functions.Function1<? super T,kotlin.Unit> update);
+    method @Deprecated @androidx.compose.runtime.Composable @androidx.compose.ui.UiComposable public static <T extends android.view.View> void AndroidView(kotlin.jvm.functions.Function1<? super android.content.Context,? extends T> factory, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit> update);
     method public static kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> getNoOpUpdate();
     property public static final kotlin.jvm.functions.Function1<android.view.View,kotlin.Unit> NoOpUpdate;
   }
diff --git a/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt
new file mode 100644
index 0000000..eb56a62
--- /dev/null
+++ b/compose/ui/ui/benchmark/src/androidTest/java/androidx/compose/ui/benchmark/input/pointer/VelocityTrackerBenchmark.kt
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.compose.ui.benchmark.input.pointer
+
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.ui.input.pointer.util.VelocityTracker1D
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/** Helper class to create [MotionDataPoint]s deterministically for velocity benchmark testing. */
+private class DataPointCreator(private val dataPointDelta: Float = DefaultDataPointDelta) {
+    private var currentTimeMillis = StartTimeMillis
+    private var dataPointValue = DataPointStartValue
+
+    fun createDataPoint(): MotionDataPoint {
+        val dataPoint = MotionDataPoint(currentTimeMillis, dataPointValue)
+        dataPointValue += dataPointDelta
+        currentTimeMillis += TimeDeltaMillis
+        return dataPoint
+    }
+
+    companion object {
+        /** Arbitrarily chosen. */
+        private const val StartTimeMillis = 0L
+        /** Small enough that it won't reset velocity tracking. */
+        private const val TimeDeltaMillis = 2L
+        /** Arbitrarily chosen. Not 0, so it won't remain 0 if delta is 0. */
+        private const val DataPointStartValue = 100f
+        /** Arbitrarily chosen. */
+        private const val DefaultDataPointDelta = 3f
+    }
+}
+
+private data class MotionDataPoint(val timeMillis: Long, val motionValue: Float)
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class VelocityTrackerBenchmarkTest {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun addMovement_differential() {
+        testAddDataPoint(differential = true)
+    }
+
+    @Test
+    fun addMovement_nonDifferential() {
+        testAddDataPoint(differential = false)
+    }
+
+    @Test
+    fun calculateVelocity_differential() {
+        testCalculateVelocity(differential = true)
+    }
+
+    @Test
+    fun calculateVelocity_nonDifferential() {
+        testCalculateVelocity(differential = false)
+    }
+
+    private fun testAddDataPoint(differential: Boolean) {
+        benchmarkRule.measureRepeated {
+            val velocityTracker = VelocityTracker1D(differential)
+            val dataPointCreator = DataPointCreator()
+
+            for (i in 0 until TestNumDataPoints) {
+                val dataPoint = dataPointCreator.createDataPoint()
+                velocityTracker.addDataPoint(dataPoint.timeMillis, dataPoint.motionValue)
+            }
+        }
+    }
+
+    private fun testCalculateVelocity(differential: Boolean) {
+        val velocityTracker = VelocityTracker1D(differential)
+        val dataPointCreator = DataPointCreator()
+
+        for (i in 0 until TestNumDataPoints) {
+            val dataPoint = dataPointCreator.createDataPoint()
+            velocityTracker.addDataPoint(dataPoint.timeMillis, dataPoint.motionValue)
+        }
+
+        benchmarkRule.measureRepeated {
+            assert(velocityTracker.calculateVelocity() != 0f)
+        }
+    }
+
+    companion object {
+        private const val TestNumDataPoints = 100
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index 1131691..b76ffed 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -77,12 +77,12 @@
         testImplementation(libs.kotlinReflect)
 
         implementation("androidx.activity:activity-ktx:1.7.0")
-        implementation("androidx.core:core:1.9.0")
+        implementation(project(":core:core"))
         implementation('androidx.collection:collection:1.0.0')
         implementation("androidx.customview:customview-poolingcontainer:1.0.0")
-        implementation("androidx.savedstate:savedstate-ktx:1.2.0")
-        implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+        implementation("androidx.savedstate:savedstate-ktx:1.2.1")
+        implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+        implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
         implementation("androidx.profileinstaller:profileinstaller:1.2.1")
         implementation("androidx.emoji2:emoji2:1.2.0")
 
@@ -121,7 +121,7 @@
         androidTestImplementation(project(":compose:ui:ui-test-junit4"))
         androidTestImplementation(project(":internal-testutils-runtime"))
         androidTestImplementation(project(":test:screenshot:screenshot"))
-        androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.0")
+        androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
         androidTestImplementation("androidx.recyclerview:recyclerview:1.3.0-alpha02")
         androidTestImplementation("androidx.core:core-ktx:1.9.0")
         androidTestImplementation("androidx.activity:activity-compose:1.7.0")
@@ -174,12 +174,12 @@
                 implementation(libs.kotlinCoroutinesAndroid)
 
                 implementation("androidx.activity:activity-ktx:1.7.0")
-                implementation("androidx.core:core:1.9.0")
+                implementation(project(":core:core"))
                 implementation('androidx.collection:collection:1.0.0')
                 implementation("androidx.customview:customview-poolingcontainer:1.0.0")
-                implementation("androidx.savedstate:savedstate-ktx:1.2.0")
-                implementation("androidx.lifecycle:lifecycle-runtime:2.6.0")
-                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
+                implementation("androidx.savedstate:savedstate-ktx:1.2.1")
+                implementation("androidx.lifecycle:lifecycle-runtime:2.6.1")
+                implementation("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
                 implementation("androidx.emoji2:emoji2:1.2.0")
             }
 
@@ -248,7 +248,7 @@
                 implementation(project(":compose:ui:ui-test-junit4"))
                 implementation(project(":internal-testutils-runtime"))
                 implementation(project(":test:screenshot:screenshot"))
-                implementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.0")
+                implementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
                 implementation("androidx.recyclerview:recyclerview:1.3.0-alpha02")
                 implementation("androidx.core:core-ktx:1.2.0")
                 implementation("androidx.activity:activity-compose:1.7.0")
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ScrollingAndroidViewsDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ScrollingAndroidViewsDemo.kt
index cdc2bb9..20eb48b 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ScrollingAndroidViewsDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ScrollingAndroidViewsDemo.kt
@@ -33,7 +33,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.demos.R
 import androidx.compose.ui.unit.dp
@@ -41,7 +40,6 @@
 
 private const val ItemCount = 50
 
-@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 fun ScrollingAndroidViewsDemo() {
     Column {
@@ -76,7 +74,6 @@
     }
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
 @Composable
 private fun RecyclingAndroidViewLazyColumn(
     checkedItems: Set<Int>,
@@ -111,6 +108,8 @@
 
                         isChecked = index in checkedItems
                         if (view in resetViews) {
+                            // If we just reset the view before updating the `isChecked` state, we
+                            // don't want the check animation to play.
                             jumpDrawablesToCurrentState()
                             resetViews -= view
                         }
@@ -118,6 +117,9 @@
                 },
                 onReset = { view ->
                     resetViews += view
+                },
+                onRelease = { view ->
+                    resetViews -= view
                 }
             )
         }
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
index 6bae97c..5c7bddf 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/AndroidViewSample.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.samples
 
+import android.content.Context
 import android.view.View
 import android.view.ViewGroup
 import android.webkit.WebView
@@ -43,8 +44,11 @@
 import androidx.compose.ui.graphics.drawscope.drawIntoCanvas
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalLifecycleOwner
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.viewinterop.AndroidView
+import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleEventObserver
 import kotlin.math.roundToInt
 
 @Suppress("SetTextI18n")
@@ -63,6 +67,36 @@
     }
 }
 
+@Suppress("UNUSED_ANONYMOUS_PARAMETER")
+@Sampled
+@Composable
+fun AndroidViewWithReleaseSample() {
+    // Compose a View that needs to be cleaned up when removed from the UI
+    class LifecycleAwareView(context: Context) : View(context) {
+        var lifecycle: Lifecycle? = null
+            set(value) {
+                field?.removeObserver(observer)
+                value?.addObserver(observer)
+                field = value
+            }
+
+        private val observer = LifecycleEventObserver { source, event ->
+            // React to the event
+        }
+    }
+    val lifecycle = LocalLifecycleOwner.current.lifecycle
+    AndroidView(
+        factory = { context -> LifecycleAwareView(context) },
+        update = { view ->
+            view.lifecycle = lifecycle
+        },
+        onRelease = { view ->
+            // Need to release the lifecycle to prevent a memory leak
+            view.lifecycle = null
+        }
+    )
+}
+
 @OptIn(ExperimentalComposeUiApi::class)
 @Sampled
 @Composable
diff --git a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
index b98dee2..df7fd7a 100644
--- a/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
+++ b/compose/ui/ui/samples/src/main/java/androidx/compose/ui/samples/ModifierCompositionLocalSample.kt
@@ -21,7 +21,6 @@
 import androidx.compose.material.Text
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.compositionLocalOf
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
@@ -31,7 +30,6 @@
 import androidx.compose.ui.node.currentValueOf
 import androidx.compose.ui.platform.InspectorInfo
 
-@OptIn(ExperimentalComposeUiApi::class)
 @Sampled
 @Composable
 fun CompositionLocalConsumingModifierSample() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
index 92cffbf..f2e450f 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidComposeViewAccessibilityDelegateCompatTest.kt
@@ -20,6 +20,7 @@
 import android.os.Build
 import android.text.SpannableString
 import android.view.ViewGroup
+import android.view.ViewStructure
 import android.view.accessibility.AccessibilityEvent
 import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.FrameLayout
@@ -35,6 +36,7 @@
 import androidx.compose.ui.node.SemanticsModifierNode
 import androidx.compose.ui.platform.AndroidComposeView
 import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat
+import androidx.compose.ui.platform.AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy
 import androidx.compose.ui.platform.LocalClipboardManager
 import androidx.compose.ui.platform.SemanticsNodeWithAdjustedBounds
 import androidx.compose.ui.platform.getAllUncoveredSemanticsNodesToMap
@@ -55,8 +57,8 @@
 import androidx.compose.ui.semantics.cutText
 import androidx.compose.ui.semantics.disabled
 import androidx.compose.ui.semantics.dismiss
-import androidx.compose.ui.semantics.error
 import androidx.compose.ui.semantics.editableText
+import androidx.compose.ui.semantics.error
 import androidx.compose.ui.semantics.expand
 import androidx.compose.ui.semantics.focused
 import androidx.compose.ui.semantics.getTextLayoutResult
@@ -69,6 +71,7 @@
 import androidx.compose.ui.semantics.pasteText
 import androidx.compose.ui.semantics.progressBarRangeInfo
 import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.setProgress
 import androidx.compose.ui.semantics.setSelection
 import androidx.compose.ui.semantics.setText
@@ -84,19 +87,27 @@
 import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.unit.IntRect
 import androidx.core.view.ViewCompat
+import androidx.core.view.ViewStructureCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
+import androidx.core.view.contentcapture.ContentCaptureSessionCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.nhaarman.mockitokotlin2.any
 import com.nhaarman.mockitokotlin2.argThat
+import com.nhaarman.mockitokotlin2.argumentCaptor
 import com.nhaarman.mockitokotlin2.doReturn
 import com.nhaarman.mockitokotlin2.eq
+import com.nhaarman.mockitokotlin2.mock
 import com.nhaarman.mockitokotlin2.never
 import com.nhaarman.mockitokotlin2.spy
 import com.nhaarman.mockitokotlin2.times
 import com.nhaarman.mockitokotlin2.verify
+import com.nhaarman.mockitokotlin2.whenever
 import java.util.concurrent.Executors
 import kotlinx.coroutines.asCoroutineDispatcher
 import org.junit.Assert.assertEquals
@@ -120,6 +131,9 @@
     private lateinit var androidComposeView: AndroidComposeView
     private lateinit var info: AccessibilityNodeInfoCompat
 
+    private lateinit var contentCaptureSessionCompat: ContentCaptureSessionCompat
+    private lateinit var viewStructureCompat: ViewStructureCompat
+
     @Before
     fun setup() {
         // Use uiAutomation to enable accessibility manager.
@@ -703,7 +717,7 @@
         }
 
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 semanticsNode,
                 mapOf()
             )
@@ -756,7 +770,7 @@
     fun sendWindowContentChangeUndefinedEventByDefault_whenPropertyAdded() {
         val oldSemanticsNode = createSemanticsNodeWithProperties(1, false) {}
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -783,7 +797,7 @@
             disabled()
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -808,7 +822,7 @@
             disabled()
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -836,7 +850,7 @@
             onClick(label = label) { true }
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -865,7 +879,7 @@
             onClick(label = labelOld) { true }
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -893,7 +907,7 @@
             customActions = listOf(CustomAccessibilityAction(label) { true })
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -922,7 +936,7 @@
             customActions = listOf(CustomAccessibilityAction(labelOld) { true })
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -971,7 +985,7 @@
         val oldSemanticsNode = createSemanticsNodeWithProperties(1, true) {
         }
         accessibilityDelegate.previousSemanticsNodes[1] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(
+            SemanticsNodeCopy(
                 oldSemanticsNode,
                 mapOf()
             )
@@ -1383,6 +1397,170 @@
         )
     }
 
+    @SdkSuppress(minSdkVersion = 29)
+    private fun setUpContentCapture() {
+        contentCaptureSessionCompat = mock()
+        viewStructureCompat = mock()
+        val viewStructure: ViewStructure = mock()
+
+        whenever(contentCaptureSessionCompat.newVirtualViewStructure(any(), any()))
+            .thenReturn(viewStructureCompat)
+        whenever(viewStructureCompat.toViewStructure()).thenReturn(viewStructure)
+        accessibilityDelegate.contentCaptureSession = contentCaptureSessionCompat
+        accessibilityDelegate.currentSemanticsNodes
+        accessibilityDelegate.contentCaptureForceEnabledForTesting = true
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    fun testSendContentCaptureSemanticsStructureChangeEvents_appeared() {
+        setUpContentCapture()
+
+        val nodeId = 12
+        val oldNode = createSemanticsNodeWithChildren(nodeId, emptyList())
+        val oldNodeCopy = SemanticsNodeCopy(oldNode, mapOf())
+        accessibilityDelegate.previousSemanticsNodes[nodeId] = oldNodeCopy
+
+        val newNodeId1 = 10
+        val newNodeId2 = 11
+        val newNodeId3 = 12
+        val newNode1 = createSemanticsNodeWithChildren(newNodeId1, emptyList()) {
+            text = AnnotatedString("foo")
+        }
+        val newNode2 = createSemanticsNodeWithChildren(newNodeId2, emptyList()) {
+            text = AnnotatedString("bar")
+        }
+        val newNode3 = createSemanticsNodeWithChildren(newNodeId3, listOf(newNode1, newNode2))
+        accessibilityDelegate.currentSemanticsNodes = mapOf(
+            newNodeId1 to SemanticsNodeWithAdjustedBounds(newNode1, android.graphics.Rect()),
+            newNodeId2 to SemanticsNodeWithAdjustedBounds(newNode2, android.graphics.Rect()),
+            newNodeId3 to SemanticsNodeWithAdjustedBounds(newNode3, android.graphics.Rect()),
+        )
+
+        accessibilityDelegate.sendContentCaptureSemanticsStructureChangeEvents(
+            newNode3, oldNodeCopy)
+
+        val captor = argumentCaptor<CharSequence>()
+        verify(viewStructureCompat, times(2)).setText(captor.capture())
+        assertEquals(captor.firstValue, "foo")
+        assertEquals(captor.secondValue, "bar")
+
+        assertThat(accessibilityDelegate.bufferedContentCaptureDisappearedNodes)
+            .isEmpty()
+        assertThat(accessibilityDelegate.bufferedContentCaptureAppearedNodes.keys)
+            .containsExactly(newNodeId1, newNodeId2)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    fun testSendContentCaptureSemanticsStructureChangeEvents_disappeared() {
+        setUpContentCapture()
+        val nodeId1 = 10
+        val nodeId2 = 11
+        val nodeId3 = 12
+        val oldNode1 = createSemanticsNodeWithChildren(nodeId1, emptyList())
+        val oldNode2 = createSemanticsNodeWithChildren(nodeId2, emptyList())
+        val oldNode3 = createSemanticsNodeWithChildren(nodeId3, listOf(oldNode1, oldNode2))
+        val oldNodeCopy1 = SemanticsNodeCopy(oldNode1, mapOf())
+        val oldNodeCopy2 = SemanticsNodeCopy(oldNode2, mapOf())
+        val oldNodeCopy3 = SemanticsNodeCopy(oldNode3, mapOf())
+        accessibilityDelegate.previousSemanticsNodes[nodeId1] = oldNodeCopy1
+        accessibilityDelegate.previousSemanticsNodes[nodeId2] = oldNodeCopy2
+        accessibilityDelegate.previousSemanticsNodes[nodeId3] = oldNodeCopy3
+
+        val newNodeId1 = 12
+        val newNode1 = createSemanticsNodeWithChildren(newNodeId1, emptyList())
+        accessibilityDelegate.currentSemanticsNodes = mapOf(
+            newNodeId1 to SemanticsNodeWithAdjustedBounds(newNode1, android.graphics.Rect()),
+        )
+
+        accessibilityDelegate.sendContentCaptureSemanticsStructureChangeEvents(
+            newNode1, oldNodeCopy3)
+
+        assertThat(accessibilityDelegate.bufferedContentCaptureAppearedNodes).isEmpty()
+        assertThat(accessibilityDelegate.bufferedContentCaptureDisappearedNodes)
+            .containsExactly(nodeId1, nodeId2)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    fun testSendContentCaptureSemanticsStructureChangeEvents_appearedAndDisappeared() {
+        setUpContentCapture()
+        val nodeId1 = 10
+        val nodeId2 = 11
+        val nodeId3 = 12
+        val oldNode1 = createSemanticsNodeWithChildren(nodeId1, emptyList())
+        val oldNode2 = createSemanticsNodeWithChildren(nodeId2, emptyList())
+        val oldNode3 = createSemanticsNodeWithChildren(nodeId3, listOf(oldNode1, oldNode2))
+        val oldNodeCopy1 = SemanticsNodeCopy(oldNode1, mapOf())
+        val oldNodeCopy2 = SemanticsNodeCopy(oldNode2, mapOf())
+        val oldNodeCopy3 = SemanticsNodeCopy(oldNode3, mapOf())
+        accessibilityDelegate.previousSemanticsNodes[nodeId1] = oldNodeCopy1
+        accessibilityDelegate.previousSemanticsNodes[nodeId2] = oldNodeCopy2
+        accessibilityDelegate.previousSemanticsNodes[nodeId3] = oldNodeCopy3
+
+        val newNodeId1 = 13
+        val newNodeId2 = 14
+        val newNodeId3 = 12
+        val newNode1 = createSemanticsNodeWithChildren(newNodeId1, emptyList())
+        val newNode2 = createSemanticsNodeWithChildren(newNodeId2, emptyList())
+        val newNode3 = createSemanticsNodeWithChildren(newNodeId3, listOf(newNode1, newNode2))
+        accessibilityDelegate.currentSemanticsNodes = mapOf(
+            newNodeId1 to SemanticsNodeWithAdjustedBounds(newNode1, android.graphics.Rect()),
+            newNodeId2 to SemanticsNodeWithAdjustedBounds(newNode2, android.graphics.Rect()),
+            newNodeId3 to SemanticsNodeWithAdjustedBounds(newNode3, android.graphics.Rect()),
+        )
+
+        accessibilityDelegate.sendContentCaptureSemanticsStructureChangeEvents(
+            newNode3, oldNodeCopy3)
+
+        assertThat(accessibilityDelegate.bufferedContentCaptureAppearedNodes.keys)
+            .containsExactly(newNodeId1, newNodeId2)
+        assertThat(accessibilityDelegate.bufferedContentCaptureDisappearedNodes)
+            .containsExactly(nodeId1, nodeId2)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 29)
+    fun testSendContentCaptureSemanticsStructureChangeEvents_sameNodeAppearedThenDisappeared() {
+        setUpContentCapture()
+        val nodeId1 = 10
+        val oldNode1 = createSemanticsNodeWithChildren(nodeId1, emptyList())
+        val oldNodeCopy1 =
+            SemanticsNodeCopy(oldNode1, mapOf())
+        accessibilityDelegate.previousSemanticsNodes[nodeId1] = oldNodeCopy1
+
+        val newNodeId1 = 11
+        val newNodeId2 = 10
+        val newNode1 = createSemanticsNodeWithChildren(newNodeId1, emptyList())
+        val newNode2 = createSemanticsNodeWithChildren(newNodeId2, listOf(newNode1))
+        accessibilityDelegate.currentSemanticsNodes = mapOf(
+            newNodeId1 to SemanticsNodeWithAdjustedBounds(newNode1, android.graphics.Rect()),
+            newNodeId2 to SemanticsNodeWithAdjustedBounds(newNode2, android.graphics.Rect()),
+        )
+
+        accessibilityDelegate.sendContentCaptureSemanticsStructureChangeEvents(
+            newNode2, oldNodeCopy1)
+
+        assertThat(accessibilityDelegate.bufferedContentCaptureAppearedNodes.keys)
+            .containsExactly(newNodeId1)
+        assertThat(accessibilityDelegate.bufferedContentCaptureDisappearedNodes).isEmpty()
+
+        val newNodeCopy1 = SemanticsNodeCopy(newNode1, mapOf())
+        val newNodeCopy2 = SemanticsNodeCopy(newNode2, mapOf())
+        accessibilityDelegate.previousSemanticsNodes[newNodeId1] = newNodeCopy1
+        accessibilityDelegate.previousSemanticsNodes[newNodeId2] = newNodeCopy2
+        accessibilityDelegate.currentSemanticsNodes = mapOf(
+            nodeId1 to SemanticsNodeWithAdjustedBounds(oldNode1, android.graphics.Rect()),
+        )
+
+        accessibilityDelegate.sendContentCaptureSemanticsStructureChangeEvents(
+            oldNode1, newNodeCopy2)
+
+        assertThat(accessibilityDelegate.bufferedContentCaptureAppearedNodes).isEmpty()
+        assertThat(accessibilityDelegate.bufferedContentCaptureDisappearedNodes).isEmpty()
+    }
+
     private fun sendTextSemanticsChangeEvent(oldNodePassword: Boolean, newNodePassword: Boolean) {
         val nodeId = 1
         val oldTextNode = createSemanticsNodeWithProperties(nodeId, true) {
@@ -1399,7 +1577,7 @@
             )
         }
         accessibilityDelegate.previousSemanticsNodes[nodeId] =
-            AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy(oldTextNode, mapOf())
+            SemanticsNodeCopy(oldTextNode, mapOf())
 
         val newTextNode = createSemanticsNodeWithAdjustedBoundsWithProperties(nodeId, true) {
             setText { true }
@@ -1439,6 +1617,29 @@
         )
     }
 
+    private fun createSemanticsNodeWithChildren(
+        id: Int,
+        children: List<SemanticsNode>,
+        properties: (SemanticsPropertyReceiver.() -> Unit) = {}
+    ): SemanticsNode {
+        val layoutNode = LayoutNode(semanticsId = id)
+        layoutNode.zSortedChildren.addAll(children.map { it.layoutNode })
+        val nodeCoordinator = InnerNodeCoordinator(layoutNode)
+        val modifierNode = object : SemanticsModifierNode, Modifier.Node() {
+            override val semanticsConfiguration = SemanticsConfiguration().also {
+                it.properties()
+            }
+        }
+        modifierNode.updateCoordinator(nodeCoordinator)
+
+        val semanticsNode = SemanticsNode(modifierNode, true, layoutNode)
+        layoutNode.modifier = Modifier.semantics {
+            properties()
+        }
+
+        return semanticsNode
+    }
+
     private fun createSemanticsNodeWithAdjustedBoundsWithProperties(
         id: Int,
         mergeDescendants: Boolean,
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ParentDataModifierTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ParentDataModifierTest.kt
index 7df3c84..47f9c79 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ParentDataModifierTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/ParentDataModifierTest.kt
@@ -21,9 +21,17 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.LayoutIdParentData
+import androidx.compose.ui.layout.Measurable
+import androidx.compose.ui.layout.MeasureResult
+import androidx.compose.ui.layout.MeasureScope
 import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.node.LayoutModifierNode
+import androidx.compose.ui.node.ModifierNodeElement
+import androidx.compose.ui.node.ParentDataModifierNode
 import androidx.compose.ui.node.Ref
 import androidx.compose.ui.test.TestActivity
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.Density
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import org.junit.Assert.assertEquals
@@ -152,6 +160,24 @@
         }
     }
 
+    @Test
+    fun implementingBothParentDataAndLayoutModifier() {
+        val parentData = "data"
+        runOnUiThread {
+            activity.setContent {
+                Layout({
+                    Layout(
+                        modifier = ParentDataAndLayoutElement(parentData),
+                        content = {}
+                    ) { _, _ -> layout(0, 0) {} }
+                }) { measurables, _ ->
+                    assertEquals("data", measurables[0].parentData)
+                    layout(0, 0) { }
+                }
+            }
+        }
+    }
+
     // We only need this because IR compiler doesn't like converting lambdas to Runnables
     private fun runOnUiThread(block: () -> Unit) {
         val runnable: Runnable = object : Runnable {
@@ -173,3 +199,24 @@
         }
     ) {}
 }
+
+private data class ParentDataAndLayoutElement(val data: String) :
+    ModifierNodeElement<ParentDataAndLayoutNode>() {
+    override fun create() = ParentDataAndLayoutNode(data)
+    override fun update(node: ParentDataAndLayoutNode) = node.also { it.data = data }
+}
+
+class ParentDataAndLayoutNode(var data: String) : Modifier.Node(), LayoutModifierNode,
+    ParentDataModifierNode {
+    override fun MeasureScope.measure(
+        measurable: Measurable,
+        constraints: Constraints
+    ): MeasureResult {
+        val placeable = measurable.measure(constraints)
+        return layout(placeable.width, placeable.height) {
+            placeable.place(0, 0)
+        }
+    }
+
+    override fun Density.modifyParentData(parentData: Any?) = data
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
index 93902e5..7641c8a 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ClipDrawTest.kt
@@ -53,6 +53,7 @@
 import androidx.test.filters.SdkSuppress
 import org.junit.Assert
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -262,6 +263,7 @@
         }
     }
 
+    @Ignore("Test disabled due to flakiness, see b/256950653")
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun concaveClip() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputDensityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputDensityTest.kt
index a34f741..417ace5 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputDensityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/pointer/PointerInputDensityTest.kt
@@ -16,13 +16,21 @@
 
 package androidx.compose.ui.input.pointer
 
+import android.view.MotionEvent
+import android.view.View
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.background
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.unit.Density
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -39,6 +47,134 @@
     @get:Rule
     val rule = createComposeRule()
 
+    val tag = "Tagged Layout"
+
+    @Test
+    fun sendNotANumberDensityInPointerEvents() {
+        lateinit var view: View
+
+        val motionEventsToTrigger = generateMultipleMotionEvents(
+            lastPointerX = Float.NaN,
+            lastPointerY = Float.NaN
+        )
+        val recordedEvents = mutableListOf<PointerEventType>()
+
+        rule.setContent {
+            view = LocalView.current
+
+            Box(Modifier
+                .fillMaxSize()
+                .background(Color.Green)
+                .testTag(tag)
+                .pointerInput(Unit) {
+                    awaitPointerEventScope {
+                        while (true) {
+                            val event: PointerEvent = awaitPointerEvent()
+                            recordedEvents += event.type
+                        }
+                    }
+                }
+            ) { }
+        }
+
+        rule.waitForIdle()
+
+        rule.runOnUiThread {
+            motionEventsToTrigger.forEach {
+                view.dispatchTouchEvent(it)
+            }
+        }
+        rule.waitForIdle()
+
+        assertThat(recordedEvents).hasSize(2)
+        assertThat(recordedEvents[0]).isEqualTo(PointerEventType.Press)
+        assertThat(recordedEvents[1]).isEqualTo(PointerEventType.Press)
+    }
+
+    @Test
+    fun sendPositiveInfinityDensityInPointerEvents() {
+        lateinit var view: View
+
+        val motionEventsToTrigger = generateMultipleMotionEvents(
+            lastPointerX = Float.POSITIVE_INFINITY,
+            lastPointerY = Float.POSITIVE_INFINITY
+        )
+        val recordedEvents = mutableListOf<PointerEventType>()
+
+        rule.setContent {
+            view = LocalView.current
+
+            Box(Modifier
+                .fillMaxSize()
+                .background(Color.Red)
+                .testTag(tag)
+                .pointerInput(Unit) {
+                    awaitPointerEventScope {
+                        while (true) {
+                            val event: PointerEvent = awaitPointerEvent()
+                            recordedEvents += event.type
+                        }
+                    }
+                }
+            ) { }
+        }
+
+        rule.waitForIdle()
+
+        rule.runOnUiThread {
+            motionEventsToTrigger.forEach {
+                view.dispatchTouchEvent(it)
+            }
+        }
+        rule.waitForIdle()
+
+        assertThat(recordedEvents).hasSize(2)
+        assertThat(recordedEvents[0]).isEqualTo(PointerEventType.Press)
+        assertThat(recordedEvents[1]).isEqualTo(PointerEventType.Press)
+    }
+
+    @Test
+    fun sendNegativeInfinityDensityInPointerEvents() {
+        lateinit var view: View
+
+        val motionEventsToTrigger = generateMultipleMotionEvents(
+            lastPointerX = Float.NEGATIVE_INFINITY,
+            lastPointerY = Float.NEGATIVE_INFINITY
+        )
+        val recordedEvents = mutableListOf<PointerEventType>()
+
+        rule.setContent {
+            view = LocalView.current
+
+            Box(Modifier
+                .fillMaxSize()
+                .background(Color.Cyan)
+                .testTag(tag)
+                .pointerInput(Unit) {
+                    awaitPointerEventScope {
+                        while (true) {
+                            val event: PointerEvent = awaitPointerEvent()
+                            recordedEvents += event.type
+                        }
+                    }
+                }
+            ) { }
+        }
+
+        rule.waitForIdle()
+
+        rule.runOnUiThread {
+            motionEventsToTrigger.forEach {
+                view.dispatchTouchEvent(it)
+            }
+        }
+        rule.waitForIdle()
+
+        assertThat(recordedEvents).hasSize(2)
+        assertThat(recordedEvents[0]).isEqualTo(PointerEventType.Press)
+        assertThat(recordedEvents[1]).isEqualTo(PointerEventType.Press)
+    }
+
     @Test
     fun compositionLocalDensityChangeRestartsPointerInputOverload1() {
         compositionLocalDensityChangeRestartsPointerInput {
@@ -90,4 +226,104 @@
             assertThat(pointerInputDensities.last()).isEqualTo(9f)
         }
     }
+
+    private fun generateMultipleMotionEvents(
+        lastPointerX: Float,
+        lastPointerY: Float
+    ): List<MotionEvent> {
+        val firstPointerOffset = Offset(0f, 0f)
+        val secondPointerOffset = Offset(10f, 0f)
+
+        val firstFingerPointerPropertiesId = 0
+        val secondFingerPointerPropertiesId = 1
+        val thirdFingerPointerPropertiesId = 2
+
+        val firstPointerProperties =
+            PointerProperties(firstFingerPointerPropertiesId).also {
+                it.toolType = MotionEvent.TOOL_TYPE_FINGER
+            }
+        val secondPointerProperties =
+            PointerProperties(secondFingerPointerPropertiesId).also {
+                it.toolType = MotionEvent.TOOL_TYPE_FINGER
+            }
+
+        val thirdPointerProperties =
+            PointerProperties(thirdFingerPointerPropertiesId).also {
+                it.toolType = MotionEvent.TOOL_TYPE_FINGER
+            }
+
+        val eventDownTime = 1L
+        var eventStartTime = 0L
+
+        val firstPointerEvent = MotionEvent.obtain(
+            eventDownTime,
+            eventStartTime,
+            MotionEvent.ACTION_DOWN,
+            1,
+            arrayOf(firstPointerProperties),
+            arrayOf(PointerCoords(firstPointerOffset.x, firstPointerOffset.y)),
+            0,
+            0,
+            0f,
+            0f,
+            0,
+            0,
+            0,
+            0
+        )
+
+        eventStartTime += 500
+
+        val secondPointerEvent = MotionEvent.obtain(
+            eventDownTime,
+            eventStartTime,
+            MotionEvent.ACTION_POINTER_DOWN,
+            2,
+            arrayOf(
+                firstPointerProperties,
+                secondPointerProperties
+            ),
+            arrayOf(
+                PointerCoords(firstPointerOffset.x, firstPointerOffset.y),
+                PointerCoords(secondPointerOffset.x, secondPointerOffset.y)
+            ),
+            0,
+            0,
+            0f,
+            0f,
+            0,
+            0,
+            0,
+            0
+        )
+
+        eventStartTime += 500
+
+        val thirdPointerEvent = MotionEvent.obtain(
+            eventDownTime,
+            eventStartTime,
+            MotionEvent.ACTION_POINTER_DOWN,
+            3,
+            arrayOf(
+                firstPointerProperties,
+                secondPointerProperties,
+                thirdPointerProperties
+            ),
+            arrayOf(
+                PointerCoords(firstPointerOffset.x, firstPointerOffset.y),
+                PointerCoords(secondPointerOffset.x, secondPointerOffset.y),
+                PointerCoords(lastPointerX, lastPointerY)
+            ),
+            0,
+            0,
+            0f,
+            0f,
+            0,
+            0,
+            0,
+            0
+        )
+
+        return listOf(firstPointerEvent, secondPointerEvent, thirdPointerEvent)
+    }
 }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalModifierNodeTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalModifierNodeTest.kt
index 27cee1b..c11ac21 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalModifierNodeTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/node/CompositionLocalModifierNodeTest.kt
@@ -22,7 +22,6 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.staticCompositionLocalOf
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.drawscope.ContentDrawScope
 import androidx.compose.ui.layout.Layout
@@ -35,7 +34,6 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 
-@OptIn(ExperimentalComposeUiApi::class)
 @MediumTest
 @RunWith(AndroidJUnit4::class)
 class CompositionLocalModifierNodeTest {
@@ -188,7 +186,6 @@
         }
     }
 
-    @ExperimentalComposeUiApi
     private inline fun <reified T : Modifier.Node> modifierNodeElementOf(
         crossinline create: () -> T
     ): ModifierNodeElement<T> = object : ModifierNodeElement<T>() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
index dc336e7..c4f9bbc 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WrapperTest.kt
@@ -31,6 +31,7 @@
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
@@ -57,6 +58,7 @@
         activityScenario.moveToState(Lifecycle.State.STARTED)
     }
 
+    @SdkSuppress(minSdkVersion = 22) // b/269521688
     @Test
     fun ensureComposeWrapperDoesntPropagateInvalidations() {
         val commitLatch = CountDownLatch(2)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
index ae9bd78..8b7397d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/semantics/SemanticsTests.kt
@@ -19,6 +19,7 @@
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.offset
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.text.BasicText
@@ -30,6 +31,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.layout.Layout
 import androidx.compose.ui.layout.MeasurePolicy
 import androidx.compose.ui.layout.SubcomposeLayout
@@ -50,6 +52,7 @@
 import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.text.AnnotatedString
+import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastMap
@@ -333,6 +336,32 @@
     }
 
     @Test
+    fun fakeSemanticsNode_usesValuesFromParent() {
+        val tag = "tag1"
+        rule.setContent {
+            SimpleTestLayout(
+                Modifier
+                    .offset(10.dp, 10.dp)
+                    .clickable(role = Role.Button, onClick = {})
+                    .testTag(tag)
+            ) {
+                BasicText("text")
+            }
+        }
+
+        val node = rule.onNodeWithTag(tag, true).fetchSemanticsNode()
+        val fakeNode = node.replacedChildren.first { it.isFake }
+
+        // Ensure that the fake node uses the properties of the parent.
+        assertThat(fakeNode.size).isNotEqualTo(IntSize.Zero)
+        assertThat(fakeNode.boundsInRoot).isNotEqualTo(Rect.Zero)
+        assertThat(fakeNode.positionInRoot).isNotEqualTo(Offset.Zero)
+        assertThat(fakeNode.boundsInWindow).isNotEqualTo(Rect.Zero)
+        assertThat(fakeNode.positionInWindow).isNotEqualTo(Offset.Zero)
+        assertThat(fakeNode.isTransparent).isFalse()
+    }
+
+    @Test
     fun removingMergedSubtree_updatesSemantics() {
         val label = "foo"
         val showSubtree = mutableStateOf(true)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
index de755b1..f17c79b 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/viewinterop/AndroidViewTest.kt
@@ -30,6 +30,7 @@
 import android.view.View.OnAttachStateChangeListener
 import android.view.ViewGroup
 import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
+import android.view.accessibility.AccessibilityNodeInfo
 import android.widget.EditText
 import android.widget.FrameLayout
 import android.widget.RelativeLayout
@@ -168,6 +169,32 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.R)
+    fun androidViewAccessibilityDelegate() {
+        rule.setContent {
+             AndroidView({ TextView(it).apply { text = "Test"; setScreenReaderFocusable(true) } })
+        }
+        Espresso
+            .onView(instanceOf(TextView::class.java))
+            .check(matches(isDisplayed()))
+            .check { view, exception ->
+                val viewParent = view.getParent()
+                if (viewParent !is View) {
+                    throw exception
+                }
+                val delegate = viewParent.getAccessibilityDelegate()
+                if (viewParent.getAccessibilityDelegate() == null) {
+                    throw exception
+                }
+                val info: AccessibilityNodeInfo = AccessibilityNodeInfo()
+                delegate.onInitializeAccessibilityNodeInfo(view, info)
+                if (!info.isScreenReaderFocusable()) {
+                    throw exception
+                }
+            }
+    }
+
+    @Test
     fun androidViewWithResourceTest_preservesLayoutParams() {
         rule.setContent {
             AndroidView({
@@ -627,6 +654,37 @@
     }
 
     @Test
+    fun androidView_callsOnRelease() {
+        var releaseCount = 0
+        var showContent by mutableStateOf(true)
+        rule.setContent {
+            if (showContent) {
+                AndroidView(
+                    factory = { TextView(it) },
+                    update = { it.text = "onRelease test" },
+                    onRelease = { releaseCount++ }
+                )
+            }
+        }
+
+        onView(instanceOf(TextView::class.java))
+            .check(matches(isDisplayed()))
+
+        assertEquals("onRelease() was called unexpectedly", 0, releaseCount)
+
+        showContent = false
+
+        onView(instanceOf(TextView::class.java))
+            .check(doesNotExist())
+
+        assertEquals(
+            "onRelease() should be called exactly once after " +
+                "removing the view from the composition hierarchy",
+            1, releaseCount
+        )
+    }
+
+    @Test
     fun androidView_restoresState() {
         var result = ""
 
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index 60eb74f..14b1cc3 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -125,9 +125,10 @@
 import androidx.compose.ui.node.Owner
 import androidx.compose.ui.node.OwnerSnapshotObserver
 import androidx.compose.ui.node.RootForTest
+import androidx.compose.ui.platform.MotionEventVerifierApi29.isValidMotionEvent
 import androidx.compose.ui.semantics.EmptySemanticsModifierNodeElement
-import androidx.compose.ui.semantics.SemanticsNode
 import androidx.compose.ui.semantics.SemanticsOwner
+import androidx.compose.ui.semantics.findClosestParentNode
 import androidx.compose.ui.semantics.outerSemantics
 import androidx.compose.ui.text.ExperimentalTextApi
 import androidx.compose.ui.text.InternalTextApi
@@ -725,8 +726,12 @@
                     info: AccessibilityNodeInfoCompat
                 ) {
                     super.onInitializeAccessibilityNodeInfo(host, info)
-                    var parentId = SemanticsNode(layoutNode.outerSemantics!!, false).parent!!.id
-                    if (parentId == semanticsOwner.unmergedRootSemanticsNode.id) {
+                    var parentId = layoutNode
+                        .findClosestParentNode { it.outerSemantics != null }
+                        ?.semanticsId
+                    if (parentId == null ||
+                        parentId == semanticsOwner.unmergedRootSemanticsNode.id
+                    ) {
                         parentId = AccessibilityNodeProviderCompat.HOST_VIEW_ID
                     }
                     info.setParent(thisView, parentId)
@@ -1607,10 +1612,26 @@
     }
 
     private fun isBadMotionEvent(event: MotionEvent): Boolean {
-        return !event.x.isFinite() ||
+        var eventInvalid = !event.x.isFinite() ||
             !event.y.isFinite() ||
             !event.rawX.isFinite() ||
             !event.rawY.isFinite()
+
+        if (!eventInvalid) {
+            // First event x,y is checked above if block, so we can skip index 0.
+            for (index in 1 until event.pointerCount) {
+                eventInvalid = !event.getX(index).isFinite() || !event.getY(index).isFinite() ||
+                    if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
+                        !isValidMotionEvent(event, index)
+                    } else {
+                        false
+                    }
+
+                if (eventInvalid) break
+            }
+        }
+
+        return eventInvalid
     }
 
     private fun isPositionChanged(event: MotionEvent): Boolean {
@@ -1618,7 +1639,8 @@
             return true
         }
         val lastEvent = previousMotionEvent
-        return lastEvent == null || event.rawX != lastEvent.rawX || event.rawY != lastEvent.rawY
+        return lastEvent == null || lastEvent.pointerCount != event.pointerCount ||
+            event.rawX != lastEvent.rawX || event.rawY != lastEvent.rawY
     }
 
     private fun findViewByAccessibilityIdRootedAtCurrentView(
@@ -1939,3 +1961,11 @@
         preTransform(tmpMatrix)
     }
 }
+
+@RequiresApi(29)
+private object MotionEventVerifierApi29 {
+    @DoNotInline
+    fun isValidMotionEvent(event: MotionEvent, index: Int): Boolean {
+        return event.getRawX(index).isFinite() && event.getRawY(index).isFinite()
+    }
+}
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
index dfedb9f..4bcf007 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeViewAccessibilityDelegateCompat.android.kt
@@ -42,6 +42,7 @@
 import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.annotation.VisibleForTesting.Companion.PRIVATE
+import androidx.collection.ArrayMap
 import androidx.collection.ArraySet
 import androidx.collection.SparseArrayCompat
 import androidx.compose.ui.ExperimentalComposeUiApi
@@ -87,14 +88,17 @@
 import androidx.compose.ui.unit.toSize
 import androidx.compose.ui.util.fastForEach
 import androidx.compose.ui.util.fastForEachIndexed
+import androidx.compose.ui.util.fastMap
 import androidx.core.view.AccessibilityDelegateCompat
 import androidx.core.view.ViewCompat
 import androidx.core.view.ViewCompat.ACCESSIBILITY_LIVE_REGION_ASSERTIVE
 import androidx.core.view.ViewCompat.ACCESSIBILITY_LIVE_REGION_POLITE
+import androidx.core.view.ViewStructureCompat
 import androidx.core.view.accessibility.AccessibilityEventCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
 import androidx.core.view.accessibility.AccessibilityNodeInfoCompat.AccessibilityActionCompat
 import androidx.core.view.accessibility.AccessibilityNodeProviderCompat
+import androidx.core.view.contentcapture.ContentCaptureSessionCompat
 import androidx.lifecycle.Lifecycle
 import kotlin.math.abs
 import kotlin.math.ceil
@@ -292,18 +296,38 @@
     )
 
     /**
-     * True if any accessibility service enabled in the system, except the UIAutomator (as it
-     * doesn't appear in the list of enabled services)
+     * True if any accessibility service enabled in the system, or if any content capture service
+     * enabled in the system.
      */
     @VisibleForTesting
     internal val isEnabled: Boolean
         get() {
+            return isEnabledForAccessibility || isEnabledForContentCapture
+        }
+
+    /**
+     * True if any accessibility service enabled in the system, except the UIAutomator (as it
+     * doesn't appear in the list of enabled services)
+     */
+    private val isEnabledForAccessibility: Boolean
+        get() {
             // checking the list allows us to filter out the UIAutomator which doesn't appear in it
             return accessibilityForceEnabledForTesting ||
                 accessibilityManager.isEnabled && enabledServices.isNotEmpty()
         }
 
     /**
+     * True if any content capture service enabled in the system.
+     *
+     * TODO(b/272068594): follow up on improving the performance and actually enabling the content
+     * capture feature in production later.
+     */
+    private val isEnabledForContentCapture: Boolean
+        get() {
+            return contentCaptureForceEnabledForTesting
+        }
+
+    /**
      * True if accessibility service with the touch exploration (e.g. Talkback) is enabled in the
      * system.
      * Note that UIAutomator doesn't request touch exploration therefore returns false
@@ -331,6 +355,11 @@
     private val boundsUpdateChannel = Channel<Unit>(Channel.CONFLATED)
     private var currentSemanticsNodesInvalidated = true
 
+    internal var contentCaptureForceEnabledForTesting = false
+    internal var contentCaptureSession: ContentCaptureSessionCompat? = null
+    internal val bufferedContentCaptureAppearedNodes = ArrayMap<Int, ViewStructureCompat>()
+    internal val bufferedContentCaptureDisappearedNodes = ArraySet<Int>()
+
     private class PendingTextTraversedEvent(
         val node: SemanticsNode,
         val action: Int,
@@ -347,7 +376,7 @@
      * semantics tree. They key is the virtual view id(the root node has a key of
      * AccessibilityNodeProviderCompat.HOST_VIEW_ID and other node has a key of its id).
      */
-    private var currentSemanticsNodes: Map<Int, SemanticsNodeWithAdjustedBounds> = mapOf()
+    internal var currentSemanticsNodes: Map<Int, SemanticsNodeWithAdjustedBounds> = mapOf()
         get() {
             if (currentSemanticsNodesInvalidated) { // first instance of retrieving all nodes
                 currentSemanticsNodesInvalidated = false
@@ -405,6 +434,7 @@
                 accessibilityManager.addTouchExplorationStateChangeListener(
                     touchExplorationStateListener
                 )
+                contentCaptureSession = view.getContentCaptureSessionCompat()
             }
 
             override fun onViewDetachedFromWindow(view: View) {
@@ -414,6 +444,7 @@
                 accessibilityManager.removeTouchExplorationStateChangeListener(
                     touchExplorationStateListener
                 )
+                contentCaptureSession = null
             }
         })
     }
@@ -715,14 +746,7 @@
                 } else if (role == Role.Switch) {
                     info.roleDescription = view.context.resources.getString(R.string.switch_role)
                 } else {
-                    val className = when (it) {
-                        Role.Button -> "android.widget.Button"
-                        Role.Checkbox -> "android.widget.CheckBox"
-                        Role.RadioButton -> "android.widget.RadioButton"
-                        Role.Image -> "android.widget.ImageView"
-                        Role.DropdownList -> "android.widget.Spinner"
-                        else -> null
-                    }
+                    val className = role.toLegacyClassName()
                     // Images are often minor children of larger widgets, so we only want to
                     // announce the Image role when the image itself is focusable.
                     if (role != Role.Image ||
@@ -878,16 +902,7 @@
         }
 
         // Mark invisible nodes
-        val wrapperToCheckTransparency = if (semanticsNode.isFake) {
-            // when node is fake, its parent that is the original semantics node should define the
-            // alpha value
-            semanticsNode.parent?.findCoordinatorToGetBounds()
-        } else {
-            semanticsNode.findCoordinatorToGetBounds()
-        }
-        val isTransparent = wrapperToCheckTransparency?.isTransparent() ?: false
-        info.isVisibleToUser = !isTransparent &&
-            semanticsNode.unmergedConfig.getOrNull(SemanticsProperties.InvisibleToUser) == null
+        info.isVisibleToUser = semanticsNode.isVisible
 
         semanticsNode.unmergedConfig.getOrNull(SemanticsProperties.LiveRegion)?.let {
             info.liveRegion = when (it) {
@@ -1392,7 +1407,7 @@
      */
     private fun sendEvent(event: AccessibilityEvent): Boolean {
         // only send an event if there's an enabled service listening for events of this type
-        if (!isEnabled) {
+        if (!isEnabledForAccessibility) {
             return false
         }
 
@@ -1951,13 +1966,10 @@
             // The node below is not added to the tree; it's a wrapper around outer semantics to
             // use the methods available to the SemanticsNode
             val semanticsNode = SemanticsNode(wrapper, false)
-            val wrapperToCheckAlpha = semanticsNode.findCoordinatorToGetBounds()
 
             // Do not 'find' invisible nodes when exploring by touch. This will prevent us from
             // sending events for invisible nodes
-            if (!semanticsNode.unmergedConfig.contains(SemanticsProperties.InvisibleToUser) &&
-                !wrapperToCheckAlpha.isTransparent()
-            ) {
+            if (semanticsNode.isVisible) {
                 val layoutNode = wrapper.requireLayoutNode()
                 val androidView = view
                     .androidViewsHandler
@@ -2041,14 +2053,17 @@
 
     /**
      * This suspend function loops for the entire lifetime of the Compose instance: it consumes
-     * recent layout changes and sends events to the accessibility framework in batches separated
-     * by a 100ms delay.
+     * recent layout changes and sends events to the accessibility and content capture framework in
+     * batches separated by a 100ms delay.
      */
     suspend fun boundsUpdatesEventLoop() {
         try {
             val subtreeChangedSemanticsNodesIds = ArraySet<Int>()
             for (notification in boundsUpdateChannel) {
-                if (isEnabled) {
+                if (isEnabledForContentCapture) {
+                    notifyContentCaptureChanges()
+                }
+                if (isEnabledForAccessibility) {
                     for (i in subtreeChangedLayoutNodes.indices) {
                         @Suppress("UNNECESSARY_NOT_NULL_ASSERTION")
                         sendSubtreeChangeAccessibilityEvents(
@@ -2142,7 +2157,11 @@
 
     private fun checkForSemanticsChanges() {
         // Structural change
-        sendSemanticsStructureChangeEvents(
+        sendAccessibilitySemanticsStructureChangeEvents(
+            view.semanticsOwner.unmergedRootSemanticsNode,
+            previousSemanticsRoot
+        )
+        sendContentCaptureSemanticsStructureChangeEvents(
             view.semanticsOwner.unmergedRootSemanticsNode,
             previousSemanticsRoot
         )
@@ -2211,6 +2230,15 @@
                 }
                 @Suppress("UNCHECKED_CAST")
                 when (entry.key) {
+                    SemanticsProperties.Text -> {
+                        val oldText = oldNode.unmergedConfig.getOrNull(SemanticsProperties.Text)
+                            ?.firstOrNull()
+                        val newText = newNode.unmergedConfig.getOrNull(SemanticsProperties.Text)
+                            ?.firstOrNull()
+                        if (oldText != newText) {
+                            sendContentCaptureTextUpdateEvent(newNode.id, newText.toString())
+                        }
+                    }
                     SemanticsProperties.PaneTitle -> {
                         val paneTitle = entry.value as String
                         // If oldNode doesn't have pane title, it will be handled in
@@ -2496,6 +2524,17 @@
         }
     }
 
+    private fun sendContentCaptureTextUpdateEvent(id: Int, newText: String) {
+        val session = contentCaptureSession ?: return
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            return
+        }
+        // TODO: consider having a `newContentCaptureId` function to improve readability.
+        val autofillId = session.newAutofillId(id.toLong())
+        checkNotNull(autofillId) { "Invalid content capture ID" }
+        session.notifyViewTextChanged(autofillId, newText)
+    }
+
     // List of visible scrollable nodes (which are observing scroll state snapshot writes).
     private val scrollObservationScopes = mutableListOf<ScrollObservationScope>()
 
@@ -2611,7 +2650,108 @@
         sendEvent(event)
     }
 
-    private fun sendSemanticsStructureChangeEvents(
+    private fun View.getContentCaptureSessionCompat(): ContentCaptureSessionCompat? {
+        ViewCompat.setImportantForContentCapture(this, ViewCompat.IMPORTANT_FOR_CONTENT_CAPTURE_YES)
+        return ViewCompat.getContentCaptureSession(this)
+    }
+
+    private fun SemanticsNode.toViewStructure(): ViewStructureCompat? {
+        val session = contentCaptureSession ?: return null
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            return null
+        }
+
+        val rootAutofillId = ViewCompat.getAutofillId(view) ?: return null
+        val parentNode = parent
+        val parentAutofillId = if (parentNode != null) {
+            session.newAutofillId(parentNode.id.toLong()) ?: return null
+        } else {
+            rootAutofillId.toAutofillId()
+        }
+        val structure = session.newVirtualViewStructure(
+            parentAutofillId, id.toLong()) ?: return null
+
+        val configuration = this.unmergedConfig
+        if (configuration.contains(SemanticsProperties.Password)) {
+            return null
+        }
+
+        configuration.getOrNull(SemanticsProperties.Text)?.let {
+            structure.setClassName("android.widget.TextView")
+            structure.setText(it.fastJoinToString("\n"))
+        }
+        configuration.getOrNull(SemanticsProperties.EditableText)?.let {
+            structure.setClassName("android.widget.EditText")
+            structure.setText(it)
+        }
+        configuration.getOrNull(SemanticsProperties.ContentDescription)?.let {
+            structure.setContentDescription(it.fastJoinToString("\n"))
+        }
+        configuration.getOrNull(SemanticsProperties.Role)?.toLegacyClassName()?.let {
+            structure.setClassName(it)
+        }
+
+        with(boundsInWindow) {
+            structure.setDimens(
+                left.toInt(), top.toInt(), 0, 0, width.toInt(), height.toInt()
+            )
+        }
+        return structure
+    }
+
+    private fun bufferContentCaptureViewAppeared(
+        virtualId: Int,
+        viewStructure: ViewStructureCompat?
+    ) {
+        if (viewStructure == null) {
+            return
+        }
+
+        if (bufferedContentCaptureDisappearedNodes.contains(virtualId)) {
+            // disappear then appear
+            bufferedContentCaptureDisappearedNodes.remove(virtualId)
+        } else {
+            bufferedContentCaptureAppearedNodes[virtualId] = viewStructure
+        }
+    }
+    private fun bufferContentCaptureViewDisappeared(virtualId: Int) {
+        if (bufferedContentCaptureAppearedNodes.containsKey(virtualId)) {
+            // appear then disappear
+            bufferedContentCaptureAppearedNodes.remove(virtualId)
+        } else {
+            bufferedContentCaptureDisappearedNodes.add(virtualId)
+        }
+    }
+
+    private fun notifyContentCaptureChanges() {
+        val session = contentCaptureSession ?: return
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) {
+            return
+        }
+
+        if (bufferedContentCaptureAppearedNodes.isNotEmpty()) {
+            session.notifyViewsAppeared(
+                bufferedContentCaptureAppearedNodes.values
+                    .toList()
+                    .fastMap { it.toViewStructure() })
+            bufferedContentCaptureAppearedNodes.clear()
+        }
+        if (bufferedContentCaptureDisappearedNodes.isNotEmpty()) {
+            session.notifyViewsDisappeared(
+                    bufferedContentCaptureDisappearedNodes
+                        .toList()
+                        .fastMap { it.toLong() }
+                        .toLongArray())
+            bufferedContentCaptureDisappearedNodes.clear()
+        }
+    }
+
+    private fun notifySubtreeAppeared(node: SemanticsNode) {
+        bufferContentCaptureViewAppeared(node.id, node.toViewStructure())
+        node.replacedChildren.fastForEach { child -> notifySubtreeAppeared(child) }
+    }
+
+    private fun sendAccessibilitySemanticsStructureChangeEvents(
         newNode: SemanticsNode,
         oldNode: SemanticsNodeCopy
     ) {
@@ -2638,7 +2778,36 @@
 
         newNode.replacedChildren.fastForEach { child ->
             if (currentSemanticsNodes.contains(child.id)) {
-                sendSemanticsStructureChangeEvents(child, previousSemanticsNodes[child.id]!!)
+                sendAccessibilitySemanticsStructureChangeEvents(
+                    child, previousSemanticsNodes[child.id]!!)
+            }
+        }
+    }
+
+    @VisibleForTesting(otherwise = PRIVATE)
+    internal fun sendContentCaptureSemanticsStructureChangeEvents(
+        newNode: SemanticsNode,
+        oldNode: SemanticsNodeCopy
+    ) {
+        // Iterate the new tree to notify content capture appear
+        newNode.replacedChildren.fastForEach { child ->
+            if (currentSemanticsNodes.contains(child.id) &&
+                !oldNode.children.contains(child.id)) {
+                notifySubtreeAppeared(child)
+            }
+        }
+        // Notify content capture disappear
+        for (entry in previousSemanticsNodes.entries) {
+            if (!currentSemanticsNodes.contains(entry.key)) {
+                bufferContentCaptureViewDisappeared(entry.key)
+            }
+        }
+
+        newNode.replacedChildren.fastForEach { child ->
+            if (currentSemanticsNodes.contains(child.id) &&
+                previousSemanticsNodes.contains(child.id)) {
+                sendContentCaptureSemanticsStructureChangeEvents(
+                    child, previousSemanticsNodes[child.id]!!)
             }
         }
     }
@@ -2990,6 +3159,10 @@
 
 private fun SemanticsNode.enabled() = (config.getOrNull(SemanticsProperties.Disabled) == null)
 
+@OptIn(ExperimentalComposeUiApi::class)
+private val SemanticsNode.isVisible: Boolean
+    get() = !isTransparent && !unmergedConfig.contains(SemanticsProperties.InvisibleToUser)
+
 private fun SemanticsNode.propertiesDeleted(
     oldNode: AndroidComposeViewAccessibilityDelegateCompat.SemanticsNodeCopy
 ): Boolean {
@@ -3188,3 +3361,13 @@
     }
     return null
 }
+
+private fun Role.toLegacyClassName(): String? =
+    when (this) {
+        Role.Button -> "android.widget.Button"
+        Role.Checkbox -> "android.widget.CheckBox"
+        Role.RadioButton -> "android.widget.RadioButton"
+        Role.Image -> "android.widget.ImageView"
+        Role.DropdownList -> "android.widget.Spinner"
+        else -> null
+    }
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
index 682cb01..1c9c17a 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/viewinterop/AndroidView.android.kt
@@ -31,7 +31,6 @@
 import androidx.compose.runtime.rememberCompositionContext
 import androidx.compose.runtime.saveable.LocalSaveableStateRegistry
 import androidx.compose.runtime.saveable.SaveableStateRegistry
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.UiComposable
 import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
@@ -50,27 +49,83 @@
 import androidx.lifecycle.findViewTreeLifecycleOwner
 import androidx.savedstate.SavedStateRegistryOwner
 
+@Deprecated(
+    message = "AndroidView now has arguments for onReset and onRelease callbacks. This original " +
+        "overload is retained for binary compatibility only.",
+    level = DeprecationLevel.HIDDEN
+)
+@Composable
+@UiComposable
+fun <T : View> AndroidView(
+    factory: (Context) -> T,
+    modifier: Modifier = Modifier,
+    update: (T) -> Unit = NoOpUpdate
+) {
+    AndroidView(
+        factory = factory,
+        modifier = modifier,
+        update = update,
+        onRelease = NoOpUpdate
+    )
+}
+
 /**
- * Composes an Android [View] obtained from [factory]. The [factory] block will be called
- * exactly once to obtain the [View] to be composed, and it is also guaranteed to be invoked on
- * the UI thread. Therefore, in addition to creating the [View], the [factory] can also be used
- * to perform one-off initializations and [View] constant properties' setting.
- * The [update] block can be run multiple times (on the UI thread as well) due to recomposition,
- * and it is the right place to set [View] properties depending on state. When state changes,
- * the block will be reexecuted to set the new properties. Note the block will also be ran once
- * right after the [factory] block completes.
+ * Composes an Android [View] obtained from [factory]. The [factory] block will be called exactly
+ * once to obtain the [View] being composed, and it is also guaranteed to be invoked on the UI
+ * thread. Therefore, in addition to creating the [View], the [factory] block can also be used to
+ * perform one-off initializations and [View] constant properties' setting. The [update] block can
+ * run multiple times (on the UI thread as well) due to recomposition, and it is the right place to
+ * set the new properties. Note that the block will also run once right after the [factory] block
+ * completes.
  *
  * [AndroidView] is commonly needed for using Views that are infeasible to be reimplemented in
  * Compose and there is no corresponding Compose API. Common examples for the moment are
  * WebView, SurfaceView, AdView, etc.
  *
- * This version of [AndroidView] does not automatically pool or reuse Views. If placed inside of a
+ * By default, [AndroidView] does not automatically pool or reuse Views. If placed inside of a
  * reusable container (including inside a [LazyRow][androidx.compose.foundation.lazy.LazyRow] or
  * [LazyColumn][androidx.compose.foundation.lazy.LazyColumn]), the View instances will always be
  * discarded and recreated if the composition hierarchy containing the AndroidView changes, even
- * if its group structure did not change and the View could have conceivably been reused. To
- * opt-in to View reuse, use the `AndroidView(factory, onReset, modifier, update, onRelease)`
- * overload instead.
+ * if its group structure did not change and the View could have conceivably been reused.
+ *
+ * Views are eligible for reuse if [AndroidView] is given a non-null [onReset] callback. Since
+ * it is expensive to discard and recreate View instances, reusing Views can lead to noticeable
+ * performance improvements — especially when building a scrolling list of
+ * [AndroidViews][AndroidView]. It is highly recommended to specify an [onReset] implementation and
+ * opt-in to View reuse when possible.
+ *
+ * When [onReset] is specified, [View] instances may be reused when hosted inside of a container
+ * that supports reusable elements. Reuse occurs when compatible instances of [AndroidView] are
+ * inserted and removed during recomposition. Two instances of `AndroidView` are considered
+ * compatible if they are invoked with the same composable group structure. The most common
+ * scenario where this happens is in lazy layout APIs like `LazyRow` and `LazyColumn`, which
+ * can reuse layout nodes (and Views, in this case) between items when scrolling.
+ *
+ * [onReset] is invoked on the UI thread when the View will be reused, signaling that the View
+ * should be prepared to appear in a new context in the composition hierarchy. This callback
+ * is invoked before [update] and may be used to reset any transient View state like animations or
+ * user input.
+ *
+ * Note that [onReset] may not be immediately followed by a call to [update]. Compose may
+ * temporarily detach the View from the composition hierarchy if it is deactivated but not released
+ * from composition. This can happen if the View appears in a [ReusableContentHost] that is not
+ * currently active or inside of a [movable content][androidx.compose.runtime.movableContentOf]
+ * block that is being moved. If this happens, the View will be removed from its parent, but
+ * retained by Compose so that it may be reused if its content host becomes active again. If the
+ * View never becomes active again and is instead discarded entirely, the [onReset] callback will
+ * be invoked directly from this deactivated state when Compose releases the View.
+ *
+ * If you need to observe whether the View is currently used in the composition hierarchy, you may
+ * observe whether it is attached via [View.addOnAttachStateChangeListener]. The View may also
+ * observe the lifecycle of its host via [findViewTreeLifecycleOwner]. The lifecycle returned by
+ * this function will match the [LocalLifecycleOwner]. Note that the lifecycle is not set and cannot
+ * be used until the View is attached.
+ *
+ * When the View is removed from the composition permanently, [onRelease] will be invoked (also on
+ * the UI thread). Once this callback returns, Compose will never attempt to reuse the previous
+ * View instance regardless of whether an [onReset] implementation was provided. If the View is
+ * needed again in the future, a new instance will be created, with a fresh lifecycle that begins
+ * by calling the [factory].
  *
  * [AndroidView] will not clip its content to the layout bounds. Use [View.setClipToOutline] on
  * the child View to clip the contents, if desired. Developers will likely want to do this with
@@ -84,15 +139,28 @@
  *
  * @sample androidx.compose.ui.samples.AndroidViewSample
  *
+ * @sample androidx.compose.ui.samples.ReusableAndroidViewInLazyColumnSample
+ *
  * @param factory The block creating the [View] to be composed.
+ * @param onReset A callback invoked as a signal that the view is about to be attached to the
+ * composition hierarchy in a different context than its original creation. This callback is invoked
+ * before [update] and should prepare the view for general reuse. If `null` or not specified, the
+ * `AndroidView` instance will not support reuse, and the View instance will always be discarded
+ * whenever the AndroidView is moved or removed from the composition hierarchy.
  * @param modifier The modifier to be applied to the layout.
- * @param update The callback to be invoked after the layout is inflated.
+ * @param onRelease A callback invoked as a signal that this view instance has exited the
+ * composition hierarchy entirely and will not be reused again. Any additional resources used by the
+ * View should be freed at this time.
+ * @param update A callback to be invoked after the layout is inflated and upon recomposition to
+ * update the information and state of the view.
  */
 @Composable
 @UiComposable
 fun <T : View> AndroidView(
     factory: (Context) -> T,
     modifier: Modifier = Modifier,
+    onReset: ((T) -> Unit)? = null,
+    onRelease: (T) -> Unit = NoOpUpdate,
     update: (T) -> Unit = NoOpUpdate
 ) {
     val materializedModifier = currentComposer.materialize(modifier)
@@ -106,112 +174,38 @@
     val lifecycleOwner = LocalLifecycleOwner.current
     val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
 
-    ComposeNode<LayoutNode, UiApplier>(
-        factory = createAndroidViewNodeFactory(factory),
-        update = {
-            updateViewHolderParams<T>(
-                modifier = materializedModifier,
-                density = density,
-                lifecycleOwner = lifecycleOwner,
-                savedStateRegistryOwner = savedStateRegistryOwner,
-                layoutDirection = layoutDirection
-            )
-            set(update) { requireViewFactoryHolder<T>().updateBlock = it }
-        }
-    )
-}
-
-/**
- * Composes an Android [View] obtained from [factory], allowing Compose to reuse Android [View]
- * instances when placed in a container that supports reuse. Reuse occurs when compatible instances
- * of [AndroidView] are inserted and removed during recomposition. Two instances of `AndroidView`
- * are considered compatible if they are invoked with the same composable group structure. The most
- * common scenario where this happens is in lazy layout APIs like `LazyRow` and `LazyColumn`, which
- * can reuse layout nodes (and Views, in this case) between items when scrolling.
- *
- * [AndroidView] will invoke [factory] once to create the View being composed. [factory] is a good
- * place to perform any one-time initialization for your layout after it is inflated. This view will
- * be retained and reused until the composable is discarded.
- *
- * When the View is recomposed, the [update] callback will be invoked. This is an ideal place to
- * set view properties to the target state. The [update] block will be run once after [factory]
- * block completes, and also after the View is reused.
- *
- * When the View is about to be reused, [onReset] will be invoked, signaling that the View should
- * be prepared to appear in a new context in the composition hierarchy. This callback is invoked
- * before [update] and may be used to reset any transient View state like animations or user input.
- *
- * When the View is removed from the composition hierarchy, [onRelease] will be invoked. Once this
- * callback returns, Compose will never attempt to reuse the previous View instance. If one is
- * needed in the future, a new instance of the node will be created, with a fresh lifecycle that
- * begins by calling the factory.
- *
- * In addition to being reset and released, Compose may also temporarily detach the View from the
- * composition hierarchy if it is temporarily deactivated (Namely, if it appears in a
- * [ReusableContentHost] that is not currently active or inside of a
- * [movable content][androidx.compose.runtime.movableContentOf] block that is being moved). If this
- * happens, the View will be removed from its parent, but retained by Compose so that it may be
- * reused if its content host becomes active again. If this does not happen and the View is instead
- * discarded entirely, the [onRelease] callback will be invoked.
- *
- * If you need to observe whether the View is currently used in the composition hierarchy, you may
- * observe whether it is attached via [View.addOnAttachStateChangeListener]. The View may also
- * observe the lifecycle of its host via [findViewTreeLifecycleOwner]. The lifecycle returned by
- * this function will match the [LocalLifecycleOwner]. Note that the lifecycle is not set and cannot
- * be used until the View is attached.
- *
- * All three callback parameters to this function ([update], [onReset], and [onRelease]) are invoked
- * on the UI thread.
- *
- * @sample androidx.compose.ui.samples.ReusableAndroidViewInLazyColumnSample
- *
- * @param factory The block creating the [View] to be composed.
- * @param onReset A callback invoked as a signal that the view is about to be attached to the
- * composition hierarchy in a different context than its original creation. This callback is invoked
- * before [update] and should prepare the view for general reuse.
- * @param modifier The modifier to be applied to the layout.
- * @param update A callback to be invoked after the layout is inflated and upon recomposition to
- * update the information and state of the view.
- * @param onRelease A callback invoked as a signal that this view instance has exited the
- * composition hierarchy entirely and will not be reused again. Any additional resources used by the
- * View should be freed at this time.
- */
-@ExperimentalComposeUiApi
-@Composable
-@UiComposable
-fun <T : View> AndroidView(
-    factory: (Context) -> T,
-    onReset: (T) -> Unit,
-    modifier: Modifier = Modifier,
-    update: (T) -> Unit = NoOpUpdate,
-    onRelease: (T) -> Unit = NoOpUpdate
-) {
-    val materializedModifier = currentComposer.materialize(modifier)
-    val density = LocalDensity.current
-    val layoutDirection = LocalLayoutDirection.current
-
-    // These locals are initialized from the view tree at the AndroidComposeView hosting this
-    // composition, but they need to be passed to this Android View so that the ViewTree*Owner
-    // functions return the correct owners if different local values were provided by the
-    // composition, e.g. by a navigation library.
-    val lifecycleOwner = LocalLifecycleOwner.current
-    val savedStateRegistryOwner = LocalSavedStateRegistryOwner.current
-
-    ReusableComposeNode<LayoutNode, UiApplier>(
-        factory = createAndroidViewNodeFactory(factory),
-        update = {
-            updateViewHolderParams<T>(
-                modifier = materializedModifier,
-                density = density,
-                lifecycleOwner = lifecycleOwner,
-                savedStateRegistryOwner = savedStateRegistryOwner,
-                layoutDirection = layoutDirection
-            )
-            set(onReset) { requireViewFactoryHolder<T>().resetBlock = it }
-            set(update) { requireViewFactoryHolder<T>().updateBlock = it }
-            set(onRelease) { requireViewFactoryHolder<T>().releaseBlock = it }
-        }
-    )
+    if (onReset != null) {
+        ReusableComposeNode<LayoutNode, UiApplier>(
+            factory = createAndroidViewNodeFactory(factory),
+            update = {
+                updateViewHolderParams<T>(
+                    modifier = materializedModifier,
+                    density = density,
+                    lifecycleOwner = lifecycleOwner,
+                    savedStateRegistryOwner = savedStateRegistryOwner,
+                    layoutDirection = layoutDirection
+                )
+                set(onReset) { requireViewFactoryHolder<T>().resetBlock = it }
+                set(update) { requireViewFactoryHolder<T>().updateBlock = it }
+                set(onRelease) { requireViewFactoryHolder<T>().releaseBlock = it }
+            }
+        )
+    } else {
+        ComposeNode<LayoutNode, UiApplier>(
+            factory = createAndroidViewNodeFactory(factory),
+            update = {
+                updateViewHolderParams<T>(
+                    modifier = materializedModifier,
+                    density = density,
+                    lifecycleOwner = lifecycleOwner,
+                    savedStateRegistryOwner = savedStateRegistryOwner,
+                    layoutDirection = layoutDirection
+                )
+                set(update) { requireViewFactoryHolder<T>().updateBlock = it }
+                set(onRelease) { requireViewFactoryHolder<T>().releaseBlock = it }
+            }
+        )
+    }
 }
 
 @Composable
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
index d24885c..ea2846b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/pointer/util/VelocityTracker.kt
@@ -20,7 +20,6 @@
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.input.pointer.PointerInputChange
 import androidx.compose.ui.input.pointer.changedToDownIgnoreConsumed
-import androidx.compose.ui.input.pointer.util.VelocityTracker1D.Strategy
 import androidx.compose.ui.unit.Velocity
 import androidx.compose.ui.util.fastForEach
 import kotlin.math.abs
@@ -154,9 +153,13 @@
         Impulse,
     }
     // Circular buffer; current sample at index.
-    private val samples: Array<DataPointAtTime?> = Array(HistorySize) { null }
+    private val samples: Array<DataPointAtTime?> = arrayOfNulls(HistorySize)
     private var index: Int = 0
 
+    // Reusable arrays to avoid allocation inside calculateVelocity.
+    private val reusableDataPointsArray = FloatArray(HistorySize)
+    private val reusableTimeArray = FloatArray(HistorySize)
+
     /**
      * Adds a data point for velocity calculation at a given time, [timeMillis]. The data ponit
      * represents an amount of a change in position (for differential data points), or an absolute
@@ -181,8 +184,8 @@
      * This can be expensive. Only call this when you need the velocity.
      */
     fun calculateVelocity(): Float {
-        val dataPoints: MutableList<Float> = mutableListOf()
-        val time: MutableList<Float> = mutableListOf()
+        val dataPoints = reusableDataPointsArray
+        val time = reusableTimeArray
         var sampleCount = 0
         var index: Int = index
 
@@ -204,8 +207,8 @@
                 break
             }
 
-            dataPoints.add(sample.dataPoint)
-            time.add(-age)
+            dataPoints[sampleCount] = sample.dataPoint
+            time[sampleCount] = -age
             index = (if (index == 0) HistorySize else index) - 1
 
             sampleCount += 1
@@ -213,12 +216,14 @@
 
         if (sampleCount >= minSampleSize) {
             // Choose computation logic based on strategy.
-            // Multiply by "1000" to convert from units/ms to units/s
             return when (strategy) {
-                Strategy.Impulse ->
-                    calculateImpulseVelocity(dataPoints, time, isDataDifferential) * 1000
-                Strategy.Lsq2 -> calculateLeastSquaresVelocity(dataPoints, time) * 1000
-            }
+                Strategy.Impulse -> {
+                    calculateImpulseVelocity(dataPoints, time, sampleCount, isDataDifferential)
+                }
+                Strategy.Lsq2 -> {
+                    calculateLeastSquaresVelocity(dataPoints, time, sampleCount)
+                }
+            } * 1000 // Multiply by "1000" to convert from units/ms to units/s
         }
 
         // We're unable to make a velocity estimate but we did have at least one
@@ -239,12 +244,16 @@
      * should be provided in reverse chronological order. The returned velocity is in "units/ms",
      * where "units" is unit of the [dataPoints].
      */
-    private fun calculateLeastSquaresVelocity(dataPoints: List<Float>, time: List<Float>): Float {
+    private fun calculateLeastSquaresVelocity(
+        dataPoints: FloatArray,
+        time: FloatArray,
+        sampleCount: Int
+    ): Float {
         // The 2nd coefficient is the derivative of the quadratic polynomial at
         // x = 0, and that happens to be the last timestamp that we end up
         // passing to polyFitLeastSquares.
         try {
-            return polyFitLeastSquares(time, dataPoints, 2)[1]
+            return polyFitLeastSquares(time, dataPoints, sampleCount, 2)[1]
         } catch (exception: IllegalArgumentException) {
             return 0f
         }
@@ -336,40 +345,36 @@
  * Throws an IllegalArgumentException if:
  * <ul>
  *   <li>[degree] is not a positive integer.
- *   <li>[x] and [y] are not the same size.
- *   <li>[x] or [y] are empty.
- *   <li>(some other reason that
+ *   <li>[sampleCount] is zero.
  * </ul>
  *
  */
 internal fun polyFitLeastSquares(
     /** The x-coordinates of each data point. */
-    x: List<Float>,
+    x: FloatArray,
     /** The y-coordinates of each data point. */
-    y: List<Float>,
+    y: FloatArray,
+    /** number of items in each array */
+    sampleCount: Int,
     degree: Int
-): List<Float> {
+): FloatArray {
     if (degree < 1) {
         throw IllegalArgumentException("The degree must be at positive integer")
     }
-    if (x.size != y.size) {
-        throw IllegalArgumentException("x and y must be the same length")
-    }
-    if (x.isEmpty()) {
+    if (sampleCount == 0) {
         throw IllegalArgumentException("At least one point must be provided")
     }
 
     val truncatedDegree =
-        if (degree >= x.size) {
-            x.size - 1
+        if (degree >= sampleCount) {
+            sampleCount - 1
         } else {
             degree
         }
 
-    val coefficients = MutableList(degree + 1) { 0.0f }
-
+    val coefficients = FloatArray(degree + 1)
     // Shorthands for the purpose of notation equivalence to original C++ code.
-    val m: Int = x.size
+    val m: Int = sampleCount
     val n: Int = truncatedDegree + 1
 
     // Expand the X vector to a matrix A, pre-multiplied by the weights.
@@ -513,15 +518,15 @@
  * the boundary condition must be applied to the oldest sample to be accurate.
  */
 private fun calculateImpulseVelocity(
-    dataPoints: List<Float>,
-    time: List<Float>,
+    dataPoints: FloatArray,
+    time: FloatArray,
+    sampleCount: Int,
     isDataDifferential: Boolean
 ): Float {
-    val numDataPoints = dataPoints.size
-    if (numDataPoints < 2) {
+    if (sampleCount < 2) {
         return 0f
     }
-    if (numDataPoints == 2) {
+    if (sampleCount == 2) {
         if (time[0] == time[1]) {
             return 0f
         }
@@ -534,7 +539,7 @@
         return dataPointsDelta / (time[0] - time[1])
     }
     var work = 0f
-    for (i in (numDataPoints - 1) downTo 1) {
+    for (i in (sampleCount - 1) downTo 1) {
         if (time[i] == time[i - 1]) {
             continue
         }
@@ -544,7 +549,7 @@
             else dataPoints[i] - dataPoints[i - 1]
         val vCurr = dataPointsDelta / (time[i] - time[i - 1])
         work += (vCurr - vPrev) * abs(vCurr)
-        if (i == (numDataPoints - 1)) {
+        if (i == (sampleCount - 1)) {
             work = (work * 0.5f)
         }
     }
@@ -563,7 +568,7 @@
 private class Vector(
     val length: Int
 ) {
-    val elements: Array<Float> = Array(length) { 0.0f }
+    val elements: FloatArray = FloatArray(length)
 
     operator fun get(i: Int) = elements[i]
 
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode.kt
index 647b2dc..d87ed28 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/CompositionLocalConsumerModifierNode.kt
@@ -17,7 +17,6 @@
 package androidx.compose.ui.node
 
 import androidx.compose.runtime.CompositionLocal
-import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 
 /**
@@ -32,7 +31,6 @@
  * @see Modifier.Node
  * @see CompositionLocal
  */
-@ExperimentalComposeUiApi
 interface CompositionLocalConsumerModifierNode : DelegatableNode
 
 /**
@@ -62,7 +60,6 @@
  * before the node is [attached][Modifier.Node.onAttach] or after the node is
  * [detached][Modifier.Node.onDetach].
  */
-@ExperimentalComposeUiApi
 fun <T> CompositionLocalConsumerModifierNode.currentValueOf(local: CompositionLocal<T>): T {
     check(node.isAttached) {
         "Cannot read CompositionLocal because the Modifier node is not currently attached. Make " +
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
index a4b324c..9bdb4f2 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/NodeCoordinator.kt
@@ -242,10 +242,10 @@
             if (layoutNode.nodes.has(Nodes.ParentData)) {
                 with(layoutNode.density) {
                     layoutNode.nodes.tailToHead {
-                        if (it === thisNode) return@tailToHead
                         if (it.isKind(Nodes.ParentData) && it is ParentDataModifierNode) {
                             data = with(it) { modifyParentData(data) }
                         }
+                        if (it === thisNode) return@tailToHead
                     }
                 }
             }
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
index 0bf327d..ab6678e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/semantics/SemanticsNode.kt
@@ -112,7 +112,8 @@
     /**
      * The size of the bounding box for this node, with no clipping applied
      */
-    val size: IntSize get() = findCoordinatorToGetBounds().size
+    val size: IntSize
+        get() = findCoordinatorToGetBounds()?.size ?: IntSize.Zero
 
     /**
      * The bounding box for this node relative to the root of this Compose hierarchy, with
@@ -120,46 +121,44 @@
      * Rect([positionInRoot], [size].toSize())
      */
     val boundsInRoot: Rect
-        get() {
-            if (!layoutNode.isAttached) return Rect.Zero
-            return findCoordinatorToGetBounds().boundsInRoot()
-        }
+        get() = findCoordinatorToGetBounds()?.takeIf { it.isAttached }?.boundsInRoot()
+            ?: Rect.Zero
 
     /**
      * The position of this node relative to the root of this Compose hierarchy, with no clipping
      * applied
      */
     val positionInRoot: Offset
-        get() {
-            if (!layoutNode.isAttached) return Offset.Zero
-            return findCoordinatorToGetBounds().positionInRoot()
-        }
+        get() = findCoordinatorToGetBounds()?.takeIf { it.isAttached }?.positionInRoot()
+            ?: Offset.Zero
 
     /**
      * The bounding box for this node relative to the screen, with clipping applied. To get the
      * bounds with no clipping applied, use PxBounds([positionInWindow], [size].toSize())
      */
     val boundsInWindow: Rect
-        get() {
-            if (!layoutNode.isAttached) return Rect.Zero
-            return findCoordinatorToGetBounds().boundsInWindow()
-        }
+        get() = findCoordinatorToGetBounds()?.takeIf { it.isAttached }?.boundsInWindow()
+            ?: Rect.Zero
 
     /**
      * The position of this node relative to the screen, with no clipping applied
      */
     val positionInWindow: Offset
-        get() {
-            if (!layoutNode.isAttached) return Offset.Zero
-            return findCoordinatorToGetBounds().positionInWindow()
-        }
+        get() = findCoordinatorToGetBounds()?.takeIf { it.isAttached }?.positionInWindow()
+            ?: Offset.Zero
+
+    /**
+     * Whether this node is transparent.
+     */
+    internal val isTransparent: Boolean
+        get() = findCoordinatorToGetBounds()?.isTransparent() ?: false
 
     /**
      * Returns the position of an [alignment line][AlignmentLine], or [AlignmentLine.Unspecified]
      * if the line is not provided.
      */
     fun getAlignmentLinePosition(alignmentLine: AlignmentLine): Int {
-        return findCoordinatorToGetBounds()[alignmentLine]
+        return findCoordinatorToGetBounds()?.get(alignmentLine) ?: AlignmentLine.Unspecified
     }
 
     // CHILDREN
@@ -325,13 +324,11 @@
      * of use cases it means that accessibility bounds will be equal to the clickable area.
      * Otherwise the outermost semantics will be used to report bounds, size and position.
      */
-    internal fun findCoordinatorToGetBounds(): NodeCoordinator {
-        return if (unmergedConfig.isMergingSemanticsOfDescendants) {
-            (layoutNode.outerMergingSemantics ?: outerSemanticsNode)
-                .requireCoordinator(Nodes.Semantics)
-        } else {
-            outerSemanticsNode.requireCoordinator(Nodes.Semantics)
-        }
+    internal fun findCoordinatorToGetBounds(): NodeCoordinator? {
+        if (isFake) return parent?.findCoordinatorToGetBounds()
+        val semanticsModifierNode = layoutNode.outerMergingSemantics
+            .takeIf { unmergedConfig.isMergingSemanticsOfDescendants } ?: outerSemanticsNode
+        return semanticsModifierNode.requireCoordinator(Nodes.Semantics)
     }
 
     // Fake nodes
@@ -421,7 +418,7 @@
  * [LayoutNode] to return `true` from [selector] or null if [selector] returns false
  * for all ancestors.
  */
-private fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
+internal fun LayoutNode.findClosestParentNode(selector: (LayoutNode) -> Boolean): LayoutNode? {
     var currentParent = this.parent
     while (currentParent != null) {
         if (selector(currentParent)) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt
index 9d3b1c9..b94e5ef 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/text/TextMeasurerHelper.kt
@@ -37,7 +37,6 @@
  * @param cacheSize Capacity of internal cache inside TextMeasurer. Size unit is the number of
  * unique text layout inputs that are measured.
  */
-@ExperimentalTextApi
 @Composable
 fun rememberTextMeasurer(
     cacheSize: Int = DefaultCacheSize
diff --git a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
index 7c4f310..5dd6560 100644
--- a/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
+++ b/compose/ui/ui/src/test/kotlin/androidx/compose/ui/gesture/scrollorientationlocking/PolyFitLeastSquaresTest.kt
@@ -27,182 +27,182 @@
 
     @Test
     fun polyFitLeastSquares_linear2PointsSlopeOf0Intercept1_isCorrect() {
-        val x = listOf(0f, 1f)
-        val y = listOf(1f, 1f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(1f, 1f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(1f, 0f))
+        assertIsCloseToEquals(actual, floatArrayOf(1f, 0f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsSlopeOf1Intercept0_isCorrect() {
-        val x = listOf(0f, 1f)
-        val y = listOf(0f, 1f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(0f, 1f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsSlopeOf1000000_isCorrect() {
-        val x = listOf(0f, 1f)
-        val y = listOf(0f, 1000000f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(0f, 1000000f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1000000f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1000000f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsSlopeOfNegative5_isCorrect() {
-        val x = listOf(0f, 1f)
-        val y = listOf(0f, -5f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(0f, -5f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(0f, -5f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, -5f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsRandom1_isCorrect() {
-        val x = listOf(-8f, -2f)
-        val y = listOf(7f, 5f)
+        val x = floatArrayOf(-8f, -2f)
+        val y = floatArrayOf(7f, 5f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(4.33333f, -.33333f))
+        assertIsCloseToEquals(actual, floatArrayOf(4.33333f, -.33333f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsRandom2_isCorrect() {
-        val x = listOf(-7f, 4f)
-        val y = listOf(4f, -2f)
+        val x = floatArrayOf(-7f, 4f)
+        val y = floatArrayOf(4f, -2f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(.181818f, -.545454f))
+        assertIsCloseToEquals(actual, floatArrayOf(.181818f, -.545454f))
     }
 
     @Test
     fun polyFitLeastSquares_linear2PointsRandom3_isCorrect() {
-        val x = listOf(4f, -6f)
-        val y = listOf(-6f, 0f)
+        val x = floatArrayOf(4f, -6f)
+        val y = floatArrayOf(-6f, 0f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(-3.6f, -.6f))
+        assertIsCloseToEquals(actual, floatArrayOf(-3.6f, -.6f))
     }
 
     @Test
     fun polyFitLeastSquares_linear4PointsImperfect_isCorrect() {
-        val x = listOf(0f, 2f, 0f, 2f)
-        val y = listOf(0f, 1f, 2f, 3f)
+        val x = floatArrayOf(0f, 2f, 0f, 2f)
+        val y = floatArrayOf(0f, 1f, 2f, 3f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(1f, .5f))
+        assertIsCloseToEquals(actual, floatArrayOf(1f, .5f))
     }
 
     @Test
     fun polyFitLeastSquares_quadratic3PointsActuallyLinear_isCorrect() {
-        val x = listOf(0f, 1f, 2f)
-        val y = listOf(0f, 1f, 2f)
+        val x = floatArrayOf(0f, 1f, 2f)
+        val y = floatArrayOf(0f, 1f, 2f)
 
-        val actual = polyFitLeastSquares(x, y, 2)
+        val actual = polyFitLeastSquares(x, y, x.size, 2)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1f, 0f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1f, 0f))
     }
 
     @Test
     fun polyFitLeastSquares_quadratic3Points_isCorrect() {
-        val x = listOf(0f, 1f, 2f)
-        val y = listOf(0f, 1f, 0f)
+        val x = floatArrayOf(0f, 1f, 2f)
+        val y = floatArrayOf(0f, 1f, 0f)
 
-        val actual = polyFitLeastSquares(x, y, 2)
+        val actual = polyFitLeastSquares(x, y, x.size, 2)
 
-        assertIsCloseToEquals(actual, listOf(0f, 2f, -1f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 2f, -1f))
     }
 
     @Test
     fun polyFitLeastSquares_quadratic5Points_isCorrect() {
-        val x = listOf(0f, 1f, 2f, 3f, 4f)
-        val y = listOf(0f, 1f, 4f, 9f, 16f)
+        val x = floatArrayOf(0f, 1f, 2f, 3f, 4f)
+        val y = floatArrayOf(0f, 1f, 4f, 9f, 16f)
 
-        val actual = polyFitLeastSquares(x, y, 2)
+        val actual = polyFitLeastSquares(x, y, x.size, 2)
 
-        assertIsCloseToEquals(actual, listOf(0f, 0f, 1f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 0f, 1f))
     }
 
     @Test
     fun polyFitLeastSquares_quadratic4PointsImperfect_isCorrect() {
-        val x = listOf(0f, 1f, 2f, 1f)
-        val y = listOf(0f, -1f, 0f, -2f)
+        val x = floatArrayOf(0f, 1f, 2f, 1f)
+        val y = floatArrayOf(0f, -1f, 0f, -2f)
 
-        val actual = polyFitLeastSquares(x, y, 2)
+        val actual = polyFitLeastSquares(x, y, x.size, 2)
 
-        assertIsCloseToEquals(actual, listOf(0f, -3f, 1.5f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, -3f, 1.5f))
     }
 
     @Test
     fun polyFitLeastSquares_cubic4PointsActuallyLinear_isCorrect() {
-        val x = listOf(0f, 1f, 2f, 3f)
-        val y = listOf(0f, 1f, 2f, 3f)
+        val x = floatArrayOf(0f, 1f, 2f, 3f)
+        val y = floatArrayOf(0f, 1f, 2f, 3f)
 
-        val actual = polyFitLeastSquares(x, y, 3)
+        val actual = polyFitLeastSquares(x, y, x.size, 3)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1f, 0f, 0f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1f, 0f, 0f))
     }
 
     @Test
     fun polyFitLeastSquares_cubic4Points_isCorrect() {
-        val x = listOf(-1f, 0f, 1f, 2f)
-        val y = listOf(1f, 0f, 1f, 0f)
+        val x = floatArrayOf(-1f, 0f, 1f, 2f)
+        val y = floatArrayOf(1f, 0f, 1f, 0f)
 
-        val actual = polyFitLeastSquares(x, y, 3)
+        val actual = polyFitLeastSquares(x, y, x.size, 3)
 
-        assertIsCloseToEquals(actual, listOf(0f, .66666f, 1f, -.66666f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, .66666f, 1f, -.66666f))
     }
 
     @Test
     fun polyFitLeastSquares_cubic6PointsImperfect_isCorrect() {
-        val x = listOf(-1f, 0f, 1f, 2f, 0f, 1f)
-        val y = listOf(1f, 0f, 1f, 0f, 1f, 0f)
+        val x = floatArrayOf(-1f, 0f, 1f, 2f, 0f, 1f)
+        val y = floatArrayOf(1f, 0f, 1f, 0f, 1f, 0f)
 
-        val actual = polyFitLeastSquares(x, y, 3)
+        val actual = polyFitLeastSquares(x, y, x.size, 3)
 
-        assertIsCloseToEquals(actual, listOf(.5f, -.083333f, .25f, -.16666f))
+        assertIsCloseToEquals(actual, floatArrayOf(.5f, -.083333f, .25f, -.16666f))
     }
 
     @Test
     fun polyFitLeastSquares_1Point_isCorrect() {
-        val x = listOf(0f)
-        val y = listOf(13f)
+        val x = floatArrayOf(0f)
+        val y = floatArrayOf(13f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(13f, 0f))
+        assertIsCloseToEquals(actual, floatArrayOf(13f, 0f))
     }
 
     @Test
     fun polyFitLeastSquares_degreeLargerThanData_isCorrect() {
-        val x = listOf(0f, 1f)
-        val y = listOf(0f, 1f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(0f, 1f)
 
-        val actual = polyFitLeastSquares(x, y, 2)
+        val actual = polyFitLeastSquares(x, y, x.size, 2)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1f, 0f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1f, 0f))
     }
 
     @Test
     fun polyFitLeastSquares_3Points2IdenticalDegree1_isCorrect() {
-        val x = listOf(0f, 0f, 1f)
-        val y = listOf(0f, 0f, 1f)
+        val x = floatArrayOf(0f, 0f, 1f)
+        val y = floatArrayOf(0f, 0f, 1f)
 
-        val actual = polyFitLeastSquares(x, y, 1)
+        val actual = polyFitLeastSquares(x, y, x.size, 1)
 
-        assertIsCloseToEquals(actual, listOf(0f, 1f))
+        assertIsCloseToEquals(actual, floatArrayOf(0f, 1f))
     }
 
     @Test
@@ -215,23 +215,11 @@
     private fun polyFitLeastSquares_degreeIsNegative_throwsIllegalArgumentException(
         degree: Int
     ) {
-        val x = listOf(0f, 1f)
-        val y = listOf(0f, 1f)
+        val x = floatArrayOf(0f, 1f)
+        val y = floatArrayOf(0f, 1f)
 
         val throwable = catchThrowable {
-            polyFitLeastSquares(x, y, degree)
-        }
-
-        assertThat(throwable is IllegalArgumentException).isTrue()
-    }
-
-    @Test
-    fun polyFitLeastSquares_missingData_throwsIllegalArgumentException() {
-        val x = listOf(-1f, 1f, 3f)
-        val y = listOf(1f, 3f)
-
-        val throwable = catchThrowable {
-            polyFitLeastSquares(x, y, 1)
+            polyFitLeastSquares(x, y, x.size, degree)
         }
 
         assertThat(throwable is IllegalArgumentException).isTrue()
@@ -239,11 +227,11 @@
 
     @Test
     fun polyFitLeastSquares_noData_throwsIllegalArgumentException() {
-        val x = listOf<Float>()
-        val y = listOf<Float>()
+        val x = floatArrayOf()
+        val y = floatArrayOf()
 
         val throwable = catchThrowable {
-            polyFitLeastSquares(x, y, 1)
+            polyFitLeastSquares(x, y, x.size, 1)
         }
 
         assertThat(throwable is IllegalArgumentException).isTrue()
@@ -251,11 +239,11 @@
 
     @Test
     fun polyFitLeastSquares_extremeSlope_throwsException() {
-        val x = listOf(0f, Float.MIN_VALUE)
-        val y = listOf(0f, Float.MAX_VALUE)
+        val x = floatArrayOf(0f, Float.MIN_VALUE)
+        val y = floatArrayOf(0f, Float.MAX_VALUE)
 
         val throwable = catchThrowable {
-            polyFitLeastSquares(x, y, 1)
+            polyFitLeastSquares(x, y, x.size, 1)
         }
 
         assertThat(throwable is IllegalArgumentException).isTrue()
@@ -263,11 +251,11 @@
 
     @Test
     fun polyFitLeastSquares_3Points2IdenticalDegree2_throwsException() {
-        val x = listOf(0f, 0f, 1f)
-        val y = listOf(0f, 0f, 1f)
+        val x = floatArrayOf(0f, 0f, 1f)
+        val y = floatArrayOf(0f, 0f, 1f)
 
         val throwable = catchThrowable {
-            polyFitLeastSquares(x, y, 2)
+            polyFitLeastSquares(x, y, x.size, 2)
         }
 
         assertThat(throwable is IllegalArgumentException).isTrue()
@@ -286,8 +274,8 @@
     }
 
     private fun assertIsCloseToEquals(
-        actual: List<Float>,
-        expected: List<Float>
+        actual: FloatArray,
+        expected: FloatArray
     ) {
         assertThat(expected.size).isEqualTo(expected.size)
         expected.forEachIndexed() { index, value ->
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/build.gradle b/constraintlayout/constraintlayout-compose/integration-tests/demos/build.gradle
index 4f17087..d552342 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/build.gradle
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/build.gradle
@@ -29,6 +29,7 @@
     implementation(project(":compose:ui:ui"))
     implementation(project(":compose:animation:animation"))
     implementation(project(":compose:material:material"))
+    implementation(project(":compose:material:material-icons-extended"))
     implementation(project(":compose:ui:ui-tooling-preview"))
     debugImplementation(project(":compose:ui:ui-tooling"))
 }
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
index 20dacf6..935adbe 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/AllDemos.kt
@@ -23,10 +23,12 @@
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Row
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxHeight
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Button
 import androidx.compose.material.Icon
 import androidx.compose.material.MaterialTheme
 import androidx.compose.material.Text
@@ -52,7 +54,13 @@
     listOf(
         ComposeDemo("CustomColorInKeyAttributes") { CustomColorInKeyAttributesDemo() },
         ComposeDemo("SimpleOnSwipe") { SimpleOnSwipe() },
-        ComposeDemo("AnimatedChainOrientation") { ChainsAnimatedOrientationDemo() }
+        ComposeDemo("AnimatedChainOrientation") { ChainsAnimatedOrientationDemo() },
+        ComposeDemo("CollapsibleToolbar w/ Column") { ToolBarDslDemo() },
+        ComposeDemo("CollapsibleToolbar w/ LazyColumn") { ToolBarLazyDslDemo() },
+        ComposeDemo("MotionLayout in LazyList") { MotionInLazyColumnDslDemo() },
+        ComposeDemo("Animated Graphs") { AnimateGraphsOnRevealDemo() },
+        ComposeDemo("Animated Reactions Selector") { ReactionSelectorDemo() },
+        ComposeDemo("Animated Puzzle Pieces") { AnimatedPuzzlePiecesDemo() }
     )
 
 /**
@@ -61,43 +69,81 @@
 @Preview
 @Composable
 fun ComposeConstraintLayoutDemos() {
-    var displayedDemo by remember { mutableStateOf<ComposeDemo?>(null) }
+    var displayedDemoIndex by remember { mutableStateOf(-1) }
+    val maxIndex = AllComposeConstraintLayoutDemos.size - 1
     Column {
         Column {
-            displayedDemo?.let {
-                // Header with back button
-                Row(
-                    modifier = Modifier
-                        .fillMaxWidth()
-                        .height(50.dp)
-                        .background(Color.White)
-                        .graphicsLayer(shadowElevation = 2f)
-                        .clickable { displayedDemo = null }, // Return to list of demos
-                    verticalAlignment = Alignment.CenterVertically
-                ) {
-                    Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
-                    Text(text = it.title)
+            when (displayedDemoIndex) {
+                -1 -> {
+                    // Main Title
+                    Text(text = "ComposeConstraintLayoutDemos", style = MaterialTheme.typography.h6)
+                    Spacer(modifier = Modifier.height(8.dp))
                 }
-            } ?: kotlin.run {
-                // Main Title
-                Text(text = "ComposeConstraintLayoutDemos", style = MaterialTheme.typography.h6)
-                Spacer(modifier = Modifier.height(8.dp))
+
+                else -> {
+                    // Header with back button
+                    val composeDemo = AllComposeConstraintLayoutDemos[displayedDemoIndex]
+                    Row(
+                        modifier = Modifier
+                            .fillMaxWidth()
+                            .height(50.dp)
+                            .background(Color.White)
+                            .graphicsLayer(shadowElevation = 2f),
+                        verticalAlignment = Alignment.CenterVertically,
+                        horizontalArrangement = Arrangement.SpaceBetween
+                    ) {
+                        Row(
+                            modifier = Modifier
+                                .fillMaxHeight()
+                                .weight(1f, true)
+                                .clickable { displayedDemoIndex = -1 },
+                            verticalAlignment = Alignment.CenterVertically
+                        ) {
+                            Icon(imageVector = Icons.Default.ArrowBack, contentDescription = "Back")
+                            Text(text = composeDemo.title)
+                        }
+                        Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) {
+                            Button(onClick = {
+                                displayedDemoIndex = if (displayedDemoIndex == 0) {
+                                    maxIndex
+                                } else {
+                                    displayedDemoIndex - 1
+                                }
+                            }) {
+                                Text("Prev")
+                            }
+                            Button(onClick = {
+                                displayedDemoIndex = if (displayedDemoIndex == maxIndex) {
+                                    0
+                                } else {
+                                    displayedDemoIndex + 1
+                                }
+                            }) {
+                                Text("Next")
+                            }
+                        }
+                    }
+                }
             }
         }
         Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
-            displayedDemo?.let { demo ->
-                // Display selected demo
-                Box(
-                    Modifier
-                        .fillMaxWidth()
-                        .weight(1.0f, true)
-                ) {
-                    demo.content()
+            when (displayedDemoIndex) {
+                -1 -> {
+                    // Display list of demos
+                    AllComposeConstraintLayoutDemos.forEachIndexed { index, composeDemo ->
+                        ComposeDemoItem(composeDemo.title) { displayedDemoIndex = index }
+                    }
                 }
-            } ?: kotlin.run {
-                // Display list of demos
-                AllComposeConstraintLayoutDemos.forEach {
-                    ComposeDemoItem(it.title) { displayedDemo = it }
+
+                else -> {
+                    // Display selected demo
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .weight(1.0f, true)
+                    ) {
+                        AllComposeConstraintLayoutDemos[displayedDemoIndex].content()
+                    }
                 }
             }
         }
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt
index 5611bc4..06cff7e 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ChainsDemo.kt
@@ -35,9 +35,17 @@
 import androidx.compose.ui.tooling.preview.Preview
 import androidx.compose.ui.unit.dp
 import androidx.constraintlayout.compose.ConstraintLayout
+import androidx.constraintlayout.compose.ConstraintLayoutBaseScope
 import androidx.constraintlayout.compose.ConstraintSet
+import androidx.constraintlayout.compose.ConstraintSetScope
 import androidx.constraintlayout.compose.Dimension
 
+/**
+ * Shows the usage of `animateChanges = true` with a chain that changes orientation.
+ *
+ * Also shown here, usage of [ConstraintLayoutBaseScope.withChainParams], [Dimension.ratio] and
+ * [ConstraintSetScope.createRefsFor].
+ */
 @Preview
 @Composable
 fun ChainsAnimatedOrientationDemo() {
@@ -47,9 +55,14 @@
     Column(Modifier.fillMaxSize()) {
         ConstraintLayout(
             constraintSet = ConstraintSet {
+                // Create multiple references using destructuring declaration
                 val (box0, box1, box2) = createRefsFor("box0", "box1", "box2")
+
+                // Assign Chain element margins with `withChainParams`
                 box1.withChainParams(8.dp, 8.dp, 8.dp, 8.dp)
 
+                // When State value of `isHorizontal` changes, ConstraintLayout will automatically
+                // animate ot the resulting ConstraintSet
                 if (isHorizontal) {
                     constrain(box0, box1, box2) {
                         width = Dimension.fillToConstraints
@@ -76,7 +89,7 @@
                     createVerticalChain(box0, box1, box2)
                 }
             },
-            animateChanges = true,
+            animateChanges = true, // Set to true, to automatically animate on ConstraintSet changes
             animationSpec = tween(800),
             modifier = Modifier
                 .fillMaxWidth()
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CollapsibleToolbarDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CollapsibleToolbarDemo.kt
new file mode 100644
index 0000000..21dd10a4
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CollapsibleToolbarDemo.kt
@@ -0,0 +1,307 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMotionApi::class)
+
+package androidx.constraintlayout.compose.demos
+
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.tooling.preview.datasource.LoremIpsum
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.constraintlayout.compose.Dimension
+import androidx.constraintlayout.compose.ExperimentalMotionApi
+import androidx.constraintlayout.compose.MotionLayout
+import androidx.constraintlayout.compose.MotionScene
+
+/**
+ * A demo of using MotionLayout as a collapsing Toolbar using the DSL to define the MotionScene.
+ *
+ * This is based on using
+ *
+ * ```
+ * Column(
+ *  horizontalAlignment = Alignment.CenterHorizontally,
+ *  modifier = Modifier.verticalScroll(scroll)
+ * )
+ * ```
+ * The Column's modifier  Modifier.verticalScroll(scroll) will modify scroll.value as it scrolls.
+ * We can use this value with a little math to calculate the appropriate progress.
+ *
+ * When the Column is at the start the MotionLayout sits on top of the Spacer. As the user scrolls
+ * up the MotionLayout shrinks with the scrolling Spacer then, stops.
+ */
+@Preview(group = "scroll", device = "spec:width=480dp,height=800dp,dpi=440")
+@Composable
+fun ToolBarDslDemo() {
+    val scroll = rememberScrollState(0)
+    val big = 250.dp
+    val small = 50.dp
+    val scene = MotionScene {
+        val (title, image, icon) = createRefsFor("title", "image", "icon")
+
+        val start1 = constraintSet {
+            constrain(title) {
+                bottom.linkTo(image.bottom)
+                start.linkTo(image.start)
+            }
+            constrain(image) {
+                width = Dimension.matchParent
+                height = Dimension.value(big)
+                top.linkTo(parent.top)
+                customColor("cover", Color(0xFF000000))
+            }
+            constrain(icon) {
+                top.linkTo(image.top, 16.dp)
+                start.linkTo(image.start, 16.dp)
+                alpha = 0f
+            }
+        }
+        val end1 = constraintSet {
+            constrain(title) {
+                bottom.linkTo(image.bottom)
+                start.linkTo(icon.end)
+                centerVerticallyTo(image)
+                scaleX = 0.7f
+                scaleY = 0.7f
+            }
+            constrain(image) {
+                width = Dimension.matchParent
+                height = Dimension.value(small)
+                top.linkTo(parent.top)
+                customColor("cover", Color(0xFF0000FF))
+            }
+            constrain(icon) {
+                top.linkTo(image.top, 16.dp)
+                start.linkTo(image.start, 16.dp)
+            }
+        }
+        transition(start1, end1, "default") {}
+    }
+
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally,
+        modifier = Modifier.verticalScroll(scroll)
+    ) {
+        Spacer(Modifier.height(big))
+        repeat(5) {
+            Text(
+                text = LoremIpsum(222).values.first(),
+                modifier = Modifier
+                    .background(Color.White)
+                    .padding(16.dp)
+            )
+        }
+    }
+    val gap = with(LocalDensity.current) { big.toPx() - small.toPx() }
+    val progress = minOf(scroll.value / gap, 1f)
+
+    MotionLayout(
+        modifier = Modifier.fillMaxSize(),
+        motionScene = scene,
+        progress = progress
+    ) {
+        Image(
+            modifier = Modifier
+                .layoutId("image")
+                .background(customColor("image", "cover")),
+            imageVector = Icons.Default.Face,
+            contentDescription = null,
+            contentScale = ContentScale.Crop,
+            colorFilter = ColorFilter.tint(Color(0x51FFFFFF))
+        )
+        Image(
+            modifier = Modifier.layoutId("icon"),
+            imageVector = Icons.Default.Menu,
+            contentDescription = null,
+            colorFilter = ColorFilter.tint(Color.White)
+        )
+        Text(
+            modifier = Modifier.layoutId("title"),
+            text = "San Francisco",
+            fontSize = 30.sp,
+            color = Color.White
+        )
+    }
+}
+
+/**
+ * A demo using MotionLayout as a collapsing Toolbar using the DSL to define the MotionScene, where
+ * the scrolling of the LazyColumn is obtained with a NestedScrollConnection.
+ *
+ * ```
+ * LazyColumn(
+ *   Modifier
+ *     .fillMaxWidth()
+ *     .nestedScroll(nestedScrollConnection)
+ * ) {
+ *   items(100) {
+ *     Text(text = "item $it", modifier = Modifier.padding(4.dp))
+ *   }
+ * }
+ * ```
+ *
+ * A NestedScrollConnection object is passed to a LazyColumn Composable via a modifier
+ * (Modifier.nestedScroll(nestedScrollConnection)).
+ *
+ * When the onPreScroll of the NestedScrollConnection is called It returns the amount of "offset" to
+ * absorb and uses the offset to collapse the MotionLayout.
+ */
+@Preview(group = "scroll", device = "spec:width=480dp,height=800dp,dpi=440")
+@Composable
+fun ToolBarLazyDslDemo() {
+    val big = 250.dp
+    val small = 50.dp
+    val scene = MotionScene {
+        val title = createRefFor("title")
+        val image = createRefFor("image")
+        val icon = createRefFor("icon")
+
+        val start1 = constraintSet {
+            constrain(title) {
+                bottom.linkTo(image.bottom)
+                start.linkTo(image.start)
+            }
+            constrain(image) {
+                width = Dimension.matchParent
+                height = Dimension.value(big)
+                top.linkTo(parent.top)
+                customColor("cover", Color(0xFF000000))
+            }
+            constrain(icon) {
+                top.linkTo(image.top, 16.dp)
+                start.linkTo(image.start, 16.dp)
+                alpha = 0f
+            }
+        }
+
+        val end1 = constraintSet {
+            constrain(title) {
+                bottom.linkTo(image.bottom)
+                start.linkTo(icon.end)
+                centerVerticallyTo(image)
+                scaleX = 0.7f
+                scaleY = 0.7f
+            }
+            constrain(image) {
+                width = Dimension.matchParent
+                height = Dimension.value(small)
+                top.linkTo(parent.top)
+                customColor("cover", Color(0xFF0000FF))
+            }
+            constrain(icon) {
+                top.linkTo(image.top, 16.dp)
+                start.linkTo(image.start, 16.dp)
+            }
+        }
+        transition(start1, end1, "default") {}
+    }
+
+    val maxPx = with(LocalDensity.current) { big.roundToPx().toFloat() }
+    val minPx = with(LocalDensity.current) { small.roundToPx().toFloat() }
+    val toolbarHeight = remember { mutableStateOf(maxPx) }
+
+    val nestedScrollConnection = remember {
+        object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                val height = toolbarHeight.value
+
+                if (height + available.y > maxPx) {
+                    toolbarHeight.value = maxPx
+                    return Offset(0f, maxPx - height)
+                }
+
+                if (height + available.y < minPx) {
+                    toolbarHeight.value = minPx
+                    return Offset(0f, minPx - height)
+                }
+
+                toolbarHeight.value += available.y
+                return Offset(0f, available.y)
+            }
+        }
+    }
+
+    val progress = 1 - (toolbarHeight.value - minPx) / (maxPx - minPx)
+
+    Column {
+        MotionLayout(
+            modifier = Modifier,
+            motionScene = scene,
+            progress = progress
+        ) {
+            Image(
+                modifier = Modifier
+                    .layoutId("image")
+                    .background(customColor("image", "cover")),
+                imageVector = Icons.Default.Face,
+                contentDescription = null,
+                contentScale = ContentScale.Crop,
+                colorFilter = ColorFilter.tint(Color(0x51FFFFFF))
+            )
+            Image(
+                modifier = Modifier.layoutId("icon"),
+                imageVector = Icons.Default.Menu,
+                contentDescription = null,
+                colorFilter = ColorFilter.tint(Color.White)
+            )
+            Text(
+                modifier = Modifier.layoutId("title"),
+                text = "San Francisco",
+                fontSize = 30.sp,
+                color = Color.White
+            )
+        }
+        LazyColumn(
+            Modifier
+                .fillMaxWidth()
+                .nestedScroll(nestedScrollConnection)
+        ) {
+            items(100) {
+                Text(text = "item $it", modifier = Modifier.padding(4.dp))
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
index d660ec7..21070d0 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/CustomKeyAttributesDemo.kt
@@ -37,6 +37,15 @@
 import androidx.constraintlayout.compose.SwipeDirection
 import androidx.constraintlayout.compose.SwipeSide
 
+/**
+ * Shows how to use MotionLayout to interpolate custom values such as colors during animation.
+ *
+ * This example animates the background color of a box from [Color.Red] to [Color.Blue] while using
+ * KeyFrames to add intermediate colors [Color.Yellow] and [Color.Green] at 33% and 66% points of
+ * the animation, respectively.
+ *
+ * The animation is driven with an swipe gesture defined by [OnSwipe].
+ */
 @Preview
 @Composable
 fun CustomColorInKeyAttributesDemo() {
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/LazyListItemsDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/LazyListItemsDemo.kt
new file mode 100644
index 0000000..b6af84a
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/LazyListItemsDemo.kt
@@ -0,0 +1,256 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMotionApi::class)
+
+package androidx.constraintlayout.compose.demos
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.Text
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.clip
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.sp
+import androidx.constraintlayout.compose.Dimension
+import androidx.constraintlayout.compose.ExperimentalMotionApi
+import androidx.constraintlayout.compose.MotionLayout
+import androidx.constraintlayout.compose.MotionScene
+import kotlin.random.Random
+
+/**
+ * Shows how to use MotionLayout to have animated expandable items in a LazyColumn.
+ *
+ * Where the MotionScene is defined using the DSL.
+ */
+@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
+@Composable
+fun MotionInLazyColumnDslDemo() {
+    val scene = MotionScene {
+        val title = createRefFor("title")
+        val image = createRefFor("image")
+        val icon = createRefFor("icon")
+
+        val start1 = constraintSet {
+            constrain(title) {
+                centerVerticallyTo(icon)
+                start.linkTo(icon.end, 16.dp)
+            }
+            constrain(image) {
+                width = Dimension.value(40.dp)
+                height = Dimension.value(40.dp)
+                centerVerticallyTo(icon)
+                end.linkTo(parent.end, 8.dp)
+            }
+            constrain(icon) {
+                top.linkTo(parent.top, 16.dp)
+                bottom.linkTo(parent.bottom, 16.dp)
+                start.linkTo(parent.start, 16.dp)
+            }
+        }
+
+        val end1 = constraintSet {
+            constrain(title) {
+                bottom.linkTo(parent.bottom)
+                start.linkTo(parent.start)
+                scaleX = 0.7f
+                scaleY = 0.7f
+            }
+            constrain(image) {
+                width = Dimension.matchParent
+                height = Dimension.value(200.dp)
+                centerVerticallyTo(parent)
+            }
+            constrain(icon) {
+                top.linkTo(parent.top, 16.dp)
+                start.linkTo(parent.start, 16.dp)
+            }
+        }
+        transition(start1, end1, "default") {}
+    }
+
+    val model = remember { BooleanArray(100) }
+
+    LazyColumn {
+        items(100) {
+            Box(modifier = Modifier.padding(3.dp)) {
+                var animateToEnd by remember { mutableStateOf(model[it]) }
+
+                val progress by animateFloatAsState(
+                    targetValue = if (animateToEnd) 1f else 0f,
+                    animationSpec = tween(700)
+                )
+
+                MotionLayout(
+                    modifier = Modifier
+                        .background(Color(0xFF331B1B))
+                        .fillMaxWidth()
+                        .padding(1.dp),
+                    motionScene = scene,
+                    progress = progress
+                ) {
+                    Image(
+                        modifier = Modifier.layoutId("image"),
+                        imageVector = Icons.Default.Face,
+                        contentDescription = null,
+                        contentScale = ContentScale.Crop
+                    )
+                    Image(
+                        modifier = Modifier
+                            .layoutId("icon")
+                            .clickable {
+                                animateToEnd = !animateToEnd
+                                model[it] = animateToEnd
+                            },
+                        imageVector = Icons.Default.Menu,
+                        contentDescription = null,
+                        colorFilter = ColorFilter.tint(Color.White)
+                    )
+                    Text(
+                        modifier = Modifier.layoutId("title"),
+                        text = "San Francisco $it",
+                        fontSize = 30.sp,
+                        color = Color.White
+                    )
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Shows how to use MotionLayout to have animated graphs in a LazyColumn, where each graph is
+ * animated as it's revealed.
+ *
+ * Demonstrates how to dynamically create constraints based on input. See [DynamicGraph]. Where
+ * constraints are created to lay out the given values into a single graph layout.
+ */
+@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
+@Composable
+fun AnimateGraphsOnRevealDemo() {
+    val graphs = mutableListOf<List<Float>>()
+    for (i in 0..100) {
+        val values = FloatArray(10) { Random.nextInt(100).toFloat() + 10f }.asList()
+        graphs.add(values)
+    }
+    LazyColumn {
+        items(100) {
+            Box(
+                modifier = Modifier
+                    .padding(3.dp)
+                    .height(200.dp)
+            ) {
+                DynamicGraph(graphs[it])
+            }
+        }
+    }
+}
+
+@Preview(group = "scroll", device = "spec:shape=Normal,width=480,height=800,unit=dp,dpi=440")
+@Composable
+private fun DynamicGraph(
+    values: List<Float> = listOf<Float>(12f, 32f, 21f, 32f, 2f),
+    max: Int = 100
+) {
+    val scale = values.map { (it * 0.8f) / max }
+    val count = values.size
+    val widthPercent = 1 / (count * 2f)
+    val tmpNames = arrayOfNulls<String>(count)
+    for (i in tmpNames.indices) {
+        tmpNames[i] = "foo$i"
+    }
+    val names: List<String> = tmpNames.filterNotNull()
+    val scene = MotionScene {
+        val cols = names.map { createRefFor(it) }.toTypedArray()
+        val start1 = constraintSet {
+            createHorizontalChain(elements = cols)
+            for (i in names.indices) {
+                constrain(cols[i]) {
+                    width = Dimension.percent(widthPercent)
+                    height = Dimension.value(1.dp)
+                    bottom.linkTo(parent.bottom, 16.dp)
+                }
+            }
+        }
+
+        val end1 = constraintSet {
+            createHorizontalChain(elements = cols)
+            for (i in names.indices) {
+                constrain(cols[i]) {
+                    width = Dimension.percent(widthPercent)
+                    height = Dimension.percent(scale[i])
+                    bottom.linkTo(parent.bottom, 16.dp)
+                }
+            }
+        }
+        transition(start1, end1, "default") {
+        }
+    }
+    var animateToEnd by remember { mutableStateOf(true) }
+    val progress = remember { Animatable(0f) }
+
+    // Animate on reveal
+    LaunchedEffect(animateToEnd) {
+        progress.animateTo(
+            if (animateToEnd) 1f else 0f,
+            animationSpec = tween(800)
+        )
+    }
+
+    MotionLayout(
+        modifier = Modifier
+            .background(Color(0xFF221010))
+            .fillMaxSize()
+            .clickable { animateToEnd = !animateToEnd }
+            .padding(1.dp),
+        motionScene = scene,
+        progress = progress.value
+    ) {
+        for (i in 0..count) {
+            Box(
+                modifier = Modifier
+                    .layoutId("foo$i")
+                    .clip(RoundedCornerShape(20.dp))
+                    .background(Color.hsv(i * 240f / count, 0.6f, 0.6f))
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
index 6a9c88f..8fdbe10 100644
--- a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/OnSwipeDemos.kt
@@ -42,6 +42,10 @@
 import androidx.constraintlayout.compose.layoutId
 import androidx.constraintlayout.compose.rememberMotionLayoutState
 
+/**
+ * Shows how to define swipe-driven transitions with `KeyPositions` and custom colors using the
+ * JSON5 syntax for MotionLayout.
+ */
 @Preview
 @Composable
 fun SimpleOnSwipe() {
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/PuzzleDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/PuzzleDemo.kt
new file mode 100644
index 0000000..42be16c
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/PuzzleDemo.kt
@@ -0,0 +1,189 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMotionApi::class)
+
+package androidx.constraintlayout.compose.demos
+
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Face
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.graphics.drawscope.withTransform
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.constraintlayout.compose.Arc
+import androidx.constraintlayout.compose.ConstraintLayoutBaseScope
+import androidx.constraintlayout.compose.Dimension
+import androidx.constraintlayout.compose.ExperimentalMotionApi
+import androidx.constraintlayout.compose.MotionLayout
+import androidx.constraintlayout.compose.MotionScene
+import androidx.constraintlayout.compose.Wrap
+
+/**
+ * Shows how to animate moving pieces of a puzzle using MotionLayout.
+ *
+ * &nbsp;
+ *
+ * The [PuzzlePiece]s are laid out using the [ConstraintLayoutBaseScope.createFlow] helper.
+ *
+ * And the animation is achieved by creating two ConstraintSets. One providing ordered IDs to Flow,
+ * and the other providing a shuffled list of the same IDs.
+ *
+ * @see PuzzlePiece
+ */
+@Preview
+@Composable
+fun AnimatedPuzzlePiecesDemo() {
+    val grid = 5
+    val blocks = grid * grid
+
+    var animateToEnd by remember { mutableStateOf(true) }
+
+    val index = remember { Array(blocks) { it }.apply { shuffle() } }
+    val refId = remember { Array(blocks) { "W$it" } }
+
+    // Recreate scene when order changes (which is driven by toggling `animateToEnd`)
+    val scene = remember(animateToEnd) {
+        MotionScene {
+            val ordered = refId.map { createRefFor(it) }.toTypedArray()
+            val shuffle = index.map { ordered[it] }.toTypedArray()
+            val set1 = constraintSet {
+                val flow = createFlow(
+                    elements = ordered,
+                    maxElement = grid,
+                    wrapMode = Wrap.Aligned,
+                )
+                constrain(flow) {
+                    centerTo(parent)
+                    width = Dimension.ratio("1:1")
+                    height = Dimension.ratio("1:1")
+                }
+                ordered.forEach {
+                    constrain(it) {
+                        width = Dimension.percent(1f / grid)
+                        height = Dimension.ratio("1:1")
+                    }
+                }
+            }
+            val set2 = constraintSet {
+                val flow = createFlow(
+                    elements = shuffle,
+                    maxElement = grid,
+                    wrapMode = Wrap.Aligned,
+                )
+                constrain(flow) {
+                    centerTo(parent)
+                    width = Dimension.ratio("1:1")
+                    height = Dimension.ratio("1:1")
+                }
+                ordered.forEach {
+                    constrain(it) {
+                        width = Dimension.percent(1f / grid)
+                        height = Dimension.ratio("1:1")
+                    }
+                }
+            }
+            transition(set1, set2, "default") {
+                motionArc = Arc.StartHorizontal
+                keyAttributes(*ordered) {
+                    frame(40) {
+                        // alpha = 0.0f
+                        rotationZ = -90f
+                        scaleX = 0.1f
+                        scaleY = 0.1f
+                    }
+                    frame(70) {
+                        rotationZ = 90f
+                        scaleX = 0.1f
+                        scaleY = 0.1f
+                    }
+                }
+            }
+        }
+    }
+
+    val progress by animateFloatAsState(
+        targetValue = if (animateToEnd) 1f else 0f,
+        animationSpec = tween(800)
+    )
+
+    MotionLayout(
+        motionScene = scene,
+        modifier = Modifier
+            .clickable {
+                animateToEnd = !animateToEnd
+                index.shuffle()
+            }
+            .background(Color.Red)
+            .fillMaxSize(),
+        progress = progress
+    ) {
+        val painter = rememberVectorPainter(image = Icons.Default.Face)
+        index.forEachIndexed { i, id ->
+            PuzzlePiece(
+                x = i % grid,
+                y = i / grid,
+                gridSize = grid,
+                painter = painter,
+                modifier = Modifier.layoutId(refId[id])
+            )
+        }
+    }
+}
+
+/**
+ * Composable that displays a fragment of the given surface (provided through [painter]) based on
+ * the given position ([x], [y]) of a square grid of size [gridSize].
+ */
+@Composable
+fun PuzzlePiece(
+    x: Int,
+    y: Int,
+    gridSize: Int,
+    painter: Painter,
+    modifier: Modifier = Modifier
+) {
+    Canvas(modifier.fillMaxSize()) {
+        clipRect {
+            withTransform({
+                scale(scaleY = gridSize.toFloat(), scaleX = gridSize.toFloat())
+                translate(
+                    left = -(x - gridSize / 2) * size.width / gridSize,
+                    top = -(y - gridSize / 2) * size.height / gridSize
+                )
+            }) {
+                with(painter) {
+                    draw(size)
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ReactionSelectorDemo.kt b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ReactionSelectorDemo.kt
new file mode 100644
index 0000000..5377ef1
--- /dev/null
+++ b/constraintlayout/constraintlayout-compose/integration-tests/demos/src/main/java/androidx/constraintlayout/compose/demos/ReactionSelectorDemo.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+@file:OptIn(ExperimentalMotionApi::class)
+
+package androidx.constraintlayout.compose.demos
+
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.background
+import androidx.compose.foundation.clickable
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshotFlow
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.constraintlayout.compose.ExperimentalMotionApi
+import androidx.constraintlayout.compose.MotionLayout
+import androidx.constraintlayout.compose.MotionScene
+
+/**
+ * A demo of using MotionLayout to create a complex animated emoji selector.
+ */
+@Preview
+@Composable
+fun ReactionSelectorDemo() {
+    var selected by remember { mutableStateOf(3) }
+    val transitionName = remember { mutableStateOf("transition1") }
+    val emojis = remember { "😀 🙂 🤨 😐 😒 😬".split(' ') }
+    val emojiNames = remember {
+        listOf<String>(
+            "Grinning Face",
+            "Slightly Smiling Face",
+            "Face with Raised Eyebrow",
+            "Neutral Face",
+            "Unamused Face",
+            "Grimacing Face"
+        )
+    }
+
+    val scene = MotionScene {
+        val emojiIds = emojis.map { createRefFor(it) }.toTypedArray()
+        val titleIds = emojiNames.map { createRefFor(it) }.toTypedArray()
+
+        val start1 = constraintSet {
+            createHorizontalChain(elements = emojiIds)
+            emojiIds.forEach {
+                constrain(it) {
+                    top.linkTo(parent.top, 10.dp)
+                }
+            }
+            titleIds.forEachIndexed { index, title ->
+                constrain(title) {
+                    top.linkTo(emojiIds[0].bottom, 10.dp)
+                    start.linkTo(emojiIds[index].start)
+                    end.linkTo(emojiIds[index].end)
+                    bottom.linkTo(parent.bottom, 10.dp)
+                    scaleX = 0.1f
+                    alpha = 0f
+                }
+            }
+        }
+        val ends = titleIds.map {
+            constraintSet(extendConstraintSet = start1) {
+                constrain(it) {
+                    scaleX = 1f
+                    alpha = 1f
+                }
+            }
+        }
+        ends.mapIndexed { index, end ->
+            transition(start1, end, "transition$index") {
+            }
+        }
+    }
+    val progress = remember { Animatable(0f) }
+    val selectedFlow = snapshotFlow { selected }
+
+    LaunchedEffect(Unit) {
+        selectedFlow.collect {
+            progress.snapTo(0f)
+            transitionName.value = "transition$it"
+            progress.animateTo(
+                targetValue = 1f,
+                animationSpec = tween(800)
+            )
+        }
+    }
+
+    Column {
+        MotionLayout(
+            modifier = Modifier
+                .background(Color(0xff334433))
+                .fillMaxWidth(),
+            motionScene = scene,
+            transitionName = transitionName.value,
+            progress = progress.value
+        ) {
+            emojis.forEachIndexed { index, icon ->
+                Text(
+                    text = icon,
+                    modifier = Modifier
+                        .layoutId(icon)
+                        .clickable {
+                            selected = index
+                        }
+                )
+            }
+            emojiNames.forEach { name ->
+                Text(
+                    text = name,
+                    color = Color.White,
+                    modifier = Modifier.layoutId(name)
+                )
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
index 733694d..f790ba9 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/ConstraintLayoutTest.kt
@@ -17,6 +17,7 @@
 package androidx.constraintlayout.compose
 
 import android.content.Context
+import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.IntrinsicSize
@@ -36,6 +37,8 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.layout.FirstBaseline
 import androidx.compose.ui.layout.boundsInParent
 import androidx.compose.ui.layout.layout
@@ -50,10 +53,13 @@
 import androidx.compose.ui.platform.ValueElement
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertHeightIsEqualTo
 import androidx.compose.ui.test.assertPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.IntOffset
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
@@ -62,7 +68,9 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import kotlin.math.roundToInt
+import kotlin.test.assertNotEquals
 import org.junit.After
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNull
@@ -2465,6 +2473,75 @@
         }
     }
 
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testVisibility_withInlineDsl() = with(rule.density) {
+        val rootSizePx = 100
+        val boxSizePx = 10
+        var box1Position = IntOffset.Zero
+        val expectedInitialBox1Position = Offset(
+            (rootSizePx + boxSizePx) / 2f,
+            (rootSizePx - boxSizePx) / 2f
+        ).round()
+
+        val boxVisibility = mutableStateOf(Visibility.Visible)
+
+        rule.setContent {
+            ConstraintLayout(Modifier.size(rootSizePx.toDp())) {
+                val (box0, box1) = createRefs()
+
+                Box(
+                    Modifier
+                        .testTag("box0")
+                        .constrainAs(box0) {
+                            centerTo(parent)
+                            visibility = boxVisibility.value
+                        }
+                        .background(Color.Red)
+                ) {
+                    Box(Modifier.size(boxSizePx.toDp()))
+                }
+                Box(
+                    Modifier
+                        .testTag("box1")
+                        .constrainAs(box1) {
+                            width = boxSizePx.toDp().asDimension
+                            height = boxSizePx.toDp().asDimension
+
+                            top.linkTo(box0.top)
+                            start.linkTo(box0.end)
+                        }
+                        .background(Color.Blue)
+                        .onGloballyPositioned {
+                            box1Position = it
+                                .positionInParent()
+                                .round()
+                        }
+                )
+            }
+        }
+        rule.waitForIdle()
+
+        var color = rule.onNodeWithTag("box0").captureToImage().asAndroidBitmap().getColor(5, 5)
+        assertEquals(Color.Red.toArgb(), color.toArgb())
+        assertEquals(expectedInitialBox1Position, box1Position)
+
+        boxVisibility.value = Visibility.Invisible
+        rule.waitForIdle()
+
+        color = rule.onNodeWithTag("box0").captureToImage().asAndroidBitmap().getColor(5, 5)
+        assertNotEquals(Color.Red.toArgb(), color.toArgb())
+        assertEquals(expectedInitialBox1Position, box1Position)
+
+        boxVisibility.value = Visibility.Gone
+        rule.waitForIdle()
+
+        // Dp.Unspecified since Gone Composables are not placed
+        rule.onNodeWithTag("box0").assertWidthIsEqualTo(Dp.Unspecified)
+        rule.onNodeWithTag("box0").assertHeightIsEqualTo(Dp.Unspecified)
+        assertEquals(Offset(rootSizePx / 2f, rootSizePx / 2f).round(), box1Position)
+    }
+
     private fun listAnchors(box: ConstrainedLayoutReference): List<ConstrainScope.() -> Unit> {
         // TODO(172055763) directly construct an immutable list when Lint supports it
         val anchors = mutableListOf<ConstrainScope.() -> Unit>()
diff --git a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
index 9ccf8d8..4ae97ba 100644
--- a/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidAndroidTest/kotlin/androidx/constraintlayout/compose/MotionLayoutTest.kt
@@ -54,6 +54,7 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.text.PlatformTextStyle
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.font.FontWeight
@@ -502,11 +503,13 @@
 @Composable
 private fun CustomTextSize(modifier: Modifier, progress: Float) {
     val context = LocalContext.current
+    @Suppress("DEPRECATION")
     CompositionLocalProvider(
         LocalDensity provides Density(1f, 1f),
         LocalTextStyle provides TextStyle(
             fontFamily = FontFamily.Monospace,
-            fontWeight = FontWeight.Normal
+            fontWeight = FontWeight.Normal,
+            platformStyle = PlatformTextStyle(includeFontPadding = true)
         )
     ) {
         MotionLayout(
diff --git a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
index 72139b8..92890ea 100644
--- a/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
+++ b/constraintlayout/constraintlayout-compose/src/androidMain/kotlin/androidx/constraintlayout/compose/ConstraintLayout.kt
@@ -1654,6 +1654,12 @@
     frame: WidgetFrame,
     offset: IntOffset = IntOffset.Zero
 ) {
+    if (frame.visibility == ConstraintWidget.GONE) {
+        if (DEBUG) {
+            Log.d("CCL", "Widget: ${frame.id} is Gone. Skipping placement.")
+        }
+        return
+    }
     if (frame.isDefaultTransform) {
         val x = frame.left - offset.x
         val y = frame.top - offset.y
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
index 372d3ad..582bb9b 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/state/ConstraintSetParser.java
@@ -1630,6 +1630,7 @@
                         break;
                     case "invisible":
                         reference.visibility(ConstraintWidget.INVISIBLE);
+                        reference.alpha(0f);
                         break;
                     case "gone":
                         reference.visibility(ConstraintWidget.GONE);
diff --git a/coordinatorlayout/OWNERS b/coordinatorlayout/OWNERS
new file mode 100644
index 0000000..347e71c
--- /dev/null
+++ b/coordinatorlayout/OWNERS
@@ -0,0 +1,2 @@
+aelias@google.com
+ryanmentley@google.com
diff --git a/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java b/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
index 5f229c4..899b3e6 100644
--- a/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/os/TraceCompatTest.java
@@ -18,6 +18,8 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assume.assumeTrue;
+
 import static java.nio.charset.StandardCharsets.UTF_8;
 
 import android.app.UiAutomation;
@@ -37,6 +39,7 @@
 import org.junit.Test;
 
 import java.io.ByteArrayOutputStream;
+import java.io.File;
 import java.io.IOException;
 import java.util.regex.Matcher;
 import java.util.regex.Pattern;
@@ -46,21 +49,34 @@
 public final class TraceCompatTest {
 
     private static final int TRACE_BUFFER_SIZE = 8192;
+
+    private static final boolean TRACE_AVAILABLE;
+
+    static {
+        // Check if tracing is available via debugfs or tracefs
+        TRACE_AVAILABLE = new File("/sys/kernel/debug/tracing/trace_marker").exists()
+                || new File("/sys/kernel/tracing/trace_marker").exists();
+    }
+
     private ByteArrayOutputStream mByteArrayOutputStream;
 
     @Before
     public void setUp() {
+        assumeTrue("Tracing is not available via debugfs or tracefs.", TRACE_AVAILABLE);
         mByteArrayOutputStream = new ByteArrayOutputStream();
     }
 
     @After
     public void stopAtrace() throws IOException {
-        // Since API 23, 'async_stop' will work. On lower API levels it was broken (see aosp/157142)
-        if (Build.VERSION.SDK_INT >= 23) {
-            executeCommand("atrace --async_stop");
-        } else {
-            // Ensure tracing is not currently running by performing a short synchronous trace.
-            executeCommand("atrace -t 0");
+        if (TRACE_AVAILABLE) {
+            // Since API 23, 'async_stop' will work. On lower API levels it was broken
+            // (see aosp/157142)
+            if (Build.VERSION.SDK_INT >= 23) {
+                executeCommand("atrace --async_stop");
+            } else {
+                // Ensure tracing is not currently running by performing a short synchronous trace.
+                executeCommand("atrace -t 0");
+            }
         }
     }
 
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompat.java b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
index 124fa91..355b4f0 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -4280,12 +4280,14 @@
              * where the platform doesn't support the MIME type, the original text provided in the
              * constructor will be used.
              *
-             * @param dataMimeType The MIME type of the content
+             * @param dataMimeType The MIME type of the content. See
+             * {@link android.graphics.ImageDecoder#isMimeTypeSupported(String)}
+             * for a list of supported image MIME types.
              * @param dataUri The uri containing the content whose type is given by the MIME type.
              * <p class="note">
+             * Notification Listeners including the System UI need permission to access the
+             * data the Uri points to. The recommended ways to do this are:
              * <ol>
-             *   <li>Notification Listeners including the System UI need permission to access the
-             *       data the Uri points to. The recommended ways to do this are:</li>
              *   <li>Store the data in your own ContentProvider, making sure that other apps have
              *       the correct permission to access your provider. The preferred mechanism for
              *       providing access is to use per-URI permissions which are temporary and only
diff --git a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/UwbClientSessionScopeRxTest.java b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/UwbClientSessionScopeRxTest.java
index 342f0ae..e8e3295 100644
--- a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/UwbClientSessionScopeRxTest.java
+++ b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/UwbClientSessionScopeRxTest.java
@@ -39,7 +39,7 @@
 
     private final UwbManager mUwbManager = new TestUwbManager();
     private final RangingParameters rangingParameters = new RangingParameters(
-            RangingParameters.UWB_CONFIG_ID_1,
+            RangingParameters.CONFIG_UNICAST_DS_TWR,
             0,
             /*sessionKeyInfo=*/ null,
             /*complexChannel=*/ null,
diff --git a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbClientSessionScope.kt b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbClientSessionScope.kt
index 08c673f..a46f50e 100644
--- a/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbClientSessionScope.kt
+++ b/core/uwb/uwb-rxjava3/src/androidTest/java/androidx/core/uwb/rxjava3/mock/TestUwbClientSessionScope.kt
@@ -40,7 +40,7 @@
     private var sessionStarted = false
     private val uwbDevice = createForAddress(ByteArray(0))
     val defaultRangingParameters = RangingParameters(
-        RangingParameters.UWB_CONFIG_ID_1,
+        RangingParameters.CONFIG_UNICAST_DS_TWR,
         0,
         null,
         null,
diff --git a/core/uwb/uwb/api/current.txt b/core/uwb/uwb/api/current.txt
index cd5803b..888340b 100644
--- a/core/uwb/uwb/api/current.txt
+++ b/core/uwb/uwb/api/current.txt
@@ -31,11 +31,12 @@
     property public final byte[]? sessionKeyInfo;
     property public final int updateRateType;
     property public final int uwbConfigType;
+    field public static final int CONFIG_MULTICAST_DS_TWR;
+    field public static final int CONFIG_UNICAST_DS_TWR;
     field public static final androidx.core.uwb.RangingParameters.Companion Companion;
     field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
     field public static final int RANGING_UPDATE_RATE_FREQUENT;
     field public static final int RANGING_UPDATE_RATE_INFREQUENT;
-    field public static final int UWB_CONFIG_ID_1;
   }
 
   public static final class RangingParameters.Companion {
diff --git a/core/uwb/uwb/api/public_plus_experimental_current.txt b/core/uwb/uwb/api/public_plus_experimental_current.txt
index cd5803b..888340b 100644
--- a/core/uwb/uwb/api/public_plus_experimental_current.txt
+++ b/core/uwb/uwb/api/public_plus_experimental_current.txt
@@ -31,11 +31,12 @@
     property public final byte[]? sessionKeyInfo;
     property public final int updateRateType;
     property public final int uwbConfigType;
+    field public static final int CONFIG_MULTICAST_DS_TWR;
+    field public static final int CONFIG_UNICAST_DS_TWR;
     field public static final androidx.core.uwb.RangingParameters.Companion Companion;
     field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
     field public static final int RANGING_UPDATE_RATE_FREQUENT;
     field public static final int RANGING_UPDATE_RATE_INFREQUENT;
-    field public static final int UWB_CONFIG_ID_1;
   }
 
   public static final class RangingParameters.Companion {
diff --git a/core/uwb/uwb/api/restricted_current.txt b/core/uwb/uwb/api/restricted_current.txt
index cd5803b..888340b 100644
--- a/core/uwb/uwb/api/restricted_current.txt
+++ b/core/uwb/uwb/api/restricted_current.txt
@@ -31,11 +31,12 @@
     property public final byte[]? sessionKeyInfo;
     property public final int updateRateType;
     property public final int uwbConfigType;
+    field public static final int CONFIG_MULTICAST_DS_TWR;
+    field public static final int CONFIG_UNICAST_DS_TWR;
     field public static final androidx.core.uwb.RangingParameters.Companion Companion;
     field public static final int RANGING_UPDATE_RATE_AUTOMATIC;
     field public static final int RANGING_UPDATE_RATE_FREQUENT;
     field public static final int RANGING_UPDATE_RATE_INFREQUENT;
-    field public static final int UWB_CONFIG_ID_1;
   }
 
   public static final class RangingParameters.Companion {
diff --git a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/common/TestCommons.kt b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/common/TestCommons.kt
index d508076..ce0dab8 100644
--- a/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/common/TestCommons.kt
+++ b/core/uwb/uwb/src/androidTest/java/androidx/core/uwb/common/TestCommons.kt
@@ -34,7 +34,7 @@
         val NEIGHBOR_2 = byteArrayOf(0xA5.toByte())
         val UWB_DEVICE = UwbDevice.createForAddress(NEIGHBOR_1)
         val RANGING_PARAMETERS = RangingParameters(
-            RangingParameters.UWB_CONFIG_ID_1,
+            RangingParameters.CONFIG_UNICAST_DS_TWR,
             sessionId = 0,
             sessionKeyInfo = null,
             complexChannel = null,
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt
index a55917e..e89c265 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/RangingParameters.kt
@@ -21,7 +21,7 @@
  *
  * @property uwbConfigType
  * The UWB configuration type. One type specifies one fixed set of pre-defined parameters. The
- * UWB config type includes [UWB_CONFIG_ID_1].
+ * UWB config type includes [CONFIG_UNICAST_DS_TWR] and [CONFIG_MULTICAST_DS_TWR].
  *
  * @property sessionId
  * The ID of the ranging session. If the value is SESSION_ID_UNSET (0), it will
@@ -72,7 +72,7 @@
          * <p> Typical use case: device tracking tags
          */
         @JvmField
-        val UWB_CONFIG_ID_1 = 1
+        val CONFIG_UNICAST_DS_TWR = 1
 
         /**
          * Pre-defined one-to-many STATIC STS DS-TWR ranging
@@ -87,7 +87,7 @@
          * <p> Typical use case: smart phone interacts with many smart devices
          */
         @JvmField
-        internal val UWB_CONFIG_ID_2 = 2
+        val CONFIG_MULTICAST_DS_TWR = 2
 
         /** Same as CONFIG_ID_1, except AoA data is not reported. */
         @JvmField
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeAospImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeAospImpl.kt
index fea81c2..55ee243 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeAospImpl.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeAospImpl.kt
@@ -52,7 +52,8 @@
 
         val parametersBuilder1 = androidx.core.uwb.backend.RangingParameters()
         parametersBuilder1.uwbConfigId = when (parameters.uwbConfigType) {
-            RangingParameters.UWB_CONFIG_ID_1 -> RangingParameters.UWB_CONFIG_ID_1
+            RangingParameters.CONFIG_UNICAST_DS_TWR -> RangingParameters.CONFIG_UNICAST_DS_TWR
+            RangingParameters.CONFIG_MULTICAST_DS_TWR -> RangingParameters.CONFIG_MULTICAST_DS_TWR
             RangingParameters.UWB_CONFIG_ID_3 -> RangingParameters.UWB_CONFIG_ID_3
             else -> throw IllegalArgumentException("The selected UWB Config Id is not a valid id.")
         }
diff --git a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt
index ce371f4..fa19f31 100644
--- a/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt
+++ b/core/uwb/uwb/src/main/java/androidx/core/uwb/impl/UwbClientSessionScopeImpl.kt
@@ -55,8 +55,10 @@
         }
 
         val configId = when (parameters.uwbConfigType) {
-            RangingParameters.UWB_CONFIG_ID_1 ->
+            RangingParameters.CONFIG_UNICAST_DS_TWR ->
                 com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.CONFIG_ID_1
+            RangingParameters.CONFIG_MULTICAST_DS_TWR ->
+                com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.CONFIG_ID_2
             RangingParameters.UWB_CONFIG_ID_3 ->
                 com.google.android.gms.nearby.uwb.RangingParameters.UwbConfigId.CONFIG_ID_3
             else ->
@@ -78,7 +80,6 @@
             .setUwbConfigId(configId)
             .setRangingUpdateRate(updateRate)
             .setSessionKeyInfo(parameters.sessionKeyInfo)
-            .setUwbConfigId(parameters.uwbConfigType)
             .setComplexChannel(
                 parameters.complexChannel?.let {
                     UwbComplexChannel.Builder()
diff --git a/cursoradapter/OWNERS b/cursoradapter/OWNERS
new file mode 100644
index 0000000..347e71c
--- /dev/null
+++ b/cursoradapter/OWNERS
@@ -0,0 +1,2 @@
+aelias@google.com
+ryanmentley@google.com
diff --git a/customview/OWNERS b/customview/OWNERS
index 31cd563..9dc5349 100644
--- a/customview/OWNERS
+++ b/customview/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 461406
 alanv@google.com
+aelias@google.com
+ryanmentley@google.com
diff --git a/datastore/datastore-core/src/androidTest/java/androidx/datastore/core/MultiProcessDataStoreMultiProcessTest.kt b/datastore/datastore-core/src/androidTest/java/androidx/datastore/core/MultiProcessDataStoreMultiProcessTest.kt
index e72f298..13bd3d7 100644
--- a/datastore/datastore-core/src/androidTest/java/androidx/datastore/core/MultiProcessDataStoreMultiProcessTest.kt
+++ b/datastore/datastore-core/src/androidTest/java/androidx/datastore/core/MultiProcessDataStoreMultiProcessTest.kt
@@ -19,6 +19,7 @@
 import android.content.Context
 import android.os.Bundle
 import androidx.datastore.core.handlers.NoOpCorruptionHandler
+import androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
 import androidx.test.core.app.ApplicationProvider
 import androidx.testing.TestMessageProto.FooProto
 import com.google.common.truth.Truth.assertThat
@@ -100,8 +101,6 @@
     private lateinit var dataStoreContext: CoroutineContext
     private lateinit var dataStoreScope: TestScope
 
-    private val TAG = "MPDS test"
-
     private val protoSerializer: Serializer<FooProto> = ProtoSerializer<FooProto>(
         FooProto.getDefaultInstance(),
         ExtensionRegistryLite.getEmptyRegistry()
@@ -114,7 +113,7 @@
         return data
     }
 
-    internal fun createDataStore(
+    private fun createDataStore(
         bundle: Bundle,
         scope: TestScope
     ): DataStoreImpl<FooProto> {
@@ -162,7 +161,7 @@
 
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
-                it.let { WRITE_TEXT(it) }
+                WRITE_TEXT(it)
             }
         }
     }
@@ -207,7 +206,7 @@
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
                 waitForSignal()
-                it.let { WRITE_BOOLEAN(it) }
+                WRITE_BOOLEAN(it)
             }
         }
     }
@@ -242,7 +241,7 @@
         val write = async(newSingleThreadContext("blockedWriter")) {
             dataStore.updateData {
                 condition.await()
-                it.let { WRITE_BOOLEAN(it) }
+                WRITE_BOOLEAN(it)
             }
         }
 
@@ -269,7 +268,7 @@
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
                 waitForSignal()
-                it.let { WRITE_TEXT(it) }
+                WRITE_TEXT(it)
             }
         }
     }
@@ -331,14 +330,14 @@
 
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
-                it.let { INCREMENT_INTEGER(it) }
+                INCREMENT_INTEGER(it)
             }
 
             waitForSignal()
 
             val write = async {
                 store.updateData {
-                    it.let { WRITE_BOOLEAN(it) }
+                    WRITE_BOOLEAN(it)
                 }
             }
             waitForSignal()
@@ -391,7 +390,7 @@
 
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
-                it.let { WRITE_TEXT(it) }
+                WRITE_TEXT(it)
             }
         }
     }
@@ -412,7 +411,7 @@
         val write = localScope.async {
             dataStore.updateData {
                 blockWrite.await()
-                it.let { WRITE_BOOLEAN(it) }
+                WRITE_BOOLEAN(it)
             }
         }
 
@@ -443,7 +442,7 @@
 
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
-                it.let { WRITE_TEXT(it) }
+                WRITE_TEXT(it)
             }
         }
     }
@@ -490,23 +489,8 @@
 
         override fun runTest() = runBlocking<Unit> {
             store.updateData {
-                it.let { WRITE_TEXT(it) }
+                WRITE_TEXT(it)
             }
         }
     }
-
-    /**
-     * A corruption handler that attempts to replace the on-disk data with data from produceNewData.
-     *
-     * TODO(zhiyuanwang): replace with androidx.datastore.core.handlers.ReplaceFileCorruptionHandler
-     */
-    private class ReplaceFileCorruptionHandler<T>(
-        private val produceNewData: (CorruptionException) -> T
-    ) : CorruptionHandler<T> {
-
-        @Throws(IOException::class)
-        override suspend fun handleCorruption(ex: CorruptionException): T {
-            return produceNewData(ex)
-        }
-    }
 }
\ No newline at end of file
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index a62f433..99a400b 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -790,14 +790,14 @@
 C/C\+\+: Building ver\.\: [0-9]+\.[0-9]+\.[0-9]+
 C/C\+\+: Packaging for\: (amd\-[0-9]+|armhf\-[0-9]+|x86\-[0-9]+)
 C/C\+\+: Compiling for ARM
-w: \[ksp\] Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\. \- androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDb\.jvmDao\(\)
+w: \[ksp\] Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\. \- androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDb\.jvmDao\(\)
 w: \[ksp\] \$SUPPORT/room/integration\-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.kt:[0-9]+: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
 # > Task :room:integration-tests:room-testapp-kotlin:kaptWithKaptDebugAndroidTestKotlin
-\$OUT_DIR/androidx/room/integration\-tests/room\-testapp\-kotlin/build/tmp/kapt[0-9]+/stubs/withKaptDebugAndroidTest/androidx/room/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.java:[0-9]+: warning: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
+\$OUT_DIR/androidx/room/integration\-tests/room\-testapp\-kotlin/build/tmp/kapt[0-9]+/stubs/withKaptDebugAndroidTest/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest\.java:[0-9]+: warning: Using @JvmName annotation on a function or accessor that will be overridden by Room is not supported\. If this is important for your use case, please file a bug at https://issuetracker\.google\.com/issues/new\?component=[0-9]+ with details\.
 public abstract void jvmDelete\(T t\);
 public abstract void jvmInsert\(@org\.jetbrains\.annotations\.NotNull.*
-public abstract java\.util\.List<androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
-public abstract androidx\.room\.androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
+public abstract java\.util\.List<androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameEntity> jvmQuery\(\);
+public abstract androidx\.room\.integration\.kotlintestapp\.test\.JvmNameInDaoTest\.JvmNameDao jvmDao\(\);
 \^
 # Gradle will log if you are not authenticated to upload scans
 A build scan was not published as you have not authenticated with server 'ge\.androidx\.dev'\.
diff --git a/development/upload_mac_metrics_to_skia/.gitignore b/development/upload_mac_metrics_to_skia/.gitignore
new file mode 100644
index 0000000..16e340a
--- /dev/null
+++ b/development/upload_mac_metrics_to_skia/.gitignore
@@ -0,0 +1 @@
+download_staging/*
\ No newline at end of file
diff --git a/development/upload_mac_metrics_to_skia/upload_mac_metrics_to_skia.py b/development/upload_mac_metrics_to_skia/upload_mac_metrics_to_skia.py
new file mode 100644
index 0000000..fb9316d
--- /dev/null
+++ b/development/upload_mac_metrics_to_skia/upload_mac_metrics_to_skia.py
@@ -0,0 +1,84 @@
+"""
+Helper script to upload metrics files from a given build ID to skia perf. Must be run on gLinux
+because it relies on the 'fetch_artifact' tool.
+"""
+
+
+import subprocess
+import sys
+import os
+import shutil
+import requests
+import json
+
+FETCH_ARTIFACT = "/google/data/ro/projects/android/fetch_artifact"
+
+
+def fetch_artifacts(build_id, branch, target, output_dir, file_path):
+  subprocess.run(
+      [
+          FETCH_ARTIFACT,
+          "--bid",
+          build_id,
+          "--branch",
+          branch,
+          "--target",
+          target,
+          file_path,
+      ],
+      cwd=output_dir,
+      capture_output=True,
+      check=True
+  )
+
+
+def prep_staging_dir(staging_path):
+  current_dir = os.path.dirname(os.path.realpath(__file__))
+  staging_dir = current_dir + staging_path
+  if os.path.isdir(staging_dir):
+    shutil.rmtree(staging_dir)
+  os.makedirs(staging_dir, exist_ok=True)
+  return staging_dir
+
+
+def post_json_to_endpoint(contents, endpoint):
+  requests.post(endpoint, json=contents)
+
+
+def mutate_metrics(contents, build_id, branch):
+  contents["git_hash"] = build_id
+  contents["key"]["branch"] = branch
+
+
+def upload_metrics(download_staging_dir, build_id, branch, endpoint):
+  for file_name in os.listdir(download_staging_dir):
+    full_path = os.path.join(download_staging_dir, file_name)
+    with open(full_path) as src_file:
+      contents = json.load(src_file)
+      mutate_metrics(contents, build_id, branch)
+      post_json_to_endpoint(contents, endpoint)
+
+
+def main(args):
+  if (len(args) < 2):
+    raise Exception("Please provide a build ID")
+  if (len(args) < 3):
+    raise Exception("Please provide an endpoint")
+  build_id = args[1]
+  endpoint = args[2]
+  branch = "androidx-main"
+  target = "androidx_multiplatform_mac_host_tests"
+  file_path = "librarymetrics/**/*.json"
+  download_staging_dir = prep_staging_dir("/download_staging")
+  fetch_artifacts(
+      build_id,
+      branch,
+      target,
+      download_staging_dir,
+      file_path
+  )
+  upload_metrics(download_staging_dir, build_id, branch, endpoint)
+
+
+if __name__ == "__main__":
+  main(sys.argv)
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index e3289e0..4b406d6 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -8,12 +8,13 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.8.0-alpha01")
-    docs("androidx.activity:activity-compose:1.8.0-alpha01")
-    samples("androidx.activity:activity-compose-samples:1.8.0-alpha01")
-    docs("androidx.activity:activity-ktx:1.8.0-alpha01")
-    docs("androidx.ads:ads-identifier:1.0.0-alpha04")
-    docs("androidx.ads:ads-identifier-provider:1.0.0-alpha04")
+    docs("androidx.activity:activity:1.8.0-alpha02")
+    docs("androidx.activity:activity-compose:1.8.0-alpha02")
+    samples("androidx.activity:activity-compose-samples:1.8.0-alpha02")
+    docs("androidx.activity:activity-ktx:1.8.0-alpha02")
+    docs("androidx.ads:ads-identifier:1.0.0-alpha05")
+    docs("androidx.ads:ads-identifier-common:1.0.0-alpha05")
+    docs("androidx.ads:ads-identifier-provider:1.0.0-alpha05")
     docs("androidx.annotation:annotation:1.6.0-rc01")
     docs("androidx.annotation:annotation-experimental:1.3.0")
     docs("androidx.appcompat:appcompat:1.7.0-alpha02")
@@ -29,10 +30,10 @@
     docs("androidx.asynclayoutinflater:asynclayoutinflater:1.1.0-alpha01")
     docs("androidx.asynclayoutinflater:asynclayoutinflater-appcompat:1.1.0-alpha01")
     docs("androidx.autofill:autofill:1.2.0-beta01")
-    docs("androidx.benchmark:benchmark-common:1.2.0-alpha10")
-    docs("androidx.benchmark:benchmark-junit4:1.2.0-alpha10")
-    docs("androidx.benchmark:benchmark-macro:1.2.0-alpha10")
-    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha10")
+    docs("androidx.benchmark:benchmark-common:1.2.0-alpha11")
+    docs("androidx.benchmark:benchmark-junit4:1.2.0-alpha11")
+    docs("androidx.benchmark:benchmark-macro:1.2.0-alpha11")
+    docs("androidx.benchmark:benchmark-macro-junit4:1.2.0-alpha11")
     docs("androidx.biometric:biometric:1.2.0-alpha05")
     docs("androidx.biometric:biometric-ktx:1.2.0-alpha05")
     samples("androidx.biometric:biometric-ktx-samples:1.2.0-alpha05")
@@ -54,60 +55,60 @@
     docs("androidx.cardview:cardview:1.0.0")
     docs("androidx.collection:collection:1.3.0-alpha02")
     docs("androidx.collection:collection-ktx:1.3.0-alpha02")
-    docs("androidx.compose.animation:animation:1.4.0-beta02")
-    docs("androidx.compose.animation:animation-core:1.4.0-beta02")
-    docs("androidx.compose.animation:animation-graphics:1.4.0-beta02")
-    samples("androidx.compose.animation:animation-samples:1.4.0-beta02")
-    samples("androidx.compose.animation:animation-core-samples:1.4.0-beta02")
-    samples("androidx.compose.animation:animation-graphics-samples:1.4.0-beta02")
-    docs("androidx.compose.foundation:foundation:1.4.0-beta02")
-    docs("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.4.0-beta02")
-    samples("androidx.compose.foundation:foundation-samples:1.4.0-beta02")
-    docs("androidx.compose.material3:material3:1.1.0-alpha07")
-    samples("androidx.compose.material3:material3-samples:1.1.0-alpha07")
-    docs("androidx.compose.material3:material3-window-size-class:1.1.0-alpha07")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.1.0-alpha07")
-    docs("androidx.compose.material:material:1.4.0-beta02")
-    docs("androidx.compose.material:material-icons-core:1.4.0-beta02")
-    samples("androidx.compose.material:material-icons-core-samples:1.4.0-beta02")
-    docs("androidx.compose.material:material-ripple:1.4.0-beta02")
-    samples("androidx.compose.material:material-samples:1.4.0-beta02")
-    docs("androidx.compose.runtime:runtime:1.4.0-beta02")
-    docs("androidx.compose.runtime:runtime-livedata:1.4.0-beta02")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.4.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.4.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.4.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.4.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.4.0-beta02")
-    docs("androidx.compose.runtime:runtime-saveable:1.4.0-beta02")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.4.0-beta02")
-    samples("androidx.compose.runtime:runtime-samples:1.4.0-beta02")
+    docs("androidx.compose.animation:animation:1.4.0-rc01")
+    docs("androidx.compose.animation:animation-core:1.4.0-rc01")
+    docs("androidx.compose.animation:animation-graphics:1.4.0-rc01")
+    samples("androidx.compose.animation:animation-samples:1.4.0-rc01")
+    samples("androidx.compose.animation:animation-core-samples:1.4.0-rc01")
+    samples("androidx.compose.animation:animation-graphics-samples:1.4.0-rc01")
+    docs("androidx.compose.foundation:foundation:1.4.0-rc01")
+    docs("androidx.compose.foundation:foundation-layout:1.4.0-rc01")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.4.0-rc01")
+    samples("androidx.compose.foundation:foundation-samples:1.4.0-rc01")
+    docs("androidx.compose.material3:material3:1.1.0-alpha08")
+    samples("androidx.compose.material3:material3-samples:1.1.0-alpha08")
+    docs("androidx.compose.material3:material3-window-size-class:1.1.0-alpha08")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.1.0-alpha08")
+    docs("androidx.compose.material:material:1.4.0-rc01")
+    docs("androidx.compose.material:material-icons-core:1.4.0-rc01")
+    samples("androidx.compose.material:material-icons-core-samples:1.4.0-rc01")
+    docs("androidx.compose.material:material-ripple:1.4.0-rc01")
+    samples("androidx.compose.material:material-samples:1.4.0-rc01")
+    docs("androidx.compose.runtime:runtime:1.4.0-rc01")
+    docs("androidx.compose.runtime:runtime-livedata:1.4.0-rc01")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.4.0-rc01")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.4.0-rc01")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.4.0-rc01")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.4.0-rc01")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.4.0-rc01")
+    docs("androidx.compose.runtime:runtime-saveable:1.4.0-rc01")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.4.0-rc01")
+    samples("androidx.compose.runtime:runtime-samples:1.4.0-rc01")
     docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha03")
-    docs("androidx.compose.ui:ui:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-geometry:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-graphics:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-graphics-samples:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-test:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-test-samples:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-text:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-text-samples:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-tooling:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-tooling-data:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-tooling-preview:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-unit:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-unit-samples:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-util:1.4.0-beta02")
-    docs("androidx.compose.ui:ui-viewbinding:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.4.0-beta02")
-    samples("androidx.compose.ui:ui-samples:1.4.0-beta02")
+    docs("androidx.compose.ui:ui:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-geometry:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-graphics:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-graphics-samples:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-test:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-test-junit4:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-test-samples:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-text:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-text-samples:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-tooling:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-tooling-data:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-tooling-preview:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-unit:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-unit-samples:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-util:1.4.0-rc01")
+    docs("androidx.compose.ui:ui-viewbinding:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.4.0-rc01")
+    samples("androidx.compose.ui:ui-samples:1.4.0-rc01")
     docs("androidx.concurrent:concurrent-futures:1.2.0-alpha01")
     docs("androidx.concurrent:concurrent-futures-ktx:1.2.0-alpha01")
-    docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha07")
-    docs("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha07")
-    docs("androidx.constraintlayout:constraintlayout-core:1.1.0-alpha07")
+    docs("androidx.constraintlayout:constraintlayout:2.2.0-alpha08")
+    docs("androidx.constraintlayout:constraintlayout-compose:1.1.0-alpha08")
+    docs("androidx.constraintlayout:constraintlayout-core:1.1.0-alpha08")
     docs("androidx.contentpager:contentpager:1.0.0")
     docs("androidx.coordinatorlayout:coordinatorlayout:1.2.0")
     docs("androidx.core.uwb:uwb:1.0.0-alpha04")
@@ -119,11 +120,11 @@
     docs("androidx.core:core-role:1.2.0-alpha01")
     docs("androidx.core:core-animation:1.0.0-beta02")
     docs("androidx.core:core-animation-testing:1.0.0-beta01")
-    docs("androidx.core:core:1.10.0-beta01")
-    docs("androidx.core:core-ktx:1.10.0-beta01")
+    docs("androidx.core:core:1.12.0-alpha01")
+    docs("androidx.core:core-ktx:1.12.0-alpha01")
     docs("androidx.core:core-splashscreen:1.1.0-alpha01")
-    docs("androidx.credentials:credentials:1.0.0-alpha03")
-    docs("androidx.credentials:credentials-play-services-auth:1.0.0-alpha03")
+    docs("androidx.credentials:credentials:1.2.0-alpha02")
+    docs("androidx.credentials:credentials-play-services-auth:1.2.0-alpha02")
     docs("androidx.credentials:credentials-provider:1.0.0-alpha03")
     docs("androidx.cursoradapter:cursoradapter:1.0.0")
     docs("androidx.customview:customview:1.2.0-alpha02")
@@ -139,23 +140,23 @@
     docs("androidx.datastore:datastore-rxjava3:1.1.0-alpha01")
     docs("androidx.documentfile:documentfile:1.1.0-alpha01")
     docs("androidx.draganddrop:draganddrop:1.0.0")
-    docs("androidx.drawerlayout:drawerlayout:1.2.0-beta01")
+    docs("androidx.drawerlayout:drawerlayout:1.2.0-rc01")
     docs("androidx.dynamicanimation:dynamicanimation:1.1.0-alpha02")
     docs("androidx.dynamicanimation:dynamicanimation-ktx:1.0.0-alpha03")
-    docs("androidx.emoji2:emoji2:1.3.0-beta03")
-    docs("androidx.emoji2:emoji2-bundled:1.3.0-beta03")
-    docs("androidx.emoji2:emoji2-emojipicker:1.0.0-alpha02")
-    docs("androidx.emoji2:emoji2-views:1.3.0-beta03")
-    docs("androidx.emoji2:emoji2-views-helper:1.3.0-beta03")
+    docs("androidx.emoji2:emoji2:1.3.0-rc01")
+    docs("androidx.emoji2:emoji2-bundled:1.3.0-rc01")
+    docs("androidx.emoji2:emoji2-emojipicker:1.0.0-alpha03")
+    docs("androidx.emoji2:emoji2-views:1.3.0-rc01")
+    docs("androidx.emoji2:emoji2-views-helper:1.3.0-rc01")
     docs("androidx.emoji:emoji:1.2.0-alpha03")
     docs("androidx.emoji:emoji-appcompat:1.2.0-alpha03")
     docs("androidx.emoji:emoji-bundled:1.2.0-alpha03")
     docs("androidx.enterprise:enterprise-feedback:1.1.0")
     docs("androidx.enterprise:enterprise-feedback-testing:1.1.0")
     docs("androidx.exifinterface:exifinterface:1.3.6")
-    docs("androidx.fragment:fragment:1.6.0-alpha06")
-    docs("androidx.fragment:fragment-ktx:1.6.0-alpha06")
-    docs("androidx.fragment:fragment-testing:1.6.0-alpha06")
+    docs("androidx.fragment:fragment:1.6.0-alpha07")
+    docs("androidx.fragment:fragment-ktx:1.6.0-alpha07")
+    docs("androidx.fragment:fragment-testing:1.6.0-alpha07")
     docs("androidx.glance:glance:1.0.0-alpha05")
     docs("androidx.glance:glance-appwidget:1.0.0-alpha05")
     docs("androidx.glance:glance-appwidget-preview:1.0.0-alpha05")
@@ -183,27 +184,27 @@
     docs("androidx.leanback:leanback-paging:1.1.0-alpha09")
     docs("androidx.leanback:leanback-preference:1.2.0-alpha02")
     docs("androidx.leanback:leanback-tab:1.1.0-beta01")
-    docs("androidx.lifecycle:lifecycle-common:2.6.0")
-    docs("androidx.lifecycle:lifecycle-common-java8:2.6.0")
+    docs("androidx.lifecycle:lifecycle-common:2.6.1")
+    docs("androidx.lifecycle:lifecycle-common-java8:2.6.1")
     docs("androidx.lifecycle:lifecycle-extensions:2.2.0")
-    docs("androidx.lifecycle:lifecycle-livedata:2.6.0")
-    docs("androidx.lifecycle:lifecycle-livedata-core:2.6.0")
-    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.0")
-    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.6.0")
-    docs("androidx.lifecycle:lifecycle-process:2.6.0")
-    docs("androidx.lifecycle:lifecycle-reactivestreams:2.6.0")
-    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.6.0")
-    docs("androidx.lifecycle:lifecycle-runtime:2.6.0")
-    docs("androidx.lifecycle:lifecycle-runtime-compose:2.6.0")
-    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.6.0")
-    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0")
-    docs("androidx.lifecycle:lifecycle-runtime-testing:2.6.0")
-    docs("androidx.lifecycle:lifecycle-service:2.6.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0")
-    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.6.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0")
-    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.0")
+    docs("androidx.lifecycle:lifecycle-livedata:2.6.1")
+    docs("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
+    docs("androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.1")
+    docs("androidx.lifecycle:lifecycle-livedata-ktx:2.6.1")
+    docs("androidx.lifecycle:lifecycle-process:2.6.1")
+    docs("androidx.lifecycle:lifecycle-reactivestreams:2.6.1")
+    docs("androidx.lifecycle:lifecycle-reactivestreams-ktx:2.6.1")
+    docs("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    docs("androidx.lifecycle:lifecycle-runtime-compose:2.6.1")
+    samples("androidx.lifecycle:lifecycle-runtime-compose-samples:2.6.1")
+    docs("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
+    docs("androidx.lifecycle:lifecycle-runtime-testing:2.6.1")
+    docs("androidx.lifecycle:lifecycle-service:2.6.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
+    samples("androidx.lifecycle:lifecycle-viewmodel-compose-samples:2.6.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+    docs("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     docs("androidx.loader:loader:1.1.0")
     docs("androidx.localbroadcastmanager:localbroadcastmanager:1.1.0")
     docs("androidx.media2:media2-common:1.2.1")
@@ -234,22 +235,22 @@
     docs("androidx.media3:media3-transformer:1.0.0-rc02")
     docs("androidx.media3:media3-ui:1.0.0-rc02")
     docs("androidx.media3:media3-ui-leanback:1.0.0-rc02")
-    docs("androidx.mediarouter:mediarouter:1.6.0-alpha01")
-    docs("androidx.mediarouter:mediarouter-testing:1.6.0-alpha01")
+    docs("androidx.mediarouter:mediarouter:1.6.0-alpha02")
+    docs("androidx.mediarouter:mediarouter-testing:1.6.0-alpha02")
     docs("androidx.metrics:metrics-performance:1.0.0-alpha03")
-    docs("androidx.navigation:navigation-common:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-common-ktx:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-compose:2.6.0-alpha06")
-    samples("androidx.navigation:navigation-compose-samples:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-dynamic-features-fragment:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-dynamic-features-runtime:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-fragment:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-fragment-ktx:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-runtime:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-runtime-ktx:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-testing:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-ui:2.6.0-alpha06")
-    docs("androidx.navigation:navigation-ui-ktx:2.6.0-alpha06")
+    docs("androidx.navigation:navigation-common:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-common-ktx:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-compose:2.6.0-alpha07")
+    samples("androidx.navigation:navigation-compose-samples:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-dynamic-features-fragment:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-dynamic-features-runtime:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-fragment:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-fragment-ktx:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-runtime:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-runtime-ktx:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-testing:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-ui:2.6.0-alpha07")
+    docs("androidx.navigation:navigation-ui-ktx:2.6.0-alpha07")
     docs("androidx.paging:paging-common:3.2.0-alpha04")
     docs("androidx.paging:paging-common-ktx:3.2.0-alpha04")
     docs("androidx.paging:paging-compose:1.0.0-alpha18")
@@ -270,13 +271,16 @@
     docs("androidx.print:print:1.1.0-beta01")
     docs("androidx.privacysandbox.ads:ads-adservices:1.0.0-beta01")
     docs("androidx.privacysandbox.ads:ads-adservices-java:1.0.0-beta01")
-    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha02")
-    docs("androidx.privacysandbox.tools:tools-apigenerator:1.0.0-alpha02")
-    docs("androidx.privacysandbox.tools:tools-apipackager:1.0.0-alpha02")
-    docs("androidx.privacysandbox.tools:tools-core:1.0.0-alpha02")
-    docs("androidx.profileinstaller:profileinstaller:1.3.0-beta01")
+    docs("androidx.privacysandbox.tools:tools:1.0.0-alpha03")
+    docs("androidx.privacysandbox.tools:tools-apigenerator:1.0.0-alpha03")
+    docs("androidx.privacysandbox.tools:tools-apipackager:1.0.0-alpha03")
+    docs("androidx.privacysandbox.tools:tools-core:1.0.0-alpha03")
+    docs("androidx.privacysandbox.ui:ui-client:1.0.0-alpha01")
+    docs("androidx.privacysandbox.ui:ui-core:1.0.0-alpha01")
+    docs("androidx.privacysandbox.ui:ui-provider:1.0.0-alpha01")
+    docs("androidx.profileinstaller:profileinstaller:1.3.0-rc01")
     docs("androidx.recommendation:recommendation:1.0.0")
-    docs("androidx.recyclerview:recyclerview:1.3.0-rc01")
+    docs("androidx.recyclerview:recyclerview:1.3.0")
     docs("androidx.recyclerview:recyclerview-selection:2.0.0-alpha01")
     docs("androidx.remotecallback:remotecallback:1.0.0-alpha02")
     docs("androidx.resourceinspection:resourceinspection-annotation:1.0.1")
@@ -293,8 +297,8 @@
     docs("androidx.room:room-rxjava2:2.5.0")
     docs("androidx.room:room-rxjava3:2.5.0")
     docs("androidx.room:room-testing:2.5.0")
-    docs("androidx.savedstate:savedstate:1.2.0")
-    docs("androidx.savedstate:savedstate-ktx:1.2.0")
+    docs("androidx.savedstate:savedstate:1.2.1")
+    docs("androidx.savedstate:savedstate-ktx:1.2.1")
     docs("androidx.security:security-app-authenticator:1.0.0-alpha02")
     docs("androidx.security:security-app-authenticator-testing:1.0.0-alpha01")
     docs("androidx.security:security-crypto:1.1.0-alpha05")
@@ -332,10 +336,10 @@
     docs("androidx.test.services:storage:1.4.2")
     docs("androidx.test.uiautomator:uiautomator:2.3.0-alpha02")
     docs("androidx.textclassifier:textclassifier:1.0.0-alpha04")
-    docs("androidx.tracing:tracing:1.2.0-alpha02")
-    docs("androidx.tracing:tracing-ktx:1.2.0-alpha02")
-    docs("androidx.tracing:tracing-perfetto:1.0.0-alpha11")
-    docs("androidx.tracing:tracing-perfetto-common:1.0.0-alpha11")
+    docs("androidx.tracing:tracing:1.2.0-beta01")
+    docs("androidx.tracing:tracing-ktx:1.2.0-beta01")
+    docs("androidx.tracing:tracing-perfetto:1.0.0-alpha12")
+    docs("androidx.tracing:tracing-perfetto-common:1.0.0-alpha12")
     docs("androidx.transition:transition:1.4.1")
     docs("androidx.transition:transition-ktx:1.4.1")
     docs("androidx.tv:tv-foundation:1.0.0-alpha04")
@@ -348,24 +352,25 @@
     docs("androidx.versionedparcelable:versionedparcelable:1.1.1")
     docs("androidx.viewpager2:viewpager2:1.1.0-beta01")
     docs("androidx.viewpager:viewpager:1.1.0-alpha01")
-    docs("androidx.wear.compose:compose-foundation:1.2.0-alpha05")
-    samples("androidx.wear.compose:compose-foundation-samples:1.2.0-alpha05")
-    docs("androidx.wear.compose:compose-material:1.2.0-alpha05")
-    docs("androidx.wear.compose:compose-material-core:1.2.0-alpha05")
-    samples("androidx.wear.compose:compose-material-samples:1.2.0-alpha05")
+    docs("androidx.wear.compose:compose-foundation:1.2.0-alpha06")
+    samples("androidx.wear.compose:compose-foundation-samples:1.2.0-alpha06")
+    docs("androidx.wear.compose:compose-material:1.2.0-alpha06")
+    docs("androidx.wear.compose:compose-material-core:1.2.0-alpha06")
+    samples("androidx.wear.compose:compose-material-samples:1.2.0-alpha06")
     docs("androidx.wear.compose:compose-material3:1.0.0-alpha01")
-    samples("androidx.wear.compose:compose-material3-samples:1.2.0-alpha05")
-    docs("androidx.wear.compose:compose-navigation:1.2.0-alpha05")
-    samples("androidx.wear.compose:compose-navigation-samples:1.2.0-alpha05")
-    docs("androidx.wear.protolayout:protolayout:1.0.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-expression:1.0.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-proto:1.0.0-alpha04")
-    docs("androidx.wear.protolayout:protolayout-renderer:1.0.0-alpha04")
-    docs("androidx.wear.tiles:tiles:1.1.0")
-    docs("androidx.wear.tiles:tiles-material:1.1.0")
-    docs("androidx.wear.tiles:tiles-proto:1.1.0")
-    docs("androidx.wear.tiles:tiles-renderer:1.1.0")
-    docs("androidx.wear.tiles:tiles-testing:1.1.0")
+    samples("androidx.wear.compose:compose-material3-samples:1.2.0-alpha06")
+    docs("androidx.wear.compose:compose-navigation:1.2.0-alpha06")
+    samples("androidx.wear.compose:compose-navigation-samples:1.2.0-alpha06")
+    docs("androidx.wear.protolayout:protolayout:1.0.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-expression:1.0.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-material:1.0.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-proto:1.0.0-alpha05")
+    docs("androidx.wear.protolayout:protolayout-renderer:1.0.0-alpha05")
+    docs("androidx.wear.tiles:tiles:1.2.0-alpha01")
+    docs("androidx.wear.tiles:tiles-material:1.2.0-alpha01")
+    docs("androidx.wear.tiles:tiles-proto:1.2.0-alpha01")
+    docs("androidx.wear.tiles:tiles-renderer:1.2.0-alpha01")
+    docs("androidx.wear.tiles:tiles-testing:1.2.0-alpha01")
     docs("androidx.wear.watchface:watchface:1.2.0-alpha07")
     docs("androidx.wear.watchface:watchface-client:1.2.0-alpha07")
     docs("androidx.wear.watchface:watchface-client-guava:1.2.0-alpha07")
@@ -390,7 +395,7 @@
     docs("androidx.wear:wear-input:1.2.0-alpha02")
     samples("androidx.wear:wear-input-samples:1.2.0-alpha01")
     docs("androidx.wear:wear-input-testing:1.2.0-alpha02")
-    docs("androidx.webkit:webkit:1.7.0-alpha02")
+    docs("androidx.webkit:webkit:1.7.0-alpha03")
     docs("androidx.window.extensions.core:core:1.0.0-alpha01")
     docs("androidx.window:window:1.1.0-alpha06")
     stubs(fileTree(dir: "../window/stubs/", include: ["window-sidecar-release-0.1.0-alpha01.aar"]))
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 2b5013a..08d11c4 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -18,9 +18,11 @@
     docs(project(":ads:ads-identifier-testing"))
     kmpDocs(project(":annotation:annotation"))
     docs(project(":annotation:annotation-experimental"))
+    docs(project(":appactions:interaction:interaction-capabilities-communication"))
     docs(project(":appactions:interaction:interaction-capabilities-core"))
     docs(project(":appactions:interaction:interaction-capabilities-productivity"))
     docs(project(":appactions:interaction:interaction-capabilities-safety"))
+    docs(project(":appactions:interaction:interaction-capabilities-fitness"))
     docs(project(":appactions:interaction:interaction-proto"))
     docs(project(":appactions:interaction:interaction-service"))
     docs(project(":appactions:interaction:interaction-service-proto"))
diff --git a/docs/principles.md b/docs/principles.md
index a2d2a42..96adce4 100644
--- a/docs/principles.md
+++ b/docs/principles.md
@@ -110,17 +110,11 @@
 ### 13. Examples of modern development
 
 -   Where possible, targeting the latest languages, OS features, and tools. All
-    new libraries should be written in Kotlin first. Existing libraries
-    implemented in Java should add Kotlin extension libraries to improve the
-    interoperability of the Java APIs from Kotlin. New libraries written in Java
-    require a significant business reason on why a dependency in Kotlin cannot
-    be taken. The following is the order of preference, with each lower tier
-    requiring a business reason:
-    1.  Implemented in Kotlin that compiles to Java 8 bytecode
-    2.  Implemented in Java 8, with `-ktx` extensions for Kotlin
-        interoperability
-    3.  Implemented in Java 7, with `-ktx` extensions for Kotlin
-        interoperability
+    new libraries should be written in Kotlin, compile using the latest stable
+    Android SDK, and assume that clients are using the latest stable versions of
+    Android Studio, Gradle, and AGP. See the
+    [AndroidX API Guidelines](/company/teams/androidx/api_guidelines/index.md#dependencies-kotlin)
+    for more details, including the use of Java sources and `-ktx` artifacts.
 
 ### 14. High quality APIs and ownership
 
diff --git a/docs/testing.md b/docs/testing.md
index 656b760..3b0d40a 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -224,34 +224,49 @@
 
 ```shell
 # Run instrumentation tests on a connected device
-./gradlew <project-name>:connectedAndroidTest --info --daemon
+./gradlew <project-name>:connectedAndroidTest --info
+
+# Run instrumentation tests in Firebase Test Lab (remote)
+./gradlew <project-name>:ftlnexus4api21
+./gradlew <project-name>:ftlpixel2api26
+./gradlew <project-name>:ftlpixel2api28
+./gradlew <project-name>:ftlpixel2api30
+./gradlew <project-name>:ftlpixel2api33
 
 # Run local unit tests
-./gradlew <project-name>:test --info --daemon
+./gradlew <project-name>:test
 ```
 
-substituting the Gradle project name (ex. `core`).
+substituting the Gradle project name (ex. `:core:core`).
 
-To run all integration tests in the specific project and test class you're
-working on, run
+To run a specific instrumentation test in a given project, run
 
 ```shell
-./gradlew <project-name>:connectedAndroidTest --info --daemon \
+# Run instrumentation tests on a connected device
+./gradlew <project-name>:connectedAndroidTest --info \
     -Pandroid.testInstrumentationRunnerArguments.class=<fully-qualified-class>[\#testName]
+
+# Run instrumentation tests on in Firebase Test Lab (remote)
+./gradlew <project-name>:ftlpixel2api30 --className=<fully-qualified-class>
 ```
 
 substituting the Gradle project name (ex. `viewpager`) and fully-qualified class
 name (ex. `androidx.viewpager.widget.ViewPagerTest`) of your test file,
 optionally followed by `\#testName` if you want to execute a single test in that
-file. Substitute `test` for `connectedAndroidTest` to run local unit tests.
+file
 
-If you see some weird compilation errors such as below, run `./gradlew clean`
-first:
+If you want to run a specific unit test, you can do it using
+[`--tests` filtering](https://docs.gradle.org/current/userguide/java_testing.html#test_filtering)
+```shell
 
-```
-Unknown source file : UNEXPECTED TOP-LEVEL EXCEPTION:
-Unknown source file : com.android.dex.DexException: Multiple dex files define Landroid/content/pm/ParceledListSlice$1;
-```
+# Run a test for an Android library on a connected device
+
+./gradlew <project-name>:test --tests androidx.core.view.DisplayCompatTest
+
+# Run a test for a JVM library
+
+./gradlew <project-name>:testDebugUnitTest --tests
+androidx.core.view.DisplayCompatTest ```
 
 ## Test apps {#testapps}
 
diff --git a/drawerlayout/OWNERS b/drawerlayout/OWNERS
new file mode 100644
index 0000000..347e71c
--- /dev/null
+++ b/drawerlayout/OWNERS
@@ -0,0 +1,2 @@
+aelias@google.com
+ryanmentley@google.com
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
index 6e8521c..f286364 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-ar/strings.xml
@@ -24,7 +24,7 @@
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"المأكولات والمشروبات"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"السفر والأماكن"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"الأنشطة والأحداث"</string>
-    <string name="emoji_category_objects" msgid="6106115586332708067">"الأشياء"</string>
+    <string name="emoji_category_objects" msgid="6106115586332708067">"عناصر متنوعة"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"الرموز"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"الأعلام"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"لا تتوفر أي رموز تعبيرية."</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
index 75d8005..29770bc 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-bs/strings.xml
@@ -19,7 +19,7 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="emoji_category_recent" msgid="7142376595414250279">"NEDAVNO KORIŠTENO"</string>
     <string name="emoji_category_emotions" msgid="1570830970240985537">"SMAJLIJI I EMOCIJE"</string>
-    <string name="emoji_category_people" msgid="7968173366822927025">"LJUDI"</string>
+    <string name="emoji_category_people" msgid="7968173366822927025">"OSOBE"</string>
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ŽIVOTINJE I PRIRODA"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"HRANA I PIĆE"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"PUTOVANJA I MJESTA"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
index 7dcb54b..3bb43cb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-de/strings.xml
@@ -23,7 +23,7 @@
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"TIERE UND NATUR"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"ESSEN UND TRINKEN"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"REISEN UND ORTE"</string>
-    <string name="emoji_category_activity" msgid="4381135114947330911">"AKTIVITÄTEN UND VERANSTALTUNGEN"</string>
+    <string name="emoji_category_activity" msgid="4381135114947330911">"AKTIVITÄTEN UND EVENTS"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"OBJEKTE"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"SYMBOLE"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FLAGGEN"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
index 5fd9943..fe48f4d 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-fa/strings.xml
@@ -25,7 +25,7 @@
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"سفر و مکان‌ها"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"فعالیت‌ها و رویدادها"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"اشیاء"</string>
-    <string name="emoji_category_symbols" msgid="5626171724310261787">"نمادها"</string>
+    <string name="emoji_category_symbols" msgid="5626171724310261787">"نشان‌ها"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"پرچم‌ها"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"اموجی دردسترس نیست"</string>
     <string name="emoji_empty_recent_category" msgid="7863877827879290200">"هنوز از هیچ اموجی‌ای استفاده نکرده‌اید"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
index ea4e6a93..c13a86c 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-is/strings.xml
@@ -23,7 +23,7 @@
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"DÝR OG NÁTTÚRA"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"MATUR OG DRYKKUR"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"FERÐALÖG OG STAÐIR"</string>
-    <string name="emoji_category_activity" msgid="4381135114947330911">"AÐGERÐIR OG VIÐBURÐIR"</string>
+    <string name="emoji_category_activity" msgid="4381135114947330911">"VIRKNI OG VIÐBURÐIR"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"HLUTIR"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"TÁKN"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"FÁNAR"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
index c030fdc..7537ddb 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-kk/strings.xml
@@ -21,7 +21,7 @@
     <string name="emoji_category_emotions" msgid="1570830970240985537">"СМАЙЛДАР МЕН ЭМОЦИЯЛАР"</string>
     <string name="emoji_category_people" msgid="7968173366822927025">"АДАМДАР"</string>
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ЖАНУАРЛАР ЖӘНЕ ТАБИҒАТ"</string>
-    <string name="emoji_category_food_drink" msgid="1189971856721244395">"АЗЫҚ-ТҮЛІК ЖӘНЕ СУСЫНДАР"</string>
+    <string name="emoji_category_food_drink" msgid="1189971856721244395">"ТАМАҚ ПЕН СУСЫН"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"САЯХАТ ЖӘНЕ ОРЫНДАР"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"ӘРЕКЕТТЕР МЕН ІС-ШАРАЛАР"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"НЫСАНДАР"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
index 6010b78..a8a57f4 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-km/strings.xml
@@ -26,7 +26,7 @@
     <string name="emoji_category_activity" msgid="4381135114947330911">"សកម្មភាព និងព្រឹត្តិការណ៍"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"វត្ថុ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"និមិត្តសញ្ញា"</string>
-    <string name="emoji_category_flags" msgid="6185639503532784871">"ទង់ជាតិ"</string>
+    <string name="emoji_category_flags" msgid="6185639503532784871">"ទង់"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"មិនមាន​រូប​អារម្មណ៍ទេ"</string>
     <string name="emoji_empty_recent_category" msgid="7863877827879290200">"អ្នក​មិនទាន់​បានប្រើរូប​អារម្មណ៍​ណាមួយ​នៅឡើយទេ"</string>
 </resources>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
index 9dc70b6..5216964 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-mk/strings.xml
@@ -18,7 +18,7 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <string name="emoji_category_recent" msgid="7142376595414250279">"НЕОДАМНА КОРИСТЕНИ"</string>
-    <string name="emoji_category_emotions" msgid="1570830970240985537">"СМЕШКОВЦИ И ЕМОТИКОНИ"</string>
+    <string name="emoji_category_emotions" msgid="1570830970240985537">"СМЕШКОВЦИ И ЕМОЦИИ"</string>
     <string name="emoji_category_people" msgid="7968173366822927025">"ЛУЃЕ"</string>
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ЖИВОТНИ И ПРИРОДА"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"ХРАНА И ПИЈАЛАЦИ"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
index adb4671..bd5436b 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-sw/strings.xml
@@ -25,7 +25,7 @@
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"SAFARI NA MAENEO"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"SHUGHULI NA MATUKIO"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"VITU"</string>
-    <string name="emoji_category_symbols" msgid="5626171724310261787">"ISHARA"</string>
+    <string name="emoji_category_symbols" msgid="5626171724310261787">"ALAMA"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BENDERA"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hakuna emoji zinazopatikana"</string>
     <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Bado hujatumia emoji zozote"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
index 87651b5..9118893 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-th/strings.xml
@@ -23,7 +23,7 @@
     <string name="emoji_category_animals_nature" msgid="4640771324837307541">"สัตว์และธรรมชาติ"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"อาหารและเครื่องดื่ม"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"การเดินทางและสถานที่"</string>
-    <string name="emoji_category_activity" msgid="4381135114947330911">"กิจกรรมและเหตุการณ์ต่างๆ"</string>
+    <string name="emoji_category_activity" msgid="4381135114947330911">"กิจกรรมและเหตุการณ์"</string>
     <string name="emoji_category_objects" msgid="6106115586332708067">"วัตถุ"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"สัญลักษณ์"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"ธง"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
index da09fcd..e346eb9 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uk/strings.xml
@@ -20,7 +20,7 @@
     <string name="emoji_category_recent" msgid="7142376595414250279">"НЕЩОДАВНО ВИКОРИСТАНІ"</string>
     <string name="emoji_category_emotions" msgid="1570830970240985537">"СМАЙЛИКИ Й ЕМОЦІЇ"</string>
     <string name="emoji_category_people" msgid="7968173366822927025">"ЛЮДИ"</string>
-    <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ТВАРИНИ ТА ПРИРОДА"</string>
+    <string name="emoji_category_animals_nature" msgid="4640771324837307541">"ТВАРИНИ Й ПРИРОДА"</string>
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"ЇЖА Й НАПОЇ"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"ПОДОРОЖІ Й МІСЦЯ"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"АКТИВНІСТЬ І ПОДІЇ"</string>
diff --git a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
index 7241558..ab3f343 100644
--- a/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
+++ b/emoji2/emoji2-emojipicker/src/main/res/values-uz/strings.xml
@@ -24,9 +24,9 @@
     <string name="emoji_category_food_drink" msgid="1189971856721244395">"TAOM VA ICHIMLIKLAR"</string>
     <string name="emoji_category_travel_places" msgid="8105712773237012672">"SAYOHAT VA JOYLAR"</string>
     <string name="emoji_category_activity" msgid="4381135114947330911">"HODISA VA TADBIRLAR"</string>
-    <string name="emoji_category_objects" msgid="6106115586332708067">"OBYEKTLAR"</string>
+    <string name="emoji_category_objects" msgid="6106115586332708067">"BUYUMLAR"</string>
     <string name="emoji_category_symbols" msgid="5626171724310261787">"BELGILAR"</string>
     <string name="emoji_category_flags" msgid="6185639503532784871">"BAYROQCHALAR"</string>
     <string name="emoji_empty_non_recent_category" msgid="288822832574892625">"Hech qanday emoji mavjud emas"</string>
-    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Haligacha birorta emojidan foydalanmagansiz"</string>
+    <string name="emoji_empty_recent_category" msgid="7863877827879290200">"Hanuz birorta emoji ishlatmagansiz"</string>
 </resources>
diff --git a/fragment/fragment-ktx/build.gradle b/fragment/fragment-ktx/build.gradle
index b222645..3eb971a9 100644
--- a/fragment/fragment-ktx/build.gradle
+++ b/fragment/fragment-ktx/build.gradle
@@ -34,11 +34,11 @@
     api("androidx.collection:collection-ktx:1.1.0") {
         because "Mirror fragment dependency graph for -ktx artifacts"
     }
-    api("androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.0") {
+    api("androidx.lifecycle:lifecycle-livedata-core-ktx:2.6.1") {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0")
-    api("androidx.savedstate:savedstate-ktx:1.2.0") {
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+    api("androidx.savedstate:savedstate-ktx:1.2.1") {
         because 'Mirror fragment dependency graph for -ktx artifacts'
     }
     api(libs.kotlinStdlib)
diff --git a/fragment/fragment/build.gradle b/fragment/fragment/build.gradle
index d24eaab..614d3fb 100644
--- a/fragment/fragment/build.gradle
+++ b/fragment/fragment/build.gradle
@@ -30,12 +30,12 @@
     api("androidx.viewpager:viewpager:1.0.0")
     api("androidx.loader:loader:1.0.0")
     api("androidx.activity:activity:1.5.1")
-    api("androidx.lifecycle:lifecycle-runtime:2.6.0")
-    api("androidx.lifecycle:lifecycle-livedata-core:2.6.0")
-    api("androidx.lifecycle:lifecycle-viewmodel:2.6.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.0")
+    api("androidx.lifecycle:lifecycle-runtime:2.6.1")
+    api("androidx.lifecycle:lifecycle-livedata-core:2.6.1")
+    api("androidx.lifecycle:lifecycle-viewmodel:2.6.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     implementation("androidx.profileinstaller:profileinstaller:1.2.1")
-    api("androidx.savedstate:savedstate:1.2.0")
+    api("androidx.savedstate:savedstate:1.2.1")
     api("androidx.annotation:annotation-experimental:1.0.0")
     api(libs.kotlinStdlib)
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
index 5dfc13d..3e1efd5 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
@@ -38,12 +38,8 @@
 import androidx.testutils.withUse
 import com.google.common.truth.Truth.assertThat
 import com.google.common.truth.Truth.assertWithMessage
-import leakcanary.DetectLeaksAfterTestSuccess
-import leakcanary.SkipLeakDetection
-import leakcanary.TestDescriptionHolder
 import org.junit.Rule
 import org.junit.Test
-import org.junit.rules.RuleChain
 import org.junit.runner.RunWith
 
 @LargeTest
@@ -51,16 +47,17 @@
 class DialogFragmentTest {
 
     @Suppress("DEPRECATION")
+    @get:Rule
     val activityTestRule =
         androidx.test.rule.ActivityTestRule(EmptyFragmentTestActivity::class.java)
 
+    // TODO(b/270722758): Add back in leak detection rule chain once leak addressed by platform
     // Detect leaks BEFORE and AFTER activity is destroyed
+    /*
     @get:Rule
     val ruleChain: RuleChain = RuleChain.outerRule(DetectLeaksAfterTestSuccess())
         .around(activityTestRule)
-
-    @get:Rule
-    val testDescriptionHolderRule = TestDescriptionHolder
+     */
 
     @Test
     fun testDialogFragmentShows() {
@@ -100,8 +97,6 @@
             .isTrue()
     }
 
-    // TODO(b/270722758): remove annotation once issue addressed by LeakCanary/platform
-    @SkipLeakDetection("Skip leak detection until platform ViewRootImpl leak addressed")
     @Test
     fun testDialogFragmentDismiss() {
         val fragment = TestDialogFragment()
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
index dd7a197..e312fa9 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentAnimationTest.kt
@@ -32,10 +32,12 @@
 import androidx.fragment.test.R
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.ViewModelStore
+import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.waitForExecution
+import androidx.testutils.withActivity
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Rule
@@ -682,38 +684,44 @@
 
     @Test
     fun animationListenersAreCalled() {
-        waitForAnimationReady()
-        val fm = activityRule.activity.supportFragmentManager
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity {
+                setContentView(R.layout.simple_container)
+                supportFragmentManager
+            }
 
-        // Add first fragment
-        val fragment1 = AnimationListenerFragment()
-        fragment1.forceRunOnHwLayer = false
-        fragment1.repeat = true
-        fm.beginTransaction()
-            .add(R.id.fragmentContainer, fragment1)
-            .commit()
-        activityRule.waitForExecution()
+            // Add first fragment
+            val fragment1 = AnimationListenerFragment()
+            fragment1.forceRunOnHwLayer = false
+            fragment1.repeat = true
+            withActivity {
+                fm.beginTransaction()
+                    .add(R.id.fragmentContainer, fragment1)
+                    .commit()
+                fm.executePendingTransactions()
+            }
 
-        // Replace first fragment with second fragment with a fade in/out animation
-        val fragment2 = AnimationListenerFragment()
-        fragment2.forceRunOnHwLayer = true
-        fragment2.repeat = false
-        fm.beginTransaction()
-            .setCustomAnimations(
-                android.R.anim.fade_in, android.R.anim.fade_out,
-                android.R.anim.fade_in, android.R.anim.fade_out
-            )
-            .replace(R.id.fragmentContainer, fragment2)
-            .addToBackStack(null)
-            .commit()
-        activityRule.waitForExecution()
+            // Replace first fragment with second fragment with a fade in/out animation
+            val fragment2 = AnimationListenerFragment()
+            fragment2.forceRunOnHwLayer = true
+            fragment2.repeat = false
+            withActivity {
+                fm.beginTransaction()
+                    .setCustomAnimations(
+                        android.R.anim.fade_in, android.R.anim.fade_out,
+                        android.R.anim.fade_in, android.R.anim.fade_out
+                    )
+                    .replace(R.id.fragmentContainer, fragment2)
+                    .addToBackStack(null)
+                    .commit()
+                fm.executePendingTransactions()
+            }
 
-        // Wait for animation to finish
-        assertThat(fragment1.exitLatch.await(2, TimeUnit.SECONDS)).isTrue()
-        assertThat(fragment2.enterLatch.await(2, TimeUnit.SECONDS)).isTrue()
+            // Wait for animation to finish
+            assertThat(fragment1.exitLatch.await(2, TimeUnit.SECONDS)).isTrue()
+            assertThat(fragment2.enterLatch.await(2, TimeUnit.SECONDS)).isTrue()
 
-        // Check if all animation listener callbacks have been called
-        activityRule.runOnUiThread {
+            // Check if all animation listener callbacks have been called
             assertThat(fragment1.exitStartCount).isEqualTo(1)
             assertThat(fragment1.exitRepeatCount).isEqualTo(1)
             assertThat(fragment1.exitEndCount).isEqualTo(1)
@@ -729,17 +737,16 @@
             assertThat(fragment2.exitStartCount).isEqualTo(0)
             assertThat(fragment2.exitRepeatCount).isEqualTo(0)
             assertThat(fragment2.exitEndCount).isEqualTo(0)
-        }
-        fragment1.resetCounts()
-        fragment2.resetCounts()
 
-        // Now pop the transaction
-        activityRule.popBackStackImmediate()
+            fragment1.resetCounts()
+            fragment2.resetCounts()
 
-        assertThat(fragment2.exitLatch.await(2, TimeUnit.SECONDS)).isTrue()
-        assertThat(fragment1.enterLatch.await(2, TimeUnit.SECONDS)).isTrue()
+            // Now pop the transaction
+            withActivity { fm.popBackStackImmediate() }
 
-        activityRule.runOnUiThread {
+            assertThat(fragment2.exitLatch.await(2, TimeUnit.SECONDS)).isTrue()
+            assertThat(fragment1.enterLatch.await(2, TimeUnit.SECONDS)).isTrue()
+
             assertThat(fragment2.exitStartCount).isEqualTo(1)
             assertThat(fragment2.exitRepeatCount).isEqualTo(0)
             assertThat(fragment2.exitEndCount).isEqualTo(1)
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
index fa117a9..86215ef 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/AppWidgetSessionTest.kt
@@ -44,6 +44,7 @@
 import kotlinx.coroutines.test.runTest
 import org.junit.Assert.assertThrows
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
@@ -80,6 +81,7 @@
         assertThat(widget.provideGlanceCalled.get()).isTrue()
     }
 
+    @Ignore // b/266518169
     @Test
     fun provideGlanceEmitsIgnoreResultForNullContent() = runTest {
         // The session starts out with null content, so we can check that here.
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
index 578fd4e..42bdefa 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/GlobalSnapshotManager.kt
@@ -51,4 +51,21 @@
             }
         }
     }
-}
\ No newline at end of file
+}
+
+/**
+ * Monitors global snapshot state writes and sends apply notifications.
+ */
+internal suspend fun globalSnapshotMonitor() {
+    val channel = Channel<Unit>(Channel.CONFLATED)
+    val observerHandle = Snapshot.registerGlobalWriteObserver {
+        channel.trySend(Unit)
+    }
+    try {
+        channel.consumeEach {
+            Snapshot.sendApplyNotifications()
+        }
+    } finally {
+        observerHandle.dispose()
+    }
+}
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
index c2369aa..f580890 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
@@ -26,6 +26,7 @@
 import androidx.work.CoroutineWorker
 import androidx.work.WorkerParameters
 import kotlin.time.Duration.Companion.seconds
+import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.cancel
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.first
@@ -54,17 +55,18 @@
         internal val defaultTimeout = 45.seconds
     }
 
-    override suspend fun doWork(): Result = withTimeoutOrNull(defaultTimeout) {
-        val frameClock = InteractiveFrameClock(this)
-        val key =
-            inputData.getString(sessionManager.keyParam)
-                ?: return@withTimeoutOrNull Result.failure()
-        val session = requireNotNull(sessionManager.getSession(key)) {
-            "No session available to key $key"
-        }
+    private val key = inputData.getString(sessionManager.keyParam)
+            ?: error("SessionWorker must be started with a key")
 
+    @Deprecated("Deprecated by super class, replacement in progress, see b/245353737")
+    @OptIn(ExperimentalStdlibApi::class)
+    override val coroutineContext = Dispatchers.Main
+
+    override suspend fun doWork(): Result = withTimeoutOrNull(defaultTimeout) {
+        val session = sessionManager.getSession(key) ?: error("No session available for key $key")
         if (DEBUG) Log.d(TAG, "Setting up composition for ${session.key}")
-        GlobalSnapshotManager.ensureStarted()
+        val frameClock = InteractiveFrameClock(this)
+        val snapshotMonitor = launch { globalSnapshotMonitor() }
         val root = session.createRootEmittable()
         val recomposer = Recomposer(coroutineContext)
         val composition = Composition(Applier(root), recomposer).apply {
@@ -109,6 +111,7 @@
 
         composition.dispose()
         frameClock.stopInteractive()
+        snapshotMonitor.cancel()
         recomposer.close()
         recomposer.join()
         return@withTimeoutOrNull Result.success()
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/MultiBufferedCanvasRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/MultiBufferedCanvasRendererTest.kt
new file mode 100644
index 0000000..85138d1
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/MultiBufferedCanvasRendererTest.kt
@@ -0,0 +1,141 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics
+
+import android.graphics.Bitmap
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.graphics.RenderNode
+import android.os.Build
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class MultiBufferedCanvasRendererTest {
+
+    companion object {
+        const val TEST_WIDTH = 20
+        const val TEST_HEIGHT = 20
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameInvokesCallback() {
+        val renderNode = RenderNode("node").apply {
+            setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
+            val canvas = beginRecording()
+            canvas.drawColor(Color.RED)
+            endRecording()
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        val renderer = MultiBufferedCanvasRenderer(renderNode, TEST_WIDTH, TEST_HEIGHT)
+        try {
+            val renderLatch = CountDownLatch(1)
+            renderer.renderFrame(executor) {
+                renderLatch.countDown()
+            }
+            assertTrue(renderLatch.await(1000, TimeUnit.MILLISECONDS))
+        } finally {
+            renderer.release()
+            executor.shutdownNow()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderAfterReleaseDoesNotRender() {
+        val renderNode = RenderNode("node").apply {
+            setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
+            val canvas = beginRecording()
+            canvas.drawColor(Color.RED)
+            endRecording()
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        val renderer = MultiBufferedCanvasRenderer(renderNode, TEST_WIDTH, TEST_HEIGHT)
+        try {
+            val renderLatch = CountDownLatch(1)
+            renderer.release()
+            renderer.renderFrame(executor) {
+                renderLatch.countDown()
+            }
+            assertFalse(renderLatch.await(1000, TimeUnit.MILLISECONDS))
+        } finally {
+            executor.shutdownNow()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testMultiReleasesDoesNotCrash() {
+        val renderNode = RenderNode("node").apply {
+            setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
+            val canvas = beginRecording()
+            canvas.drawColor(Color.RED)
+            endRecording()
+        }
+        val renderer = MultiBufferedCanvasRenderer(renderNode, TEST_WIDTH, TEST_HEIGHT)
+        renderer.release()
+        renderer.release()
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderOutput() {
+        val renderNode = RenderNode("node").apply {
+            setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
+            val canvas = beginRecording()
+            drawSquares(
+                canvas,
+                TEST_WIDTH,
+                TEST_HEIGHT,
+                Color.RED,
+                Color.YELLOW,
+                Color.GREEN,
+                Color.BLUE
+            )
+            endRecording()
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        val renderer = MultiBufferedCanvasRenderer(renderNode, TEST_WIDTH, TEST_HEIGHT)
+        try {
+            val renderLatch = CountDownLatch(1)
+            var bitmap: Bitmap? = null
+            renderer.renderFrame(executor) { buffer ->
+                val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+                bitmap = Bitmap.wrapHardwareBuffer(buffer, colorSpace)
+                    ?.copy(Bitmap.Config.ARGB_8888, false)
+                renderLatch.countDown()
+            }
+            assertTrue(renderLatch.await(1000, TimeUnit.MILLISECONDS))
+            assertNotNull(bitmap)
+            bitmap!!.verifyQuadrants(Color.RED, Color.YELLOW, Color.GREEN, Color.BLUE)
+        } finally {
+            renderer.release()
+            executor.shutdownNow()
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/SurfaceTextureRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/SurfaceTextureRendererTest.kt
new file mode 100644
index 0000000..aae6cde
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/SurfaceTextureRendererTest.kt
@@ -0,0 +1,127 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics
+
+import android.graphics.Color
+import android.graphics.RenderNode
+import android.graphics.SurfaceTexture
+import android.os.Build
+import android.os.Handler
+import android.os.HandlerThread
+import android.os.Looper
+import androidx.annotation.RequiresApi
+import androidx.graphics.SurfaceTextureRendererTest.TestHelpers.Companion.createSurfaceTextureRenderer
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SurfaceTextureRendererTest {
+
+    companion object {
+        const val TEST_WIDTH = 20
+        const val TEST_HEIGHT = 20
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameInvokesCallback() {
+        withHandlerThread { handler ->
+            val renderLatch = CountDownLatch(1)
+            val renderer = createSurfaceTextureRenderer(handler = handler) {
+                renderLatch.countDown()
+            }
+            renderer.renderFrame()
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            renderer.release()
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderAfterReleaseDoesNotRender() {
+        withHandlerThread { handler ->
+            val renderNode = RenderNode("renderNode")
+            val renderLatch = CountDownLatch(1)
+            val renderer = SurfaceTextureRenderer(renderNode, 100, 100, handler) {
+                renderLatch.countDown()
+            }
+            renderer.release()
+            renderer.renderFrame()
+            assertFalse(renderLatch.await(1000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testMultiReleasesDoesNotCrash() {
+        withHandlerThread { handler ->
+            val renderer = createSurfaceTextureRenderer(handler = handler) {
+                // NO-OP
+            }
+            renderer.release()
+            renderer.release()
+        }
+    }
+
+    /**
+     * Static inner class to enclose helper methods that rely on APIs that were introduced in newer
+     * versions of Android. Even though the individual tests are gated with SdkSuppress annotations,
+     * the test runners reflectively iterate through every function of a test class. As a result if
+     * a function refers to new APIs in the method signatures they can crash. So move these methods
+     * into a separate class that will only be inspected upon first use with an entry point of the
+     * test methods defined above, on the appropriate API level.
+     */
+    internal class TestHelpers {
+        companion object {
+            @RequiresApi(Build.VERSION_CODES.Q)
+            fun createTestRenderNode(): RenderNode =
+                RenderNode("node").apply {
+                    setPosition(0, 0, TEST_WIDTH, TEST_HEIGHT)
+                    val canvas = beginRecording()
+                    canvas.drawColor(Color.RED)
+                    endRecording()
+                }
+
+            @RequiresApi(Build.VERSION_CODES.Q)
+            fun createSurfaceTextureRenderer(
+                renderNode: RenderNode = createTestRenderNode(),
+                width: Int = TEST_WIDTH,
+                height: Int = TEST_WIDTH,
+                handler: Handler = Handler(Looper.getMainLooper()),
+                block: (SurfaceTexture) -> Unit = {}
+            ) = SurfaceTextureRenderer(renderNode, width, height, handler, block)
+        }
+    }
+
+    private fun withHandlerThread(block: (Handler) -> Unit) {
+        val handlerThread = HandlerThread("rendererCallbackThread").apply { start() }
+        val handler = Handler(handlerThread.looper)
+        try {
+            block(handler)
+        } finally {
+            handlerThread.quit()
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/TestUtils.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/TestUtils.kt
new file mode 100644
index 0000000..7958b18
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/TestUtils.kt
@@ -0,0 +1,122 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Paint
+import android.opengl.EGL14
+import androidx.graphics.opengl.egl.EGLConfigAttributes
+import androidx.graphics.opengl.egl.EGLManager
+import androidx.graphics.opengl.egl.EGLSpec
+import androidx.graphics.opengl.egl.EGLVersion
+import org.junit.Assert
+
+fun drawSquares(
+    canvas: Canvas,
+    width: Int,
+    height: Int,
+    topLeft: Int,
+    topRight: Int,
+    bottomLeft: Int,
+    bottomRight: Int
+) {
+    val paint = Paint()
+    val widthF = width.toFloat()
+    val heightF = height.toFloat()
+    val halfWidth = widthF / 2f
+    val halfHeight = heightF / 2f
+    canvas.drawRect(0f, 0f, halfWidth, halfHeight,
+        paint.apply { color = topLeft })
+    canvas.drawRect(halfWidth, 0f, widthF, halfHeight,
+        paint.apply { color = topRight })
+    canvas.drawRect(0f, halfHeight, halfWidth, heightF,
+        paint.apply { color = bottomLeft })
+    canvas.drawRect(halfWidth, halfHeight, widthF, heightF,
+        paint.apply { color = bottomRight })
+}
+
+fun Bitmap.verifyQuadrants(
+    topLeft: Int,
+    topRight: Int,
+    bottomLeft: Int,
+    bottomRight: Int
+) {
+    Assert.assertEquals(topLeft, getPixel(1, 1))
+    Assert.assertEquals(topLeft, getPixel(width / 2 - 2, 1))
+    Assert.assertEquals(topLeft, getPixel(width / 2 - 2, height / 2 - 2))
+    Assert.assertEquals(topLeft, getPixel(1, height / 2 - 2))
+
+    Assert.assertEquals(topRight, getPixel(width / 2 + 2, 1))
+    Assert.assertEquals(topRight, getPixel(width - 2, 1))
+    Assert.assertEquals(topRight, getPixel(width - 2, height / 2 - 2))
+    Assert.assertEquals(topRight, getPixel(width / 2 + 2, height / 2 - 2))
+
+    Assert.assertEquals(bottomLeft, getPixel(1, height / 2 + 2))
+    Assert.assertEquals(bottomLeft, getPixel(width / 2 - 2, height / 2 + 2))
+    Assert.assertEquals(bottomLeft, getPixel(width / 2 - 2, height - 2))
+    Assert.assertEquals(bottomLeft, getPixel(1, height - 2))
+
+    Assert.assertEquals(bottomRight, getPixel(width / 2 + 2, height / 2 + 2))
+    Assert.assertEquals(bottomRight, getPixel(width - 2, height / 2 + 2))
+    Assert.assertEquals(bottomRight, getPixel(width - 2, height - 2))
+    Assert.assertEquals(bottomRight, getPixel(width / 2 + 2, height - 2))
+}
+
+fun Bitmap.isAllColor(targetColor: Int): Boolean {
+    for (i in 0 until width) {
+        for (j in 0 until height) {
+            if (getPixel(i, j) != targetColor) {
+                return false
+            }
+        }
+    }
+    return true
+}
+
+fun withEgl(block: (egl: EGLManager) -> Unit) {
+    val egl = createAndSetupEGLManager(EGLSpec.V14)
+    try {
+        block(egl)
+    } finally {
+        releaseEGLManager(egl)
+    }
+}
+
+// Helper method to create and initialize an EGLManager
+private fun createAndSetupEGLManager(eglSpec: EGLSpec = EGLSpec.V14): EGLManager {
+    val egl = EGLManager(eglSpec)
+    Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
+    Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
+
+    egl.initialize()
+
+    val config = egl.loadConfig(EGLConfigAttributes.RGBA_8888)
+    if (config == null) {
+        Assert.fail("Config 888 should be supported")
+    }
+
+    egl.createContext(config!!)
+    return egl
+}
+
+// Helper method to release EGLManager
+private fun releaseEGLManager(egl: EGLManager) {
+    egl.release()
+    Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
+    Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
index 16ac246..b53ff80 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/GLFrontBufferedRendererTest.kt
@@ -749,7 +749,7 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     fun testUsageFlagContainsFrontBufferUsage() {
-        val usageFlags = GLFrontBufferedRenderer.obtainHardwareBufferUsageFlags()
+        val usageFlags = FrontBufferUtils.obtainHardwareBufferUsageFlags()
         if (UsageFlagsVerificationHelper.isSupported(HardwareBuffer.USAGE_FRONT_BUFFER)) {
             assertNotEquals(0, usageFlags and HardwareBuffer.USAGE_FRONT_BUFFER)
         } else {
@@ -760,7 +760,7 @@
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.TIRAMISU)
     fun testUsageFlagContainsComposerOverlay() {
-        val usageFlags = GLFrontBufferedRenderer.obtainHardwareBufferUsageFlags()
+        val usageFlags = FrontBufferUtils.obtainHardwareBufferUsageFlags()
         if (UsageFlagsVerificationHelper.isSupported(HardwareBuffer.USAGE_COMPOSER_OVERLAY)) {
             assertNotEquals(
                 0,
@@ -775,11 +775,11 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testBaseFlags() {
         assertNotEquals(
-            0, GLFrontBufferedRenderer.BaseFlags and
+            0, FrontBufferUtils.BaseFlags and
                 HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
         )
         assertNotEquals(
-            0, GLFrontBufferedRenderer.BaseFlags and
+            0, FrontBufferUtils.BaseFlags and
                 HardwareBuffer.USAGE_GPU_COLOR_OUTPUT
         )
     }
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt
new file mode 100644
index 0000000..c0d0a6a
--- /dev/null
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29Test.kt
@@ -0,0 +1,418 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.graphics.lowlatency
+
+import android.graphics.Bitmap
+import android.graphics.Canvas
+import android.graphics.Color
+import android.graphics.ColorSpace
+import android.hardware.HardwareBuffer
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.graphics.drawSquares
+import androidx.graphics.isAllColor
+import androidx.graphics.opengl.egl.supportsNativeAndroidFence
+import androidx.graphics.surface.SurfaceControlCompat
+import androidx.graphics.surface.SurfaceControlCompat.Companion.BUFFER_TRANSFORM_IDENTITY
+import androidx.graphics.verifyQuadrants
+import androidx.graphics.withEgl
+import androidx.hardware.SyncFenceCompat
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertNotNull
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class SingleBufferedCanvasRendererV29Test {
+
+    companion object {
+        const val TEST_WIDTH = 20
+        const val TEST_HEIGHT = 20
+    }
+
+    data class RectColors(
+        val topLeft: Int,
+        val topRight: Int,
+        val bottomLeft: Int,
+        val bottomRight: Int
+    )
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameRotate0() {
+        testRenderWithTransform(
+            BUFFER_TRANSFORM_IDENTITY,
+            RectColors(
+                topLeft = Color.RED,
+                topRight = Color.YELLOW,
+                bottomRight = Color.BLUE,
+                bottomLeft = Color.GREEN
+            ),
+            RectColors(
+                topLeft = Color.RED,
+                topRight = Color.YELLOW,
+                bottomRight = Color.BLUE,
+                bottomLeft = Color.GREEN
+            )
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameRotate90() {
+        testRenderWithTransform(
+            SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_90,
+            RectColors(
+                topLeft = Color.RED,
+                topRight = Color.YELLOW,
+                bottomRight = Color.BLUE,
+                bottomLeft = Color.GREEN
+            ),
+            RectColors(
+                topLeft = Color.YELLOW,
+                topRight = Color.BLUE,
+                bottomRight = Color.GREEN,
+                bottomLeft = Color.RED
+            )
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameRotate180() {
+        testRenderWithTransform(
+            SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_180,
+            RectColors(
+                topLeft = Color.RED,
+                topRight = Color.YELLOW,
+                bottomRight = Color.BLUE,
+                bottomLeft = Color.GREEN
+            ),
+            RectColors(
+                topLeft = Color.BLUE,
+                topRight = Color.GREEN,
+                bottomRight = Color.RED,
+                bottomLeft = Color.YELLOW
+            )
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRenderFrameRotate270() {
+        testRenderWithTransform(
+            SurfaceControlCompat.BUFFER_TRANSFORM_ROTATE_270,
+            RectColors(
+                topLeft = Color.RED,
+                topRight = Color.YELLOW,
+                bottomRight = Color.BLUE,
+                bottomLeft = Color.GREEN
+            ),
+            RectColors(
+                topLeft = Color.GREEN,
+                topRight = Color.RED,
+                bottomRight = Color.YELLOW,
+                bottomLeft = Color.BLUE
+            )
+        )
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testClearRenderer() {
+        val transformer = BufferTransformer().apply {
+            computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        val firstRenderLatch = CountDownLatch(1)
+        val clearLatch = CountDownLatch(2)
+        var buffer: HardwareBuffer? = null
+        val renderer = SingleBufferedCanvasRendererV29(
+            TEST_WIDTH,
+            TEST_HEIGHT,
+            transformer,
+            executor,
+            object : SingleBufferedCanvasRenderer.RenderCallbacks<Unit> {
+                override fun render(canvas: Canvas, width: Int, height: Int, param: Unit) {
+                    canvas.drawColor(Color.RED)
+                }
+
+                override fun onBufferReady(
+                    hardwareBuffer: HardwareBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    syncFenceCompat?.awaitForever()
+                    buffer = hardwareBuffer
+                    firstRenderLatch.countDown()
+                    clearLatch.countDown()
+                }
+            }).apply {
+                // See: b/236394768 Workaround for ANGLE issue where FBOs with HardwareBuffer
+                // attachments are not executed until a glReadPixels call is made
+                forceFlush.set(true)
+            }
+        try {
+            renderer.render(Unit)
+            firstRenderLatch.await(3000, TimeUnit.MILLISECONDS)
+            renderer.clear()
+            assertTrue(clearLatch.await(3000, TimeUnit.MILLISECONDS))
+            assertNotNull(buffer)
+            val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+            val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
+                ?.copy(Bitmap.Config.ARGB_8888, false)
+            assertNotNull(bitmap)
+            assertTrue(bitmap!!.isAllColor(Color.TRANSPARENT))
+        } finally {
+            val latch = CountDownLatch(1)
+            renderer.release(true) {
+                executor.shutdownNow()
+                latch.countDown()
+            }
+            assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testCancelPending() {
+        val transformer = BufferTransformer().apply {
+            computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        var buffer: HardwareBuffer? = null
+        val initialDrawLatch = CountDownLatch(1)
+        val bufferReadyLatch = CountDownLatch(1)
+        val waitForRequestLatch = CountDownLatch(1)
+
+        var drawCancelledRequestLatch: CountDownLatch? = null
+        val renderer = SingleBufferedCanvasRendererV29(
+            TEST_WIDTH,
+            TEST_HEIGHT,
+            transformer,
+            executor,
+            object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
+                override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
+                    canvas.drawColor(param)
+                    initialDrawLatch.countDown()
+                }
+
+                override fun onBufferReady(
+                    hardwareBuffer: HardwareBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    syncFenceCompat?.awaitForever()
+                    buffer = hardwareBuffer
+                    bufferReadyLatch.countDown()
+                    drawCancelledRequestLatch?.countDown()
+                }
+            })
+        try {
+            renderer.render(Color.RED)
+            assertTrue(initialDrawLatch.await(3000, TimeUnit.MILLISECONDS))
+
+            executor.execute {
+                waitForRequestLatch.await()
+            }
+
+            drawCancelledRequestLatch = CountDownLatch(2)
+            renderer.render(Color.GREEN)
+            renderer.render(Color.YELLOW)
+            renderer.cancelPending()
+            waitForRequestLatch.countDown()
+
+            assertTrue(bufferReadyLatch.await(3000, TimeUnit.MILLISECONDS))
+            // Because the requests were cancelled this latch should not be signalled
+            assertFalse(drawCancelledRequestLatch.await(1000, TimeUnit.MILLISECONDS))
+            assertNotNull(buffer)
+            val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+            val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
+                ?.copy(Bitmap.Config.ARGB_8888, false)
+            assertNotNull(bitmap)
+            assertTrue(bitmap!!.isAllColor(Color.RED))
+        } finally {
+            val latch = CountDownLatch(1)
+            renderer.release(true) {
+                executor.shutdownNow()
+                latch.countDown()
+            }
+            assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testMultiReleasesDoesNotCrash() {
+        val transformer = BufferTransformer()
+        val executor = Executors.newSingleThreadExecutor()
+        val renderer = SingleBufferedCanvasRendererV29(
+            TEST_WIDTH,
+            TEST_HEIGHT,
+            transformer,
+            executor,
+            object : SingleBufferedCanvasRenderer.RenderCallbacks<Void> {
+                override fun render(canvas: Canvas, width: Int, height: Int, param: Void) {
+                    // NO-OP
+                }
+
+                override fun onBufferReady(
+                    hardwareBuffer: HardwareBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    // NO-OP
+                }
+            })
+        try {
+            val latch = CountDownLatch(1)
+            renderer.release(true) {
+                executor.shutdownNow()
+                latch.countDown()
+            }
+            assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+            renderer.release(true)
+        } finally {
+            if (!executor.isShutdown) {
+                executor.shutdownNow()
+            }
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    @Test
+    fun testRendererVisibleFlag() {
+        var supportsNativeAndroidFence = false
+        withEgl { eglManager ->
+            supportsNativeAndroidFence = eglManager.supportsNativeAndroidFence()
+        }
+        if (!supportsNativeAndroidFence) {
+            return
+        }
+        val transformer = BufferTransformer().apply {
+            computeTransform(TEST_WIDTH, TEST_HEIGHT, BUFFER_TRANSFORM_IDENTITY)
+        }
+        val executor = Executors.newSingleThreadExecutor()
+        var syncFenceNull = false
+        var drawLatch: CountDownLatch? = null
+        val renderer = SingleBufferedCanvasRendererV29(
+            TEST_WIDTH,
+            TEST_HEIGHT,
+            transformer,
+            executor,
+            object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
+                override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
+                    canvas.drawColor(param)
+                }
+
+                override fun onBufferReady(
+                    hardwareBuffer: HardwareBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    syncFenceNull = syncFenceCompat == null
+                    syncFenceCompat?.awaitForever()
+                    drawLatch?.countDown()
+                }
+            })
+        try {
+            renderer.isVisible = false
+            drawLatch = CountDownLatch(1)
+            renderer.render(Color.RED)
+            assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
+            assertFalse(syncFenceNull)
+
+            renderer.isVisible = true
+            drawLatch = CountDownLatch(1)
+            renderer.render(Color.BLUE)
+            assertTrue(drawLatch.await(3000, TimeUnit.MILLISECONDS))
+            assertTrue(syncFenceNull)
+        } finally {
+            val latch = CountDownLatch(1)
+            renderer.release(true) {
+                executor.shutdownNow()
+                latch.countDown()
+            }
+            assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.Q)
+    private fun testRenderWithTransform(
+        transform: Int,
+        actualColors: RectColors,
+        expectedColors: RectColors
+    ) {
+        val transformer = BufferTransformer()
+        transformer.computeTransform(TEST_WIDTH, TEST_HEIGHT, transform)
+        val executor = Executors.newSingleThreadExecutor()
+        var buffer: HardwareBuffer? = null
+        val renderLatch = CountDownLatch(1)
+        val renderer = SingleBufferedCanvasRendererV29(
+            TEST_WIDTH,
+            TEST_HEIGHT,
+            transformer,
+            executor,
+            object : SingleBufferedCanvasRenderer.RenderCallbacks<Int> {
+                override fun render(canvas: Canvas, width: Int, height: Int, param: Int) {
+                    drawSquares(
+                        canvas,
+                        width,
+                        height,
+                        actualColors.topLeft,
+                        actualColors.topRight,
+                        actualColors.bottomLeft,
+                        actualColors.bottomRight
+                    )
+                }
+
+                override fun onBufferReady(
+                    hardwareBuffer: HardwareBuffer,
+                    syncFenceCompat: SyncFenceCompat?
+                ) {
+                    syncFenceCompat?.awaitForever()
+                    buffer = hardwareBuffer
+                    renderLatch.countDown()
+                }
+            })
+        try {
+            renderer.render(0)
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            assertNotNull(buffer)
+            val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+            val bitmap = Bitmap.wrapHardwareBuffer(buffer!!, colorSpace)
+                ?.copy(Bitmap.Config.ARGB_8888, false)
+            assertNotNull(bitmap)
+            bitmap!!.verifyQuadrants(
+                expectedColors.topLeft,
+                expectedColors.topRight,
+                expectedColors.bottomLeft,
+                expectedColors.bottomRight
+            )
+        } finally {
+            val latch = CountDownLatch(1)
+            renderer.release(true) {
+                executor.shutdownNow()
+                latch.countDown()
+            }
+            assertTrue(latch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
index 68fccb0..a03d42d 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/GLRendererTest.kt
@@ -19,7 +19,9 @@
 import android.graphics.Bitmap
 import android.graphics.Color
 import android.graphics.ColorSpace
+import android.graphics.Paint
 import android.graphics.PixelFormat
+import android.graphics.RenderNode
 import android.graphics.SurfaceTexture
 import android.hardware.HardwareBuffer
 import android.media.Image
@@ -37,12 +39,15 @@
 import android.view.TextureView
 import androidx.annotation.RequiresApi
 import androidx.annotation.WorkerThread
+import androidx.graphics.SurfaceTextureRenderer
+import androidx.graphics.isAllColor
 import androidx.graphics.lowlatency.LineRenderer
 import androidx.graphics.lowlatency.Rectangle
 import androidx.hardware.SyncFenceCompat
 import androidx.graphics.opengl.egl.EGLManager
 import androidx.graphics.opengl.egl.EGLSpec
 import androidx.graphics.opengl.egl.supportsNativeAndroidFence
+import androidx.graphics.verifyQuadrants
 import androidx.lifecycle.Lifecycle.State
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -816,7 +821,7 @@
         val teardownLatch = CountDownLatch(1)
         val glRenderer = GLRenderer().apply { start() }
         var frameBuffer: FrameBuffer? = null
-        var status: Boolean? = false
+        var status = false
         var supportsFence = false
 
         val callbacks = object : FrameBufferRenderer.RenderCallback {
@@ -862,7 +867,7 @@
                 frameBuffer: FrameBuffer,
                 syncFenceCompat: SyncFenceCompat?
             ) {
-                status = syncFenceCompat?.await(3000)
+                status = syncFenceCompat?.await(3000) ?: true
                 renderLatch.countDown()
             }
         }
@@ -877,10 +882,7 @@
         try {
             assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
             if (supportsFence) {
-                assert(status != null)
-                status?.let {
-                    assertTrue(it)
-                }
+                assert(status)
             }
 
             hardwareBuffer = frameBuffer?.hardwareBuffer
@@ -908,6 +910,275 @@
 
     @Test
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    fun testQuadTextureRenderer() {
+        val width = 10
+        val height = 10
+        val renderLatch = CountDownLatch(1)
+        val teardownLatch = CountDownLatch(1)
+        val glRenderer = GLRenderer().apply { start() }
+        var frameBuffer: FrameBuffer? = null
+        var status = false
+        var supportsFence = false
+        val frameHandlerThread = HandlerThread("frameAvailable").apply { start() }
+        val frameHandler = Handler(frameHandlerThread.looper)
+        val callbacks = object : FrameBufferRenderer.RenderCallback {
+
+            private val mOrthoMatrix = FloatArray(16)
+
+            override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer =
+                FrameBuffer(
+                    egl,
+                    HardwareBuffer.create(
+                        width,
+                        height,
+                        HardwareBuffer.RGBA_8888,
+                        1,
+                        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                    )
+                ).also { frameBuffer = it }
+
+            override fun onDraw(eglManager: EGLManager) {
+                val texId = genTexture()
+                val frameAvailableLatch = CountDownLatch(1)
+                val surfaceTexture = SurfaceTexture(texId).apply {
+                    setDefaultBufferSize(width, height)
+                    setOnFrameAvailableListener({
+                        frameAvailableLatch.countDown()
+                    }, frameHandler)
+                }
+
+                val surface = Surface(surfaceTexture)
+                val canvas = surface.lockCanvas(null)
+                canvas.save()
+                val paint = Paint()
+                // top left
+                canvas.drawRect(0f, 0f, width / 2f, height / 2f,
+                    paint.apply { color = Color.RED })
+                // top right
+                canvas.drawRect(width / 2f, 0f, width.toFloat(), height / 2f,
+                    paint.apply { color = Color.BLUE })
+                // bottom left
+                canvas.drawRect(0f, height / 2f, width / 2f, height.toFloat(),
+                    paint.apply { color = Color.YELLOW })
+                // bottom right
+                canvas.drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(),
+                    paint.apply { color = Color.GREEN })
+                canvas.restore()
+                surface.unlockCanvasAndPost(canvas)
+
+                assertTrue(frameAvailableLatch.await(3000, TimeUnit.MILLISECONDS))
+
+                GLES20.glViewport(0, 0, width, height)
+                Matrix.orthoM(
+                    mOrthoMatrix,
+                    0,
+                    0f,
+                    width.toFloat(),
+                    0f,
+                    height.toFloat(),
+                    -1f,
+                    1f
+                )
+                val quadRenderer = QuadTextureRenderer().apply {
+                    setSurfaceTexture(surfaceTexture)
+                }
+                quadRenderer.draw(
+                    mOrthoMatrix,
+                    width.toFloat(),
+                    height.toFloat()
+                )
+                supportsFence = eglManager.supportsNativeAndroidFence()
+                quadRenderer.release()
+                surface.release()
+                surfaceTexture.release()
+                deleteTexture(texId)
+            }
+
+            override fun onDrawComplete(
+                frameBuffer: FrameBuffer,
+                syncFenceCompat: SyncFenceCompat?
+            ) {
+                status = syncFenceCompat?.await(3000) ?: true
+                renderLatch.countDown()
+            }
+        }
+
+        glRenderer.createRenderTarget(
+            width,
+            height,
+            FrameBufferRenderer(callbacks)
+        ).requestRender()
+
+        var hardwareBuffer: HardwareBuffer? = null
+        try {
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            if (supportsFence) {
+                assertTrue(status)
+            }
+
+            hardwareBuffer = frameBuffer?.hardwareBuffer
+            if (hardwareBuffer != null) {
+                val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+                // Copy to non hardware bitmap to be able to sample pixels
+                val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace)
+                    ?.copy(Bitmap.Config.ARGB_8888, false)
+                if (bitmap != null) {
+                    bitmap.verifyQuadrants(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN)
+                } else {
+                    fail("Unable to obtain Bitmap from hardware buffer")
+                }
+            } else {
+                fail("Unable to obtain hardwarebuffer from FrameBuffer")
+            }
+        } finally {
+            hardwareBuffer?.close()
+            glRenderer.stop(true) {
+                teardownLatch.countDown()
+            }
+            frameHandlerThread.quit()
+            assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
+    fun testRenderSurfaceTextureFromSurfaceTextureRenderer() {
+        val width = 10
+        val height = 10
+        val renderLatch = CountDownLatch(1)
+        val teardownLatch = CountDownLatch(1)
+        val glRenderer = GLRenderer().apply { start() }
+        var frameBuffer: FrameBuffer? = null
+        var status = false
+        var supportsFence = false
+        val frameHandlerThread = HandlerThread("frameAvailable").apply { start() }
+        val frameHandler = Handler(frameHandlerThread.looper)
+        val renderNode = RenderNode("node").apply {
+            setPosition(0, 0, width, height)
+            val canvas = beginRecording()
+            val paint = Paint()
+            // top left
+            canvas.drawRect(0f, 0f, width / 2f, height / 2f,
+                paint.apply { color = Color.RED })
+            // top right
+            canvas.drawRect(width / 2f, 0f, width.toFloat(), height / 2f,
+                paint.apply { color = Color.BLUE })
+            // bottom left
+            canvas.drawRect(0f, height / 2f, width / 2f, height.toFloat(),
+                paint.apply { color = Color.YELLOW })
+            // bottom right
+            canvas.drawRect(width / 2f, height / 2f, width.toFloat(), height.toFloat(),
+                paint.apply { color = Color.GREEN })
+            endRecording()
+        }
+        val frameAvailableLatch = CountDownLatch(1)
+        var surfaceTexture: SurfaceTexture? = null
+        val surfaceTextureRenderer = SurfaceTextureRenderer(
+            renderNode,
+            width,
+            height,
+            frameHandler
+        ) {
+            surfaceTexture = it
+            frameAvailableLatch.countDown()
+        }
+        surfaceTextureRenderer.renderFrame()
+
+        val callbacks = object : FrameBufferRenderer.RenderCallback {
+
+            private val mOrthoMatrix = FloatArray(16)
+
+            override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer =
+                FrameBuffer(
+                    egl,
+                    HardwareBuffer.create(
+                        width,
+                        height,
+                        HardwareBuffer.RGBA_8888,
+                        1,
+                        HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE
+                    )
+                ).also { frameBuffer = it }
+
+            override fun onDraw(eglManager: EGLManager) {
+                val texId = genTexture()
+                assertTrue(frameAvailableLatch.await(3000, TimeUnit.MILLISECONDS))
+                assertNotNull(surfaceTexture)
+                surfaceTexture!!.attachToGLContext(texId)
+
+                GLES20.glViewport(0, 0, width, height)
+                Matrix.orthoM(
+                    mOrthoMatrix,
+                    0,
+                    0f,
+                    width.toFloat(),
+                    0f,
+                    height.toFloat(),
+                    -1f,
+                    1f
+                )
+                val quadRenderer = QuadTextureRenderer().apply {
+                    setSurfaceTexture(surfaceTexture!!)
+                }
+                quadRenderer.draw(
+                    mOrthoMatrix,
+                    width.toFloat(),
+                    height.toFloat()
+                )
+                supportsFence = eglManager.supportsNativeAndroidFence()
+                quadRenderer.release()
+                deleteTexture(texId)
+            }
+
+            override fun onDrawComplete(
+                frameBuffer: FrameBuffer,
+                syncFenceCompat: SyncFenceCompat?
+            ) {
+                status = syncFenceCompat?.await(3000) ?: true
+                renderLatch.countDown()
+            }
+        }
+
+        glRenderer.createRenderTarget(
+            width,
+            height,
+            FrameBufferRenderer(callbacks)
+        ).requestRender()
+
+        var hardwareBuffer: HardwareBuffer? = null
+        try {
+            assertTrue(renderLatch.await(3000, TimeUnit.MILLISECONDS))
+            if (supportsFence) {
+                assertTrue(status)
+            }
+
+            hardwareBuffer = frameBuffer?.hardwareBuffer
+            if (hardwareBuffer != null) {
+                val colorSpace = ColorSpace.get(ColorSpace.Named.LINEAR_SRGB)
+                // Copy to non hardware bitmap to be able to sample pixels
+                val bitmap = Bitmap.wrapHardwareBuffer(hardwareBuffer, colorSpace)
+                    ?.copy(Bitmap.Config.ARGB_8888, false)
+                if (bitmap != null) {
+                    bitmap.verifyQuadrants(Color.RED, Color.BLUE, Color.YELLOW, Color.GREEN)
+                } else {
+                    fail("Unable to obtain Bitmap from hardware buffer")
+                }
+            } else {
+                fail("Unable to obtain hardwarebuffer from FrameBuffer")
+            }
+        } finally {
+            surfaceTextureRenderer.release()
+            hardwareBuffer?.close()
+            glRenderer.stop(true) {
+                teardownLatch.countDown()
+            }
+            frameHandlerThread.quit()
+            assertTrue(teardownLatch.await(3000, TimeUnit.MILLISECONDS))
+        }
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     fun testFrameBufferRendererWithSyncFence() {
 
         val width = 10
@@ -1068,14 +1339,15 @@
         assertTrue(copyLatch.await(3000, TimeUnit.MILLISECONDS))
     }
 
-    private fun Bitmap.isAllColor(targetColor: Int): Boolean {
-        for (i in 0 until width) {
-            for (j in 0 until height) {
-                if (getPixel(i, j) != targetColor) {
-                    return false
-                }
-            }
-        }
-        return true
+    private fun genTexture(): Int {
+        val buffer = IntArray(1)
+        GLES20.glGenTextures(1, buffer, 0)
+        return buffer[0]
+    }
+
+    private fun deleteTexture(texId: Int) {
+        val buffer = IntArray(1)
+        buffer[0] = texId
+        GLES20.glDeleteTextures(1, buffer, 0)
     }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncStrategyTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncStrategyTest.kt
index 38050ea..482afc6 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncStrategyTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/SyncStrategyTest.kt
@@ -16,19 +16,15 @@
 
 package androidx.graphics.opengl
 
-import android.opengl.EGL14
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.graphics.lowlatency.FrontBufferSyncStrategy
-import androidx.graphics.lowlatency.GLFrontBufferedRenderer
-import androidx.graphics.opengl.egl.EGLConfigAttributes
-import androidx.graphics.opengl.egl.EGLManager
+import androidx.graphics.lowlatency.FrontBufferUtils
 import androidx.graphics.opengl.egl.EGLSpec
-import androidx.graphics.opengl.egl.EGLVersion
 import androidx.graphics.opengl.egl.supportsNativeAndroidFence
+import androidx.graphics.withEgl
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
-import org.junit.Assert
 import org.junit.Assert.assertTrue
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -37,7 +33,7 @@
 @SmallTest
 @RequiresApi(Build.VERSION_CODES.Q)
 class SyncStrategyTest {
-    private val mUsageFlags = GLFrontBufferedRenderer.obtainHardwareBufferUsageFlags()
+    private val mUsageFlags = FrontBufferUtils.obtainHardwareBufferUsageFlags()
 
     @RequiresApi(Build.VERSION_CODES.O)
     @Test
@@ -101,37 +97,4 @@
             }
         }
     }
-
-    private fun withEgl(block: (egl: EGLManager) -> Unit) {
-        val egl = createAndSetupEGLManager(EGLSpec.V14)
-        try {
-            block(egl)
-        } finally {
-            releaseEGLManager(egl)
-        }
-    }
-
-    // Helper method to create and initialize an EGLManager
-    fun createAndSetupEGLManager(eglSpec: EGLSpec = EGLSpec.V14): EGLManager {
-        val egl = EGLManager(eglSpec)
-        Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
-        Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
-
-        egl.initialize()
-
-        val config = egl.loadConfig(EGLConfigAttributes.RGBA_8888)
-        if (config == null) {
-            Assert.fail("Config 888 should be supported")
-        }
-
-        egl.createContext(config!!)
-        return egl
-    }
-
-    // Helper method to release EGLManager
-    fun releaseEGLManager(egl: EGLManager) {
-        egl.release()
-        Assert.assertEquals(EGLVersion.Unknown, egl.eglVersion)
-        Assert.assertEquals(EGL14.EGL_NO_CONTEXT, egl.eglContext)
-    }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/MultiBufferedCanvasRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/MultiBufferedCanvasRenderer.kt
new file mode 100644
index 0000000..a9feab6
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/MultiBufferedCanvasRenderer.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics
+
+import android.annotation.SuppressLint
+import android.graphics.HardwareRenderer
+import android.graphics.PixelFormat
+import android.graphics.RenderNode
+import android.hardware.HardwareBuffer
+import android.media.ImageReader
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import java.util.concurrent.Executor
+
+/**
+ * Helper class used to draw RenderNode content into a HardwareBuffer instance. The contents of the
+ * HardwareBuffer are not persisted across renders.
+ */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal class MultiBufferedCanvasRenderer(
+    private val renderNode: RenderNode,
+    width: Int,
+    height: Int,
+    format: Int = PixelFormat.RGBA_8888,
+    usage: Long = HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or HardwareBuffer.USAGE_GPU_COLOR_OUTPUT,
+    maxImages: Int = 2
+) {
+    // PixelFormat.RGBA_8888 should be accepted here but Android Studio flags as a warning
+    @SuppressLint("WrongConstant")
+    private val mImageReader = ImageReader.newInstance(width, height, format, maxImages, usage)
+    private var mHardwareRenderer: HardwareRenderer? = HardwareRenderer().apply {
+        setContentRoot(renderNode)
+        setSurface(mImageReader.surface)
+        start()
+    }
+
+    private var mIsReleased = false
+
+    fun renderFrame(executor: Executor, bufferAvailable: (HardwareBuffer) -> Unit) {
+        val renderer = mHardwareRenderer
+        if (renderer != null && !mIsReleased) {
+            with(renderer) {
+                createRenderRequest()
+                    .setFrameCommitCallback(executor) {
+                        val nextImage = mImageReader.acquireNextImage()
+                        nextImage?.let { image ->
+                            val buffer = image.hardwareBuffer
+                            if (buffer != null) {
+                                executor.execute {
+                                    bufferAvailable(buffer)
+                                }
+                            }
+                            image.close()
+                        }
+                    }
+                    .syncAndDraw()
+            }
+        } else {
+            Log.v(TAG, "mHardwareRenderer is null")
+        }
+    }
+
+    fun release() {
+        if (!mIsReleased) {
+            mImageReader.close()
+            mHardwareRenderer?.let { renderer ->
+                renderer.stop()
+                renderer.destroy()
+            }
+            mHardwareRenderer = null
+            mIsReleased = true
+        }
+    }
+
+    internal companion object {
+        const val TAG = "MultiBufferRenderer"
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/SurfaceTextureRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/SurfaceTextureRenderer.kt
new file mode 100644
index 0000000..a323ed6
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/SurfaceTextureRenderer.kt
@@ -0,0 +1,126 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics
+
+import android.graphics.HardwareRenderer
+import android.graphics.RenderNode
+import android.graphics.SurfaceTexture
+import android.os.Build
+import android.os.Handler
+import android.util.Log
+import android.view.Surface
+import androidx.annotation.RequiresApi
+
+/**
+ * Class that handles drawing content of a RenderNode into a SurfaceTexture
+ */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal class SurfaceTextureRenderer(
+    /**
+     * Target RenderNode of the content that is to be drawn
+     */
+    private val renderNode: RenderNode,
+
+    /**
+     * Width of the SurfaceTexture
+     */
+    width: Int,
+
+    /**
+     * Height of the SurfaceTexture
+     */
+    height: Int,
+
+    /**
+     * Handler used to send SurfaceTexture#OnFrameAvailableListener callbacks
+     */
+    private val handler: Handler,
+
+    /**
+     * Callback invoked when a new image frame is available on the underlying SurfaceTexture
+     */
+    private val frameAvailable: (SurfaceTexture) -> Unit
+) {
+
+    // Workaround: b/272751501
+    // For some reason, SurfaceTexture instances that are created in detached mode get gc'ed
+    // prematurely after the application is idle for ~10 or more seconds. This issue appears in
+    // multiple versions of Android. However, if we use a subclass of SurfaceTexture we seem to
+    // not run into this. It appears that there is some internal package name checking of
+    // android.graphics.SurfaceTexture API within jni in the platform and using a subclass seems
+    // to prevent work around this issue.
+    //
+    // An alternative solution is to create a SurfaceTexture in attached mode by providing a
+    // placeholder texture identifier then having the consuming GLThread call detachFromGLContext
+    // and attachToGLContext with a freshly created texture id.
+    //
+    // Currently we go with the original option as it may not be explicit to implementations that
+    // the initial detach is necessary here.
+    private class RenderSurfaceTexture(singleBufferMode: Boolean) : SurfaceTexture(singleBufferMode)
+
+    private var mIsReleased = false
+
+    private val mSurfaceTexture = RenderSurfaceTexture(false).apply {
+        setDefaultBufferSize(width, height)
+        setOnFrameAvailableListener({ surfaceTexture -> frameAvailable(surfaceTexture) }, handler)
+    }
+
+    private val mTextureSurface = Surface(mSurfaceTexture)
+    private val mHardwareRenderer = HardwareRenderer().apply {
+        setSurface(mTextureSurface)
+        setContentRoot(renderNode)
+        start()
+    }
+
+    fun renderFrame() {
+        if (!mIsReleased) {
+            mHardwareRenderer.apply {
+                createRenderRequest()
+                    .setWaitForPresent(false)
+                    .syncAndDraw()
+            }
+        } else {
+            Log.w(
+                TAG, "Attempt to renderFrame when SurfaceTextureRenderer has already " +
+                "been released")
+        }
+    }
+
+    /**
+     * Releases all resources of the SurfaceTextureRenderer instances. Attempts to use this
+     * object after this call has been made will be ignored.
+     */
+    fun release() {
+        if (!mIsReleased) {
+            mHardwareRenderer.stop()
+            mHardwareRenderer.destroy()
+            mTextureSurface.release()
+            if (!mSurfaceTexture.isReleased) {
+                mSurfaceTexture.release()
+            }
+            mIsReleased = true
+        } else {
+            Log.w(
+                TAG, "Attempt to release a SurfaceTextureRenderer that has " +
+                "already been released")
+        }
+    }
+
+    companion object {
+        private val TAG = "SurfaceTextureRenderer"
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt
new file mode 100644
index 0000000..415fa3b
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/FrontBufferUtils.kt
@@ -0,0 +1,100 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.lowlatency
+
+import android.annotation.SuppressLint
+import android.hardware.HardwareBuffer
+import android.os.Build
+import androidx.annotation.RequiresApi
+
+internal class FrontBufferUtils private constructor() {
+
+    companion object {
+
+        internal const val TAG = "FrontBufferUtils"
+
+        // Leverage the same value as HardwareBuffer.USAGE_COMPOSER_OVERLAY.
+        // While this constant was introduced in the SDK in the Android T release, it has
+        // been available within the NDK as part of
+        // AHardwareBuffer_UsageFlags#AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY for quite some time.
+        // This flag is required for usage of ASurfaceTransaction#setBuffer
+        // Use a separate constant with the same value to avoid SDK warnings of accessing the
+        // newly added constant in the SDK.
+        // See:
+        // developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
+        private const val USAGE_COMPOSER_OVERLAY: Long = 2048L
+
+        /**
+         * Flags that are expected to be supported on all [HardwareBuffer] instances
+         */
+        internal const val BaseFlags =
+            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or
+                HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or
+                USAGE_COMPOSER_OVERLAY
+
+        internal fun obtainHardwareBufferUsageFlags(): Long =
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
+                UsageFlagsVerificationHelper.obtainUsageFlagsV33()
+            } else {
+                BaseFlags
+            }
+    }
+}
+
+/**
+ * Helper class to avoid class verification failures
+ */
+@RequiresApi(Build.VERSION_CODES.Q)
+internal class UsageFlagsVerificationHelper private constructor() {
+    companion object {
+
+        /**
+         * Helper method to determine if a particular HardwareBuffer usage flag is supported.
+         * Even though the FRONT_BUFFER_USAGE and COMPOSER_OVERLAY flags are introduced in
+         * Android T, not all devices may support this flag. So we conduct a capability query
+         * with a sample 1x1 HardwareBuffer with the provided flag to see if it is compatible
+         */
+        // Suppressing WrongConstant warnings as we are leveraging a constant with the same value
+        // as HardwareBuffer.USAGE_COMPOSER_OVERLAY to avoid SDK checks as the constant has been
+        // supported in the NDK for several platform releases.
+        // See:
+        // developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
+        @SuppressLint("WrongConstant")
+        @RequiresApi(Build.VERSION_CODES.Q)
+        @androidx.annotation.DoNotInline
+        internal fun isSupported(flag: Long): Boolean =
+            HardwareBuffer.isSupported(
+                1, // width
+                1, // height
+                HardwareBuffer.RGBA_8888, // format
+                1, // layers
+                FrontBufferUtils.BaseFlags or flag
+            )
+
+        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
+        @androidx.annotation.DoNotInline
+        fun obtainUsageFlagsV33(): Long {
+            // First verify if the front buffer usage flag is supported along with the
+            // "usage composer overlay" flag that was introduced in API level 33
+            return if (isSupported(HardwareBuffer.USAGE_FRONT_BUFFER)) {
+                FrontBufferUtils.BaseFlags or HardwareBuffer.USAGE_FRONT_BUFFER
+            } else {
+                FrontBufferUtils.BaseFlags
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
index 829e606..db7462f 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/GLFrontBufferedRenderer.kt
@@ -294,7 +294,7 @@
 
         mGLRenderer = renderer
 
-        mHardwareBufferUsageFlags = obtainHardwareBufferUsageFlags()
+        mHardwareBufferUsageFlags = FrontBufferUtils.obtainHardwareBufferUsageFlags()
 
         mFrontBufferSyncStrategy = FrontBufferSyncStrategy(mHardwareBufferUsageFlags)
     }
@@ -324,7 +324,7 @@
                 bufferWidth,
                 bufferHeight,
                 format = HardwareBuffer.RGBA_8888,
-                usage = BaseFlags,
+                usage = FrontBufferUtils.BaseFlags,
                 maxPoolSize = 4
             )
 
@@ -675,32 +675,6 @@
     companion object {
 
         internal const val TAG = "GLFrontBufferedRenderer"
-
-        // Leverage the same value as HardwareBuffer.USAGE_COMPOSER_OVERLAY.
-        // While this constant was introduced in the SDK in the Android T release, it has
-        // been available within the NDK as part of
-        // AHardwareBuffer_UsageFlags#AHARDWAREBUFFER_USAGE_COMPOSER_OVERLAY for quite some time.
-        // This flag is required for usage of ASurfaceTransaction#setBuffer
-        // Use a separate constant with the same value to avoid SDK warnings of accessing the
-        // newly added constant in the SDK.
-        // See:
-        // developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
-        private const val USAGE_COMPOSER_OVERLAY: Long = 2048L
-
-        /**
-         * Flags that are expected to be supported on all [HardwareBuffer] instances
-         */
-        internal const val BaseFlags =
-            HardwareBuffer.USAGE_GPU_SAMPLED_IMAGE or
-                HardwareBuffer.USAGE_GPU_COLOR_OUTPUT or
-                USAGE_COMPOSER_OVERLAY
-
-        internal fun obtainHardwareBufferUsageFlags(): Long =
-            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
-                UsageFlagsVerificationHelper.obtainUsageFlagsV33()
-            } else {
-                BaseFlags
-            }
     }
 
     @JvmDefaultWithCompatibility
@@ -874,48 +848,4 @@
             // Default implementation is a no-op
         }
     }
-}
-
-/**
- * Helper class to avoid class verification failures
- */
-@RequiresApi(Build.VERSION_CODES.Q)
-internal class UsageFlagsVerificationHelper private constructor() {
-    companion object {
-
-        /**
-         * Helper method to determine if a particular HardwareBuffer usage flag is supported.
-         * Even though the FRONT_BUFFER_USAGE and COMPOSER_OVERLAY flags are introduced in
-         * Android T, not all devices may support this flag. So we conduct a capability query
-         * with a sample 1x1 HardwareBuffer with the provided flag to see if it is compatible
-         */
-        // Suppressing WrongConstant warnings as we are leveraging a constant with the same value
-        // as HardwareBuffer.USAGE_COMPOSER_OVERLAY to avoid SDK checks as the constant has been
-        // supported in the NDK for several platform releases.
-        // See:
-        // developer.android.com/ndk/reference/group/a-hardware-buffer#ahardwarebuffer_usageflags
-        @SuppressLint("WrongConstant")
-        @RequiresApi(Build.VERSION_CODES.Q)
-        @androidx.annotation.DoNotInline
-        internal fun isSupported(flag: Long): Boolean =
-            HardwareBuffer.isSupported(
-                1, // width
-                1, // height
-                HardwareBuffer.RGBA_8888, // format
-                1, // layers
-                GLFrontBufferedRenderer.BaseFlags or flag
-            )
-
-        @RequiresApi(Build.VERSION_CODES.TIRAMISU)
-        @androidx.annotation.DoNotInline
-        fun obtainUsageFlagsV33(): Long {
-            // First verify if the front buffer usage flag is supported along with the
-            // "usage composer overlay" flag that was introduced in API level
-            return if (isSupported(HardwareBuffer.USAGE_FRONT_BUFFER)) {
-                GLFrontBufferedRenderer.BaseFlags or HardwareBuffer.USAGE_FRONT_BUFFER
-            } else {
-                GLFrontBufferedRenderer.BaseFlags
-            }
-        }
-    }
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/ParamQueue.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/ParamQueue.kt
index a0b1765..167bd13 100644
--- a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/ParamQueue.kt
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/ParamQueue.kt
@@ -80,4 +80,8 @@
             mParams.add(param)
         }
     }
+
+    fun count(): Int = mLock.withLock { mParams.size }
+
+    fun isEmpty() = count() == 0
 }
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRenderer.kt
new file mode 100644
index 0000000..8a66107
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRenderer.kt
@@ -0,0 +1,88 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.lowlatency
+
+import android.graphics.Canvas
+import android.hardware.HardwareBuffer
+import android.os.Build
+import androidx.annotation.RequiresApi
+import androidx.annotation.WorkerThread
+import androidx.hardware.SyncFenceCompat
+import java.util.concurrent.Executor
+
+/**
+ * Interface to provide an abstraction around implementations for a low latency hardware
+ * accelerated [Canvas] that provides a [HardwareBuffer] with the [Canvas] rendered scene
+ */
+internal interface SingleBufferedCanvasRenderer<T> {
+
+    interface RenderCallbacks<T> {
+        @WorkerThread
+        fun render(canvas: Canvas, width: Int, height: Int, param: T)
+
+        @WorkerThread
+        fun onBufferReady(hardwareBuffer: HardwareBuffer, syncFenceCompat: SyncFenceCompat?)
+    }
+
+    /**
+     * Render into the [HardwareBuffer] with the given parameter and bounds
+     */
+    fun render(param: T)
+
+    /**
+     * Flag to indicate whether or not the contents of the [SingleBufferedCanvasRenderer] are visible.
+     * This is used to help internal state to determine appropriate synchronization
+     */
+    var isVisible: Boolean
+
+    /**
+     * Releases resources associated with [SingleBufferedCanvasRenderer] instance. Attempts to
+     * use this object after it is closed will be ignored
+     */
+    fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)? = null)
+
+    /**
+     * Clear the contents of the [HardwareBuffer]
+     */
+    fun clear()
+
+    /**
+     * Cancel all pending render requests
+     */
+    fun cancelPending()
+
+    companion object {
+
+        @RequiresApi(Build.VERSION_CODES.Q)
+        fun <T> create(
+            width: Int,
+            height: Int,
+            bufferTransformer: BufferTransformer,
+            executor: Executor,
+            bufferReadyListener: RenderCallbacks<T>
+        ): SingleBufferedCanvasRenderer<T> {
+            // TODO return different instance for corresponding platform version
+            return SingleBufferedCanvasRendererV29(
+                width,
+                height,
+                bufferTransformer,
+                executor,
+                bufferReadyListener
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt
new file mode 100644
index 0000000..042ecb3
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/lowlatency/SingleBufferedCanvasRendererV29.kt
@@ -0,0 +1,407 @@
+/*
+ * 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.graphics.lowlatency
+
+import android.graphics.RenderNode
+import android.graphics.SurfaceTexture
+import android.hardware.HardwareBuffer
+import android.opengl.GLES20
+import android.opengl.Matrix
+import android.os.Build
+import android.os.Handler
+import android.os.Looper
+import android.util.Log
+import androidx.annotation.RequiresApi
+import androidx.annotation.WorkerThread
+import androidx.graphics.SurfaceTextureRenderer
+import androidx.graphics.lowlatency.FrontBufferUtils.Companion.obtainHardwareBufferUsageFlags
+import androidx.graphics.opengl.FrameBuffer
+import androidx.graphics.opengl.FrameBufferRenderer
+import androidx.graphics.opengl.GLRenderer
+import androidx.graphics.opengl.QuadTextureRenderer
+import androidx.graphics.opengl.egl.EGLManager
+import androidx.graphics.opengl.egl.EGLSpec
+import androidx.hardware.SyncFenceCompat
+import java.nio.IntBuffer
+import java.util.concurrent.Executor
+import java.util.concurrent.atomic.AtomicBoolean
+import java.util.concurrent.atomic.AtomicInteger
+
+@RequiresApi(Build.VERSION_CODES.Q)
+internal class SingleBufferedCanvasRendererV29<T>(
+    private val width: Int,
+    private val height: Int,
+    private val bufferTransformer: BufferTransformer,
+    private val executor: Executor,
+    private val callbacks: SingleBufferedCanvasRenderer.RenderCallbacks<T>,
+) : SingleBufferedCanvasRenderer<T> {
+
+    private val mMainHandler = Handler(Looper.myLooper() ?: Looper.getMainLooper())
+
+    private val mRenderNode = RenderNode("renderNode").apply {
+        setPosition(
+            0,
+            0,
+            this@SingleBufferedCanvasRendererV29.width,
+            this@SingleBufferedCanvasRendererV29.height
+        )
+    }
+
+    /**
+     * Runnable used to execute the request to render batched parameters
+     */
+    private val mRenderPendingRunnable = Runnable { renderPendingParameters() }
+
+    /**
+     * Runnable used to execute the request to clear buffer content on screen
+     */
+    private val mClearContentsRunnable = Runnable {
+        mFrameBufferRenderer.clear()
+        obtainFrameBufferTarget().requestRender()
+    }
+
+    /**
+     * SurfaceTextureRenderer used to render contents of a RenderNode into a SurfaceTexture
+     * that is then rendered into a HardwareBuffer for consumption
+     */
+    private val mSurfaceTextureRenderer = SurfaceTextureRenderer(
+            mRenderNode,
+            width,
+            height,
+            mMainHandler
+        ) { texture ->
+            mSurfaceTexture = texture
+            obtainFrameBufferTarget().requestRender()
+        }
+
+    /**
+     * Helper method to request the provided RenderNode content to be drawn on the texture
+     * rendering thread
+     */
+    internal fun dispatchRenderTextureRequest() {
+        executor.execute(mRenderPendingRunnable)
+    }
+
+    /**
+     * Helper method to request clearing the contents of the destination HardwareBuffer
+     */
+    private fun dispatchClearRequest() {
+        executor.execute(mClearContentsRunnable)
+    }
+
+    /**
+     * Maximum number of pending renders to the SurfaceTexture before we queue up parameters
+     * and wait for the consumer to catch up. Some devices have very fast input sampling rates
+     * which make the producing side much faster than the consuming side. We batch the pending
+     * parameters and when the consuming side catches up, we batch and render all the pending
+     * parameters into the SurfaceTexture that then gets drawn into the destination HardwareBuffer.
+     * This ensures we don't drop any attempts to render.
+     */
+    private val mMaxPendingBuffers = 2
+
+    /**
+     * Keep track of the number of pending renders of the source SurfaceTexture to the destination
+     * HardwareBuffer
+     */
+    private val mPendingBuffers = AtomicInteger(0)
+
+    /**
+     * Source SurfaceTexture that the destination of content to be rendered from the provided
+     * RenderNode
+     */
+    private var mSurfaceTexture: SurfaceTexture? = null
+
+    /**
+     * HardwareBuffer flags for front buffered rendering
+     */
+    private val mHardwareBufferUsageFlags = obtainHardwareBufferUsageFlags()
+
+    // ---------- GLThread ------
+
+    /**
+     * [FrontBufferSyncStrategy] used for [FrameBufferRenderer] to conditionally decide
+     * when to create a [SyncFenceCompat] for transaction calls.
+     */
+    private val mFrontBufferSyncStrategy = FrontBufferSyncStrategy(mHardwareBufferUsageFlags)
+
+    /**
+     * Shader that handles rendering a texture as a quad into the destination
+     */
+    private var mQuadRenderer: QuadTextureRenderer? = null
+
+    /**
+     * Texture id of the SurfaceTexture that is to be rendered
+     */
+    private var mTextureId: Int = -1
+
+    /**
+     * Scratch buffer used for gen/delete texture operations
+     */
+    private val buffer = IntArray(1)
+
+    // ---------- GLThread ------
+
+    private val mFrameBufferRenderer = FrameBufferRenderer(
+        object : FrameBufferRenderer.RenderCallback {
+
+            private val mMVPMatrix = FloatArray(16)
+            private val mProjection = FloatArray(16)
+
+            private fun obtainQuadRenderer(): QuadTextureRenderer =
+                mQuadRenderer ?: QuadTextureRenderer().apply {
+                    GLES20.glGenTextures(1, buffer, 0)
+                    mTextureId = buffer[0]
+                    mSurfaceTexture?.let { texture ->
+                        texture.attachToGLContext(mTextureId)
+                        setSurfaceTexture(texture)
+                    }
+                    mQuadRenderer = this
+                }
+
+            override fun obtainFrameBuffer(egl: EGLSpec): FrameBuffer {
+                return mFrontBufferLayer ?: FrameBuffer(
+                    egl,
+                    HardwareBuffer.create(
+                        bufferTransformer.glWidth,
+                        bufferTransformer.glHeight,
+                        HardwareBuffer.RGBA_8888,
+                        1,
+                        mHardwareBufferUsageFlags
+                    )
+                ).also { mFrontBufferLayer = it }
+            }
+
+            @RequiresApi(Build.VERSION_CODES.S)
+            override fun onDraw(eglManager: EGLManager) {
+                mSurfaceTexture?.let { texture ->
+                    val bufferWidth = bufferTransformer.glWidth
+                    val bufferHeight = bufferTransformer.glHeight
+                    GLES20.glViewport(0, 0, bufferWidth, bufferHeight)
+                    Matrix.orthoM(
+                        mMVPMatrix,
+                        0,
+                        0f,
+                        bufferWidth.toFloat(),
+                        0f,
+                        bufferHeight.toFloat(),
+                        -1f,
+                        1f
+                    )
+
+                    Matrix.multiplyMM(mProjection, 0, mMVPMatrix, 0, bufferTransformer.transform, 0)
+                    // texture.updateTexImage is called within QuadTextureRenderer#draw
+                    obtainQuadRenderer().draw(mProjection, width.toFloat(), height.toFloat())
+                    texture.releaseTexImage()
+                }
+            }
+
+            override fun onDrawComplete(
+                frameBuffer: FrameBuffer,
+                syncFenceCompat: SyncFenceCompat?
+            ) {
+                if (forceFlush.get()) {
+                    // See b/236394768. On some ANGLE versions, attempting to do a glClear + flush
+                    // does not actually flush pixels to FBOs with HardwareBuffer attachments
+                    // For testing purposes when verifying clear use cases, do a GPU readback
+                    // to actually executed any pending clear operations to verify output
+                    GLES20.glReadPixels(0, 0, 1, 1, GLES20.GL_RGBA, GLES20.GL_UNSIGNED_BYTE,
+                        IntBuffer.wrap(IntArray(1)))
+                }
+
+                val state = mState.get()
+                if (state != RELEASED) {
+                    executor.execute {
+                        callbacks.onBufferReady(frameBuffer.hardwareBuffer, syncFenceCompat)
+                    }
+                }
+                val pending = mPendingBuffers.decrementAndGet()
+                if (state == PENDING_RELEASE && pending <= 0) {
+                    mMainHandler.post(::tearDown)
+                } else {
+                    // After rendering see if there are additional queued content to render
+                    // If all images within the SurfaceTexture are pending being drawn into the
+                    // destination HardwareBuffer, they are queued up to be batch rendered after
+                    // texture image has been released
+                    dispatchRenderTextureRequest()
+                }
+            }
+        },
+        mFrontBufferSyncStrategy
+    )
+
+    /**
+     * [GLRenderer] used to render contents of the SurfaceTexture into a HardwareBuffer
+     */
+    private val mGLRenderer = GLRenderer().apply { start() }
+
+    /**
+     * Thread safe queue of parameters to be consumed in on the texture render thread that are
+     * provided in [SingleBufferedCanvasRenderer.render]
+     */
+    private val mParams = ParamQueue<T>()
+
+    /**
+     * State to determine if [release] has been called on this [SingleBufferedCanvasRendererV29]
+     * instance. If true, all subsequent operations are a no-op
+     */
+    private val mState = AtomicInteger(ACTIVE)
+
+    /**
+     * Pending release callback to be invoked when the renderer is torn down
+     */
+    private var mReleaseComplete: (() -> Unit)? = null
+
+    /**
+     * Flag to maintain visibility state on the main thread
+     */
+    private var mIsVisible = false
+
+    private fun isReleased(): Boolean {
+        val state = mState.get()
+        return state == RELEASED || state == PENDING_RELEASE
+    }
+
+    @WorkerThread
+    internal fun renderPendingParameters() {
+        val pending = mPendingBuffers.get()
+        // If there are pending requests to draw and we are not waiting on the consuming side
+        // to catch up, then render content in the RenderNode and issue a request to draw into
+        // the SurfaceTexture
+        if (pending < mMaxPendingBuffers) {
+            val params = mParams.release()
+            if (params.isNotEmpty()) {
+                val canvas = mRenderNode.beginRecording()
+                for (p in params) {
+                    callbacks.render(canvas, width, height, p)
+                }
+                mRenderNode.endRecording()
+                mPendingBuffers.incrementAndGet()
+                mSurfaceTextureRenderer.renderFrame()
+            }
+        }
+    }
+
+    override var isVisible: Boolean
+        set(value) {
+            mGLRenderer.execute {
+                mFrontBufferSyncStrategy.isVisible = value
+            }
+            mIsVisible = value
+        }
+        get() = mFrontBufferSyncStrategy.isVisible
+
+    private var mFrontBufferLayer: FrameBuffer? = null
+
+    private fun obtainFrameBufferTarget(): GLRenderer.RenderTarget =
+        mFrameBufferTarget ?: mGLRenderer.createRenderTarget(width, height, mFrameBufferRenderer)
+            .also { mFrameBufferTarget = it }
+
+    private var mFrameBufferTarget: GLRenderer.RenderTarget? = null
+
+    override fun render(param: T) {
+        ifNotReleased {
+            mParams.add(param)
+            if (mPendingBuffers.get() < mMaxPendingBuffers) {
+                dispatchRenderTextureRequest()
+            }
+        }
+    }
+
+    override fun release(cancelPending: Boolean, onReleaseComplete: (() -> Unit)?) {
+        ifNotReleased {
+            mReleaseComplete = onReleaseComplete
+            if (cancelPending || !isPendingRendering()) {
+                mState.set(RELEASED)
+                cancelPending()
+                tearDown()
+            } else {
+                mState.set(PENDING_RELEASE)
+            }
+        }
+    }
+
+    private fun isPendingRendering() = mParams.isEmpty() || mPendingBuffers.get() > 0
+
+    internal fun tearDown() {
+        mFrameBufferTarget?.detach(true) {
+            // GL Thread
+            mQuadRenderer?.release()
+            if (mTextureId != -1) {
+                buffer[0] = mTextureId
+                GLES20.glDeleteTextures(1, buffer, 0)
+                mTextureId = -1
+            }
+        }
+        mGLRenderer.stop(false)
+        mSurfaceTexture?.let { texture ->
+            if (!texture.isReleased) {
+                texture.release()
+            }
+        }
+        mRenderNode.discardDisplayList()
+        mSurfaceTextureRenderer.release()
+
+        mReleaseComplete?.let { callback ->
+            mMainHandler.post(callback)
+        }
+    }
+
+    override fun cancelPending() {
+        ifNotReleased {
+            mParams.clear()
+        }
+    }
+
+    override fun clear() {
+        ifNotReleased {
+            dispatchClearRequest()
+        }
+    }
+
+    private inline fun ifNotReleased(block: () -> Unit) {
+        if (!isReleased()) {
+            block()
+        } else {
+            Log.w(TAG, "Attempt to use already released renderer")
+        }
+    }
+
+    // See: b/236394768. Some test emulator instances have not picked up the fix so
+    // apply a workaround here for testing purposes
+    internal val forceFlush = AtomicBoolean(false)
+
+    private companion object {
+
+        /**
+         * Indicates the renderer is an in active state and can render content
+         */
+        const val ACTIVE = 0
+
+        /**
+         * Indicates the renderer is released and is no longer in a valid state to render content
+         */
+        const val RELEASED = 1
+
+        /**
+         * Indicates the renderer is completing rendering of current pending frames but not accepting
+         * new requests to render
+         */
+        const val PENDING_RELEASE = 2
+
+        const val TAG = "SingleBufferedCanvasV29"
+    }
+}
\ No newline at end of file
diff --git a/graphics/graphics-core/src/main/java/androidx/graphics/opengl/QuadTextureRenderer.kt b/graphics/graphics-core/src/main/java/androidx/graphics/opengl/QuadTextureRenderer.kt
new file mode 100644
index 0000000..f838ec4
--- /dev/null
+++ b/graphics/graphics-core/src/main/java/androidx/graphics/opengl/QuadTextureRenderer.kt
@@ -0,0 +1,331 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.graphics.opengl
+
+import android.graphics.SurfaceTexture
+import android.opengl.GLES11Ext
+import android.opengl.GLES20
+import android.os.Build
+import android.util.Log
+import androidx.annotation.RequiresApi
+import java.nio.ByteBuffer
+import java.nio.ByteOrder
+import java.nio.FloatBuffer
+import java.nio.ShortBuffer
+
+@RequiresApi(Build.VERSION_CODES.O)
+internal class QuadTextureRenderer {
+
+    private var mSurfaceTexture: SurfaceTexture? = null
+
+    /**
+     * Array used to store 4 vertices of x and y coordinates
+     */
+    private val mQuadCoords = FloatArray(8)
+
+    /**
+     * Transform to apply to the corresponding texture source
+     */
+    private val mTextureTransform = FloatArray(16)
+
+    /**
+     * Handle to the quad position attribute
+     */
+    private var mQuadPositionHandle = -1
+
+    /**
+     * Handle to the texture coordinate attribute
+     */
+    private var mTexPositionHandle = -1
+
+    /**
+     * Handle to the texture sampler uniform
+     */
+    private var mTextureUniformHandle: Int = -1
+
+    /**
+     * Handle to the MVP matrix uniform
+     */
+    private var mViewProjectionMatrixHandle: Int = -1
+
+    /**
+     * Handle to texture transform matrix
+     */
+    private var mTextureTransformHandle: Int = -1
+
+    /**
+     * GL Program used for rendering a quad with a texture
+     */
+    private var mProgram: Int = -1
+
+    /**
+     * Handle to the vertex shader
+     */
+    private var mVertexShader = -1
+
+    /**
+     * Handle to the fragment shader
+     */
+    private var mFragmentShader = -1
+
+    /**
+     * Flag to indicate the resources associated with the shaders/texture has been
+     * released. If this is true all subsequent attempts to draw should be ignored
+     */
+    private var mIsReleased = false
+
+    /**
+     * FloatBuffer used to specify quad coordinates
+     */
+    private val mQuadrantCoordinatesBuffer: FloatBuffer =
+        ByteBuffer.allocateDirect(mQuadCoords.size * 4).run {
+            order(ByteOrder.nativeOrder())
+            asFloatBuffer().apply {
+                position(0)
+            }
+        }
+
+    init {
+        mVertexShader = loadShader(GLES20.GL_VERTEX_SHADER, VertexShader)
+        mFragmentShader = loadShader(GLES20.GL_FRAGMENT_SHADER, FragmentShader)
+        mProgram = GLES20.glCreateProgram()
+
+        GLES20.glAttachShader(mProgram, mVertexShader)
+        GLES20.glAttachShader(mProgram, mFragmentShader)
+        GLES20.glLinkProgram(mProgram)
+        GLES20.glUseProgram(mProgram)
+
+        mQuadPositionHandle = GLES20.glGetAttribLocation(mProgram, aPosition)
+        mTexPositionHandle = GLES20.glGetAttribLocation(mProgram, aTexCoord)
+
+        mTextureUniformHandle = GLES20.glGetUniformLocation(mProgram, uTexture)
+        mViewProjectionMatrixHandle = GLES20.glGetUniformLocation(mProgram, uVPMatrix)
+        mTextureTransformHandle = GLES20.glGetUniformLocation(mProgram, tVPMatrix)
+
+        // Enable blend
+        GLES20.glEnable(GLES20.GL_BLEND)
+        // Uses to prevent transparent area to turn in black
+        GLES20.glBlendFunc(GLES20.GL_ONE, GLES20.GL_ONE_MINUS_SRC_ALPHA)
+    }
+
+    fun release() {
+        if (!mIsReleased) {
+            if (mVertexShader != -1) {
+                GLES20.glDeleteShader(mVertexShader)
+                mVertexShader = -1
+            }
+
+            if (mFragmentShader != -1) {
+                GLES20.glDeleteShader(mFragmentShader)
+                mFragmentShader = -1
+            }
+
+            if (mProgram != -1) {
+                GLES20.glDeleteProgram(mProgram)
+                mProgram = -1
+            }
+
+            mIsReleased = true
+        }
+    }
+
+    private fun configureQuad(width: Float, height: Float): FloatBuffer =
+        mQuadrantCoordinatesBuffer.apply {
+            put(mQuadCoords.apply {
+                this[0] = 0f // top left
+                this[1] = height
+                this[2] = 0f // bottom left
+                this[3] = 0f
+                this[4] = width // top right
+                this[5] = 0f
+                this[6] = width // bottom right
+                this[7] = height
+            })
+            position(0)
+        }
+
+    internal fun setSurfaceTexture(surfaceTexture: SurfaceTexture) {
+        mSurfaceTexture = surfaceTexture
+    }
+
+    fun draw(
+        mvpMatrix: FloatArray,
+        width: Float,
+        height: Float
+    ) {
+        if (mIsReleased) {
+            Log.w(TAG, "Attempt to render when TextureRenderer has been released")
+            return
+        }
+
+        val textureSource = mSurfaceTexture
+        if (textureSource == null) {
+            Log.w(TAG, "Attempt to render without texture source")
+            return
+        }
+
+        GLES20.glUseProgram(mProgram)
+        textureSource.updateTexImage()
+
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER,
+            GLES20.GL_LINEAR)
+        GLES20.glTexParameteri(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER,
+            GLES20.GL_LINEAR)
+
+        GLES20.glUniform1i(mTextureUniformHandle, 0)
+
+        GLES20.glUniformMatrix4fv(
+            mViewProjectionMatrixHandle,
+            1,
+            false,
+            mvpMatrix,
+            0)
+
+        GLES20.glUniformMatrix4fv(
+            mTextureTransformHandle,
+            1,
+            false,
+            mTextureTransform.apply {
+                textureSource.getTransformMatrix(this)
+            },
+            0
+        )
+
+        GLES20.glVertexAttribPointer(
+            mQuadPositionHandle,
+            CoordsPerVertex,
+            GLES20.GL_FLOAT,
+            false,
+            VertexStride,
+            configureQuad(width, height)
+        )
+
+        GLES20.glVertexAttribPointer(
+            mTexPositionHandle,
+            CoordsPerVertex,
+            GLES20.GL_FLOAT,
+            false,
+            VertexStride,
+            TextureCoordinatesBuffer
+        )
+
+        GLES20.glEnableVertexAttribArray(mQuadPositionHandle)
+        GLES20.glEnableVertexAttribArray(mTexPositionHandle)
+
+        GLES20.glDrawElements(
+            GLES20.GL_TRIANGLES,
+            DrawOrder.size,
+            GLES20.GL_UNSIGNED_SHORT,
+            DrawOrderBuffer
+        )
+
+        GLES20.glDisableVertexAttribArray(mQuadPositionHandle)
+        GLES20.glDisableVertexAttribArray(mTexPositionHandle)
+    }
+
+    companion object {
+
+        private val TAG = "TextureRenderer"
+
+        internal const val uVPMatrix = "uVPMatrix"
+        internal const val tVPMatrix = "tVPMatrix"
+        internal const val aPosition = "aPosition"
+        internal const val aTexCoord = "aTexCoord"
+
+        private const val vTexCoord = "vTexCoord"
+        internal const val uTexture = "uTexture"
+
+        internal const val VertexShader =
+            """
+            uniform mat4 $uVPMatrix;
+            uniform mat4 $tVPMatrix;
+            attribute vec4 $aPosition;
+            attribute vec2 $aTexCoord;
+            varying vec2 $vTexCoord;
+
+            void main(void)
+            {
+                gl_Position = $uVPMatrix * $aPosition;
+                $vTexCoord = vec2($tVPMatrix * vec4($aTexCoord.x, 1.0 - $aTexCoord.y, 1.0, 1.0));
+            }
+            """
+
+        internal const val FragmentShader =
+            """
+            #extension GL_OES_EGL_image_external : require
+            precision highp float;
+
+            uniform samplerExternalOES $uTexture;
+
+            varying vec2 $vTexCoord;
+
+            void main(void){
+                gl_FragColor = texture2D($uTexture, $vTexCoord);
+            }
+            """
+
+        internal const val CoordsPerVertex = 2
+        internal const val VertexStride = 4 * CoordsPerVertex
+
+        private val TextureCoordinates = floatArrayOf(
+            // x,    y
+            0.0f, 1.0f, // top left
+            0.0f, 0.0f, // bottom left
+            1.0f, 0.0f, // bottom right
+            1.0f, 1.0f, // top right
+        )
+
+        /**
+         * FloatBuffer used to specify the texture coordinates
+         */
+        private val TextureCoordinatesBuffer: FloatBuffer =
+            ByteBuffer.allocateDirect(TextureCoordinates.size * 4).run {
+                order(ByteOrder.nativeOrder())
+                asFloatBuffer().apply {
+                    put(TextureCoordinates)
+                    position(0)
+                }
+            }
+
+        private val DrawOrder = shortArrayOf(0, 1, 2, 0, 2, 3)
+
+        /**
+         * Convert short array to short buffer
+         */
+        private val DrawOrderBuffer: ShortBuffer =
+            ByteBuffer.allocateDirect(DrawOrder.size * 2).run {
+                order(ByteOrder.nativeOrder())
+                asShortBuffer().apply {
+                    put(DrawOrder)
+                    position(0)
+                }
+            }
+
+        fun checkError(msg: String) {
+            val error = GLES20.glGetError()
+            if (error != GLES20.GL_NO_ERROR) {
+                Log.v(TAG, "GLError $msg: $error")
+            }
+        }
+
+        internal fun loadShader(type: Int, shaderCode: String): Int =
+            GLES20.glCreateShader(type).also { shader ->
+                GLES20.glShaderSource(shader, shaderCode)
+                GLES20.glCompileShader(shader)
+            }
+    }
+}
\ No newline at end of file
diff --git a/gridlayout/OWNERS b/gridlayout/OWNERS
index c352bd1..235f0c3 100644
--- a/gridlayout/OWNERS
+++ b/gridlayout/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 461201
 alanv@google.com
+aelias@google.com
+ryanmentley@google.com
diff --git a/health/health-services-client/OWNERS b/health/health-services-client/OWNERS
index 7079ccb..ff2eed8 100644
--- a/health/health-services-client/OWNERS
+++ b/health/health-services-client/OWNERS
@@ -1,5 +1,5 @@
 # Bug component: 1126127
-aakanksha@google.com
 jlannin@google.com
 shobana@google.com
 smskelley@google.com
+sutterlin@google.com
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
index 4b3f682..2ab4db1 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/Availability.kt
@@ -26,7 +26,6 @@
 public interface Availability {
     public val id: Int
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public fun toProto(): DataProto.Availability =
         DataProto.Availability.newBuilder()
@@ -34,7 +33,6 @@
             .build()
 
     public companion object {
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
         public fun fromProto(proto: DataProto.Availability): Availability =
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
index 99291bf..ed977cd 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ComparisonType.kt
@@ -39,7 +39,6 @@
 
     override fun toString(): String = name
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     internal fun toProto(): DataProto.ComparisonType =
         when (this) {
@@ -81,7 +80,6 @@
         public val VALUES: List<ComparisonType> =
             listOf(GREATER_THAN, GREATER_THAN_OR_EQUAL, LESS_THAN, LESS_THAN_OR_EQUAL)
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
         internal fun fromProto(proto: DataProto.ComparisonType): ComparisonType =
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeAvailability.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeAvailability.kt
index bbbf76c..cc8e023 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeAvailability.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/DataTypeAvailability.kt
@@ -39,7 +39,6 @@
 
     override fun hashCode(): Int = id
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public override fun toProto(): DataProto.Availability =
         DataProto.Availability.newBuilder()
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEndReason.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEndReason.kt
index 8003c72..b0ac3ad 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEndReason.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseEndReason.kt
@@ -17,6 +17,7 @@
 package androidx.health.services.client.data
 
 import androidx.annotation.IntDef
+import androidx.annotation.RestrictTo
 import androidx.health.services.client.proto.DataProto
 import androidx.health.services.client.ExerciseClient
 import kotlin.annotation.AnnotationRetention.SOURCE
@@ -24,7 +25,6 @@
 /**
  * The reason why an exercise has been ended for [ExerciseState] used in [ExerciseStateInfo].
  *
- * @hide
  */
 @Retention(SOURCE)
 @IntDef(
@@ -36,6 +36,7 @@
     ExerciseEndReason.AUTO_END_SUPERSEDED,
     ExerciseEndReason.AUTO_END_PREPARE_EXPIRED
 )
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public annotation class ExerciseEndReason {
 
     public companion object {
@@ -86,7 +87,6 @@
                 else -> DataProto.ExerciseEndReason.EXERCISE_END_REASON_UNKNOWN
             }
 
-        /** @hide */
         @ExerciseEndReason
         internal fun fromProto(proto: DataProto.ExerciseEndReason): Int =
             when (proto) {
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
index ab28fd9..0cd2a4d 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseGoalType.kt
@@ -35,7 +35,6 @@
 
     override fun hashCode(): Int = id
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     internal fun toProto(): DataProto.ExerciseGoalType =
         DataProto.ExerciseGoalType.forNumber(id) ?: EXERCISE_GOAL_TYPE_UNKNOWN
@@ -59,7 +58,6 @@
         @JvmStatic
         public fun fromId(id: Int): ExerciseGoalType? = VALUES.firstOrNull { it.id == id }
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
         internal fun fromProto(proto: DataProto.ExerciseGoalType): ExerciseGoalType? =
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
index ef7649f..40ead51 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseState.kt
@@ -66,7 +66,6 @@
 
     override fun hashCode(): Int = id
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     internal fun toProto(): DataProto.ExerciseState =
         DataProto.ExerciseState.forNumber(id) ?: DataProto.ExerciseState.EXERCISE_STATE_UNKNOWN
@@ -255,7 +254,6 @@
         @JvmStatic
         public fun fromId(id: Int): ExerciseState? = VALUES.firstOrNull { it.id == id }
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmStatic
         public fun fromProto(proto: DataProto.ExerciseState): ExerciseState? = fromId(proto.number)
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseStateInfo.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseStateInfo.kt
index 28408bf..64bd5b5 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseStateInfo.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseStateInfo.kt
@@ -72,7 +72,6 @@
         /**
          * Gets the [ExerciseEndReason] from the current [ExerciseState].
          *
-         * @hide
          */
         @ExerciseEndReason
         internal fun getEndReasonFromState(exerciseState: ExerciseState): Int =
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
index 7665eb6..e02b7f8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseTrackedStatus.kt
@@ -24,7 +24,6 @@
 /**
  * Status representing if an exercise is being tracked and which app owns the exercise.
  *
- * @hide
  */
 @Retention(AnnotationRetention.SOURCE)
 @IntDef(
@@ -32,6 +31,7 @@
     ExerciseTrackedStatus.OWNED_EXERCISE_IN_PROGRESS,
     ExerciseTrackedStatus.NO_EXERCISE_IN_PROGRESS
 )
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public annotation class ExerciseTrackedStatus {
 
     public companion object {
@@ -44,13 +44,11 @@
         /** There is not currently any exercise in progress owned by any app. */
         public const val NO_EXERCISE_IN_PROGRESS: Int = 3
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         internal fun @receiver:ExerciseTrackedStatus
         Int.toProto(): DataProto.ExerciseTrackedStatus =
             DataProto.ExerciseTrackedStatus.forNumber(this) ?: EXERCISE_TRACKED_STATUS_UNKNOWN
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @ExerciseTrackedStatus
         @Suppress("WrongConstant")
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
index 5f9affbe..0d2b3c3 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseType.kt
@@ -45,7 +45,6 @@
         return id
     }
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public fun toProto(): DataProto.ExerciseType =
         DataProto.ExerciseType.forNumber(id) ?: DataProto.ExerciseType.EXERCISE_TYPE_UNKNOWN
@@ -282,7 +281,6 @@
             return exerciseType ?: UNKNOWN
         }
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         public fun fromProto(proto: DataProto.ExerciseType): ExerciseType = fromId(proto.number)
     }
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
index a5b8ea1..104dd19 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ExerciseUpdate.kt
@@ -71,7 +71,6 @@
      */
     public val startTime: Instant? = null,
 ) {
-    /** @hide */
     @RestrictTo(Scope.LIBRARY)
     public constructor(
         proto: DataProto.ExerciseUpdate
@@ -123,7 +122,6 @@
         public val activeDuration: Duration,
     ) {
 
-        /** @hide */
         @RestrictTo(Scope.LIBRARY)
         internal fun toProto(): DataProto.ExerciseUpdate.ActiveDurationCheckpoint =
             DataProto.ExerciseUpdate.ActiveDurationCheckpoint.newBuilder()
@@ -153,7 +151,6 @@
         }
 
         internal companion object {
-            /** @hide */
             @RestrictTo(Scope.LIBRARY)
             internal fun fromProto(
                 proto: DataProto.ExerciseUpdate.ActiveDurationCheckpoint
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/HeartRateAccuracy.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/HeartRateAccuracy.kt
index 3756fbe..56c0aaa 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/HeartRateAccuracy.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/HeartRateAccuracy.kt
@@ -44,7 +44,6 @@
 
         override fun toString(): String = name
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         internal fun toProto(): SensorStatusProto =
             SensorStatusProto.forNumber(id) ?: SensorStatusProto.HR_ACCURACY_SENSOR_STATUS_UNKNOWN
@@ -91,7 +90,6 @@
                     ACCURACY_HIGH,
                 )
 
-            /** @hide */
             @RestrictTo(RestrictTo.Scope.LIBRARY)
             public fun fromProto(proto: SensorStatusProto): SensorStatus =
                 VALUES.firstOrNull { it.id == proto.number } ?: UNKNOWN
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/LocationAvailability.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/LocationAvailability.kt
index 55c7bda..a9370f0a 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/LocationAvailability.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/LocationAvailability.kt
@@ -39,7 +39,6 @@
 
     override fun hashCode(): Int = id
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public override fun toProto(): DataProto.Availability =
         DataProto.Availability.newBuilder()
@@ -83,7 +82,6 @@
         @JvmStatic
         public fun fromId(id: Int): LocationAvailability? = VALUES.firstOrNull { it.id == id }
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         internal fun fromProto(proto: LocationAvailabilityProto): LocationAvailability =
             fromId(proto.number) ?: UNKNOWN
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveGoal.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveGoal.kt
index 72c8603..bfb1d9a 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveGoal.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/PassiveGoal.kt
@@ -74,7 +74,6 @@
     /**
      * The frequency at which passive goals should be triggered.
      *
-     * @hide
      */
     @Retention(AnnotationRetention.SOURCE)
     @IntDef(
@@ -96,14 +95,12 @@
              */
             const val REPEATED: Int = 2
 
-            /** @hide */
             @RestrictTo(RestrictTo.Scope.LIBRARY)
             internal fun @receiver:TriggerFrequency
             Int.toProto(): PassiveGoalProto.TriggerFrequency =
                 PassiveGoalProto.TriggerFrequency.forNumber(this)
                     ?: PassiveGoalProto.TriggerFrequency.TRIGGER_FREQUENCY_UNKNOWN
 
-            /** @hide */
             @RestrictTo(RestrictTo.Scope.LIBRARY)
             @TriggerFrequency
             @Suppress("WrongConstant")
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
index 8ef1bea..10274fc8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
@@ -28,7 +28,6 @@
  * Provided [proto] represents everything important to subclasses, they need not implement [equals]
  * and [hashCode].
  *
- * @hide
  */
 @Suppress("ParcelCreator", "ParcelNotFinal")
 @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -68,7 +67,6 @@
         /**
          * Constructs and returns a [Creator] based on the provided [parser] accepting a [ByteArray]
          * .
-         * @hide
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         public inline fun <reified U : ProtoParcelable<*>> newCreator(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
index cc049c5..5df182a 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/UserActivityState.kt
@@ -34,7 +34,6 @@
 
     override fun toString(): String = name
 
-    /** @hide */
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     internal fun toProto(): UserActivityStateProto =
         UserActivityStateProto.forNumber(id) ?: UserActivityStateProto.USER_ACTIVITY_STATE_UNKNOWN
@@ -63,7 +62,6 @@
         public val USER_ACTIVITY_ASLEEP: UserActivityState =
             UserActivityState(3, "USER_ACTIVITY_ASLEEP")
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         @JvmField
         public val VALUES: List<UserActivityState> =
@@ -74,7 +72,6 @@
                 USER_ACTIVITY_ASLEEP,
             )
 
-        /** @hide */
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         public fun fromProto(proto: UserActivityStateProto): UserActivityState =
             VALUES.firstOrNull { it.id == proto.number } ?: USER_ACTIVITY_UNKNOWN
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/IpcConstants.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/IpcConstants.kt
index 48fae70..a82a2fe 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/IpcConstants.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/IpcConstants.kt
@@ -1,10 +1,12 @@
 package androidx.health.services.client.impl
 
+import androidx.annotation.RestrictTo
+
 /**
  * Collection of constants used for IPC.
  *
- * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public object IpcConstants {
     public const val SERVICE_PACKAGE_NAME: String = "com.google.android.wearable.healthservices"
 
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
index 18f97db..6e674e16 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/MeasureCallbackStub.kt
@@ -36,7 +36,6 @@
 /**
  * A stub implementation for IMeasureCallback.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class MeasureCallbackStub
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
index bfd3d63..faa7b30 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -55,7 +55,6 @@
 /**
  * [ExerciseClient] implementation that is backed by Health Services.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal class ServiceBackedExerciseClient(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
index f7f7f76..7ebf6f7 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedMeasureClient.kt
@@ -44,7 +44,6 @@
 /**
  * [MeasureClient] implementation that is backed by Health Services.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
index 391922f..f06ddb6 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedPassiveMonitoringClient.kt
@@ -47,7 +47,6 @@
 /**
  * [PassiveMonitoringClient] implementation that is backed by Health Services.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/MeasureCallbackEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/MeasureCallbackEvent.kt
index 30c1849..d3d42a0 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/MeasureCallbackEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/MeasureCallbackEvent.kt
@@ -10,7 +10,6 @@
 /**
  * An event representing a `MeasureCallback` invocation.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class MeasureCallbackEvent(public override val proto: ListenerProto) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveCallbackEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveCallbackEvent.kt
index 337dcee..5ae88fc 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveCallbackEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveCallbackEvent.kt
@@ -9,7 +9,6 @@
 /**
  * An event representing a `PassiveMonitoringCallback` invocation.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class PassiveCallbackEvent(public override val proto: EventProto) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveListenerEvent.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveListenerEvent.kt
index cc216a8..5a01c66 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveListenerEvent.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/event/PassiveListenerEvent.kt
@@ -30,7 +30,6 @@
 /**
  * An event representing a [PassiveListenerCallback] or [PassiveListenerService] invocation.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal class PassiveListenerEvent(public override val proto: EventProto) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
index 4d449e9..b99e192 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/ExerciseInfoCallback.kt
@@ -26,7 +26,6 @@
 /**
  * A callback for ipc invocations dealing with [ExerciseInfo].
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExerciseInfoCallback(private val resultFuture: SettableFuture<ExerciseInfo>) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/HsConnectionManager.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/HsConnectionManager.kt
index 7c19ca2..d1439d2 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/HsConnectionManager.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/HsConnectionManager.kt
@@ -28,7 +28,6 @@
 /**
  * Utility to return an instance of connection manager.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public object HsConnectionManager {
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
index 8ba8279..d0163f8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/internal/StatusCallback.kt
@@ -25,7 +25,6 @@
 /**
  * A generic callback for ipc invocations.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal open class StatusCallback(private val resultFuture: SettableFuture<Void?>) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
index 5219307..996d032 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
@@ -24,7 +24,6 @@
 /**
  * Request for enabling/disabling auto pause/resume.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class AutoPauseAndResumeConfigRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt
index 6179e7b..085363b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/BatchingModeConfigRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for updating batching mode of an exercise.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class BatchingModeConfigRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
index 875a79b..3477090 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
@@ -24,7 +24,6 @@
 /**
  * Request for capabilities.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class CapabilitiesRequest(public val packageName: String) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
index 4838af7..664a955 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/ExerciseGoalRequest.kt
@@ -24,7 +24,6 @@
 /**
  * Request for adding a [ExerciseGoal] to an exercise.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public data class ExerciseGoalRequest(val packageName: String, val exerciseGoal: ExerciseGoal<*>) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
index 86c53bfd..a9ee606 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
@@ -24,7 +24,6 @@
 /**
  * Request to flush data metrics.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class FlushRequest(public val packageName: String) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
index 4cc8131..6fd3e84 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for measure registration.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class MeasureRegistrationRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
index 6c07a1f..e1027d7 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for measure unregistration.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class MeasureUnregistrationRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
index 40303af..92062da 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for background registration.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal class PassiveListenerCallbackRegistrationRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
index b280b85..d162f2d 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for background registration.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 internal class PassiveListenerServiceRegistrationRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
index 42a659c..def39f8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for preparing for an exercise.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class PrepareExerciseRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
index 617ce951..ea36ce4 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for starting an exercise.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class StartExerciseRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
index 8416212..596413b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/UpdateExerciseTypeConfigRequest.kt
@@ -25,7 +25,6 @@
 /**
  * Request for updating exercise type configuration in an [ExerciseTypeConfig].
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 class UpdateExerciseTypeConfigRequest(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
index c7ce7be..82910bc 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
@@ -26,7 +26,6 @@
  * Response containing the [ExerciseCapabilities] of the Health Services exercise client on the
  * device.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExerciseCapabilitiesResponse(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
index b814697..2a0936f8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
@@ -25,7 +25,6 @@
 /**
  * Response containing [ExerciseInfo] when changed.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExerciseInfoResponse(public val exerciseInfo: ExerciseInfo) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
index c8e24f8..8621ad2 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
@@ -25,7 +25,6 @@
 /**
  * Response containing [ExerciseLapSummary] when it's updated.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExerciseLapSummaryResponse(public val exerciseLapSummary: ExerciseLapSummary) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
index 377053e..da1817b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
@@ -25,7 +25,6 @@
 /**
  * Response containing [ExerciseUpdate] when it's updated.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class ExerciseUpdateResponse(public val exerciseUpdate: ExerciseUpdate) :
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
index 600f26e..e8496b7 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
@@ -24,12 +24,10 @@
 /**
  * Response containing a [HealthEvent].
  *
- * @hide
  */
 internal class HealthEventResponse(public val healthEvent: HealthEvent) :
     ProtoParcelable<ResponsesProto.HealthEventResponse>() {
 
-    /** @hide */
     public constructor(
         proto: ResponsesProto.HealthEventResponse
     ) : this(HealthEvent(proto.healthEvent))
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
index c715092..245b7b0 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
@@ -25,7 +25,6 @@
 /**
  * Response containing the [MeasureCapabilities] of the device.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class MeasureCapabilitiesResponse(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
index dd38942..e2164f2 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
@@ -25,7 +25,6 @@
 /**
  * Response containing the [PassiveMonitoringCapabilities] of the device.
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class PassiveMonitoringCapabilitiesResponse(
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
index 186b9c4..013149e 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
@@ -25,13 +25,11 @@
 /**
  * Response containing an achieved [PassiveGoal].
  *
- * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY)
 public class PassiveMonitoringGoalResponse(public val passiveGoal: PassiveGoal) :
     ProtoParcelable<ResponsesProto.PassiveMonitoringGoalResponse>() {
 
-    /** @hide */
     public constructor(
         proto: ResponsesProto.PassiveMonitoringGoalResponse
     ) : this(PassiveGoal(proto.goal))
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
index 0deaf87..27f9b603 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
@@ -17,6 +17,7 @@
 package androidx.health.services.client.impl.response
 
 import android.os.Parcelable
+import androidx.annotation.RestrictTo
 import androidx.health.services.client.data.PassiveMonitoringUpdate
 import androidx.health.services.client.data.ProtoParcelable
 import androidx.health.services.client.proto.ResponsesProto
@@ -24,13 +25,12 @@
 /**
  * Response containing [PassiveMonitoringUpdate].
  *
- * @hide
  */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
 public class PassiveMonitoringUpdateResponse(
     public val passiveMonitoringUpdate: PassiveMonitoringUpdate
 ) : ProtoParcelable<ResponsesProto.PassiveMonitoringUpdateResponse>() {
 
-    /** @hide */
     public constructor(
         proto: ResponsesProto.PassiveMonitoringUpdateResponse
     ) : this(PassiveMonitoringUpdate(proto.update))
diff --git a/input/input-motionprediction/api/1.0.0-beta01.txt b/input/input-motionprediction/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/1.0.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/api/current.txt b/input/input-motionprediction/api/current.txt
index 6611119..b0eef8e 100644
--- a/input/input-motionprediction/api/current.txt
+++ b/input/input-motionprediction/api/current.txt
@@ -1,8 +1,7 @@
 // Signature format: 4.0
 package androidx.input.motionprediction {
 
-  public interface MotionEventPredictor extends java.lang.AutoCloseable {
-    method public void close();
+  public interface MotionEventPredictor {
     method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
     method public android.view.MotionEvent? predict();
     method public void record(android.view.MotionEvent);
diff --git a/input/input-motionprediction/api/public_plus_experimental_1.0.0-beta01.txt b/input/input-motionprediction/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/api/public_plus_experimental_current.txt b/input/input-motionprediction/api/public_plus_experimental_current.txt
index 6611119..b0eef8e 100644
--- a/input/input-motionprediction/api/public_plus_experimental_current.txt
+++ b/input/input-motionprediction/api/public_plus_experimental_current.txt
@@ -1,8 +1,7 @@
 // Signature format: 4.0
 package androidx.input.motionprediction {
 
-  public interface MotionEventPredictor extends java.lang.AutoCloseable {
-    method public void close();
+  public interface MotionEventPredictor {
     method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
     method public android.view.MotionEvent? predict();
     method public void record(android.view.MotionEvent);
diff --git a/input/input-motionprediction/api/res-1.0.0-beta01.txt b/input/input-motionprediction/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/input/input-motionprediction/api/res-1.0.0-beta01.txt
diff --git a/input/input-motionprediction/api/restricted_1.0.0-beta01.txt b/input/input-motionprediction/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..b0eef8e
--- /dev/null
+++ b/input/input-motionprediction/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,11 @@
+// Signature format: 4.0
+package androidx.input.motionprediction {
+
+  public interface MotionEventPredictor {
+    method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
+    method public android.view.MotionEvent? predict();
+    method public void record(android.view.MotionEvent);
+  }
+
+}
+
diff --git a/input/input-motionprediction/api/restricted_current.txt b/input/input-motionprediction/api/restricted_current.txt
index 6611119..b0eef8e 100644
--- a/input/input-motionprediction/api/restricted_current.txt
+++ b/input/input-motionprediction/api/restricted_current.txt
@@ -1,8 +1,7 @@
 // Signature format: 4.0
 package androidx.input.motionprediction {
 
-  public interface MotionEventPredictor extends java.lang.AutoCloseable {
-    method public void close();
+  public interface MotionEventPredictor {
     method public static androidx.input.motionprediction.MotionEventPredictor newInstance(android.view.View);
     method public android.view.MotionEvent? predict();
     method public void record(android.view.MotionEvent);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
index d51dbf4..e976419 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/MotionEventPredictor.java
@@ -30,10 +30,9 @@
  * {@link #newInstance(android.view.View)}; put the motion events you receive into it with
  * {@link #record(android.view.MotionEvent)}, and call {@link #predict()} to retrieve the
  * predicted  {@link android.view.MotionEvent} that would occur at the moment the next frame is
- * rendered on the display. Once no more predictions are needed, call {@link #close()} to stop it
- * and clean up resources.
+ * rendered on the display.
  */
-public interface MotionEventPredictor extends AutoCloseable {
+public interface MotionEventPredictor {
     /**
      * Record a user's movement to the predictor. You should call this for every
      * {@link android.view.MotionEvent} that is received by the associated
@@ -53,18 +52,11 @@
     MotionEvent predict();
 
     /**
-     * Notify the predictor that no more predictions are needed. Any subsequent call to
-     * {@link #predict()} will return null.
-     */
-    @Override
-    void close();
-
-    /**
      * Create a new motion predictor associated to a specific {@link android.view.View}
      * @param view the view to associated to this predictor
      * @return the new predictor instance
      */
     static @NonNull MotionEventPredictor newInstance(@NonNull View view) {
-        return new KalmanMotionEventPredictor();
+        return new KalmanMotionEventPredictor(view.getContext());
     }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
index 91bec15..1b7d73b 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanMotionEventPredictor.java
@@ -18,46 +18,36 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 
+import android.content.Context;
 import android.view.MotionEvent;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.input.motionprediction.MotionEventPredictor;
+import androidx.input.motionprediction.utils.PredictionEstimator;
 
 /**
  */
 @RestrictTo(LIBRARY)
 public class KalmanMotionEventPredictor implements MotionEventPredictor {
-    private MultiPointerPredictor mMultiPointerPredictor = new MultiPointerPredictor();
+    private final MultiPointerPredictor mMultiPointerPredictor = new MultiPointerPredictor();
+    private final PredictionEstimator mPredictionEstimator;
 
-    public KalmanMotionEventPredictor() {
-        // 1 may seem arbitrary, but this basically tells the predictor to
-        // just predict the next MotionEvent.
-        // This will need to change as we want to build a prediction depending
-        // on the expected time that the frame will arrive to the screen.
-        mMultiPointerPredictor.setPredictionTarget(1);
+    public KalmanMotionEventPredictor(@NonNull Context context) {
+        mPredictionEstimator = new PredictionEstimator(context);
     }
 
     @Override
     public void record(@NonNull MotionEvent event) {
-        if (mMultiPointerPredictor == null) {
-            return;
-        }
+        mPredictionEstimator.record(event);
         mMultiPointerPredictor.onTouchEvent(event);
     }
 
     @Nullable
     @Override
     public MotionEvent predict() {
-        if (mMultiPointerPredictor == null) {
-            return null;
-        }
-        return mMultiPointerPredictor.predict();
-    }
-
-    @Override
-    public void close() {
-        mMultiPointerPredictor = null;
+        final int predictionTimeDelta = mPredictionEstimator.estimate();
+        return mMultiPointerPredictor.predict(predictionTimeDelta);
     }
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanPredictor.java
index 87a35e6..3a8ff60 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/KalmanPredictor.java
@@ -30,13 +30,6 @@
  */
 @RestrictTo(LIBRARY)
 public interface KalmanPredictor {
-
-    /** Gets the current prediction target */
-    int getPredictionTarget();
-
-    /** Sets the current prediction target */
-    void setPredictionTarget(int predictionTargetMillis);
-
     /** Sets the report rate */
     void setReportRate(int reportRateMs);
 
@@ -45,5 +38,5 @@
 
     /** @return null if not possible to make a prediction. */
     @Nullable
-    MotionEvent predict();
+    MotionEvent predict(int predictionTargetMillis);
 }
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
index cb6c3a9..3f02764 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/MultiPointerPredictor.java
@@ -36,29 +36,11 @@
     private static final boolean DEBUG_PREDICTION = Log.isLoggable(TAG, Log.DEBUG);
 
     private final SparseArray<SinglePointerPredictor> mPredictorMap = new SparseArray<>();
-    private int mPredictionTargetMs = 0;
     private int mReportRateMs = 0;
 
     public MultiPointerPredictor() {}
 
     @Override
-    public int getPredictionTarget() {
-        return mPredictionTargetMs;
-    }
-
-    @Override
-    public void setPredictionTarget(int predictionTargetMillis) {
-        if (predictionTargetMillis < 0) {
-            predictionTargetMillis = 0;
-        }
-        mPredictionTargetMs = predictionTargetMillis;
-
-        for (int i = 0; i < mPredictorMap.size(); ++i) {
-            mPredictorMap.valueAt(i).setPredictionTarget(predictionTargetMillis);
-        }
-    }
-
-    @Override
     public void setReportRate(int reportRateMs) {
         if (reportRateMs <= 0) {
             throw new IllegalArgumentException(
@@ -78,7 +60,6 @@
         int pointerId = event.getPointerId(actionIndex);
         if (action == MotionEvent.ACTION_DOWN || action == MotionEvent.ACTION_POINTER_DOWN) {
             SinglePointerPredictor predictor = new SinglePointerPredictor();
-            predictor.setPredictionTarget(mPredictionTargetMs);
             if (mReportRateMs > 0) {
                 predictor.setReportRate(mReportRateMs);
             }
@@ -113,7 +94,7 @@
 
     /** Support eventTime */
     @Override
-    public @Nullable MotionEvent predict() {
+    public @Nullable MotionEvent predict(int predictionTargetMs) {
         final int pointerCount = mPredictorMap.size();
         // Shortcut for likely case where only zero or one pointer is on the screen
         // this logic exists only to make sure logic when one pointer is on screen then
@@ -128,7 +109,7 @@
         }
         if (pointerCount == 1) {
             SinglePointerPredictor predictor = mPredictorMap.valueAt(0);
-            MotionEvent predictedEv = predictor.predict();
+            MotionEvent predictedEv = predictor.predict(predictionTargetMs);
             if (DEBUG_PREDICTION) {
                 Log.d(TAG, "predict() -> MotionEvent: " + predictedEv);
             }
@@ -141,7 +122,7 @@
         for (int i = 0; i < pointerCount; ++i) {
             pointerIds[i] = mPredictorMap.keyAt(i);
             SinglePointerPredictor predictor = mPredictorMap.valueAt(i);
-            singlePointerEvents[i] = predictor.predict();
+            singlePointerEvents[i] = predictor.predict(predictionTargetMs);
             // If predictor consumer expect more sample, generate sample where position and
             // pressure are constant
             singlePointerEvents[i] = predictor.appendPredictedEvent(singlePointerEvents[i]);
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
index 0ba5bc6..74dfc7c 100644
--- a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/kalman/SinglePointerPredictor.java
@@ -60,9 +60,6 @@
     // Minimum number of Kalman filter samples needed for predicting the next point
     private static final int MIN_KALMAN_FILTER_ITERATIONS = 4;
 
-    // Target time in milliseconds to predict.
-    private float mPredictionTargetMs = 0.0f;
-
     // The Kalman filter is tuned to smooth noise while maintaining fast reaction to direction
     // changes. The stronger the filter, the smoother the prediction result will be, at the
     // cost of possible prediction errors.
@@ -148,23 +145,6 @@
     }
 
     @Override
-    public int getPredictionTarget() {
-        // Prediction target should always be an int, so no precision lost in the cast
-        return (int) mPredictionTargetMs;
-    }
-
-    @Override
-    public void setPredictionTarget(int predictionTargetMillis) {
-        if (predictionTargetMillis < 0) {
-            predictionTargetMillis = 0;
-        }
-        mPredictionTargetMs = predictionTargetMillis;
-        if (mReportRates == null) {
-            mExpectedPredictionSampleSize = (int) Math.ceil(mPredictionTargetMs / mReportRateMs);
-        }
-    }
-
-    @Override
     public void setReportRate(int reportRateMs) {
         if (reportRateMs <= 0) {
             throw new IllegalArgumentException(
@@ -172,8 +152,6 @@
         }
         mReportRateMs = reportRateMs;
         mReportRates = null;
-
-        mExpectedPredictionSampleSize = (int) Math.ceil(mPredictionTargetMs / mReportRateMs);
     }
 
     @Override
@@ -205,7 +183,11 @@
     }
 
     @Override
-    public @Nullable MotionEvent predict() {
+    public @Nullable MotionEvent predict(int predictionTargetMs) {
+        if (mReportRates == null) {
+            mExpectedPredictionSampleSize = (int) Math.ceil(predictionTargetMs / mReportRateMs);
+        }
+
         if (mExpectedPredictionSampleSize == -1
                 && mKalman.getNumIterations() < MIN_KALMAN_FILTER_ITERATIONS) {
             return null;
@@ -236,7 +218,7 @@
 
         // Project physical state of the pen into the future.
         int predictionTargetInSamples =
-                (int) Math.ceil(mPredictionTargetMs / mReportRateMs * confidenceFactor);
+                (int) Math.ceil(predictionTargetMs / mReportRateMs * confidenceFactor);
 
         // Normally this should always be false as confidenceFactor should be less than 1.0
         if (mExpectedPredictionSampleSize != -1
diff --git a/input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java
new file mode 100644
index 0000000..3ed9a1b
--- /dev/null
+++ b/input/input-motionprediction/src/main/java/androidx/input/motionprediction/utils/PredictionEstimator.java
@@ -0,0 +1,140 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.input.motionprediction.utils;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.content.Context;
+import android.os.Build;
+import android.os.SystemClock;
+import android.view.Display;
+import android.view.MotionEvent;
+import android.view.WindowManager;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+/**
+ */
+@SuppressWarnings("deprecation")
+@RestrictTo(LIBRARY)
+public class PredictionEstimator {
+    private static final int MAX_PREDICTION_MS = 32;
+    private static final int LEGACY_FRAME_TIME_MS = 16;
+    private static final int MS_IN_A_SECOND = 1000;
+
+    private long mLastEventTime = -1;
+    private final float mFrameTimeMs;
+
+    public PredictionEstimator(@NonNull Context context) {
+        mFrameTimeMs = getFastestFrameTimeMs(context);
+    }
+
+    /** Records the needed information from the event to calculate the prediction. */
+    public void record(@NonNull MotionEvent event) {
+        mLastEventTime = event.getEventTime();
+    }
+
+    /** Return the estimated amount of prediction needed. */
+    public int estimate() {
+        if (mLastEventTime <= 0) {
+            return (int) mFrameTimeMs;
+        }
+        // The amount of prediction is the estimated amount of time it will take to land the
+        // information on the screen from now, plus the time since the last recorded MotionEvent
+        int estimatedMs = (int) (SystemClock.uptimeMillis() - mLastEventTime + mFrameTimeMs);
+        return Math.min(MAX_PREDICTION_MS, estimatedMs);
+    }
+
+    private Display getDisplayForContext(Context context) {
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
+            return Api30Impl.getDisplayForContext(context);
+        }
+        return ((WindowManager) context.getSystemService(Context.WINDOW_SERVICE))
+                .getDefaultDisplay();
+    }
+
+    private float getFastestFrameTimeMs(Context context) {
+        Display defaultDisplay = getDisplayForContext(context);
+
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
+            return Api23Impl.getFastestFrameTimeMs(defaultDisplay);
+        } else if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+            return Api21Impl.getFastestFrameTimeMs(defaultDisplay);
+        } else {
+            return LEGACY_FRAME_TIME_MS;
+        }
+    }
+
+    @SuppressWarnings("deprecation")
+    @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+    static class Api21Impl {
+        private Api21Impl() {
+            // Not instantiable
+        }
+
+        @DoNotInline
+        static float getFastestFrameTimeMs(Display display) {
+            float[] refreshRates = display.getSupportedRefreshRates();
+            float largestRefreshRate = refreshRates[0];
+
+            for (int c = 1; c < refreshRates.length; c++) {
+                if (refreshRates[c] > largestRefreshRate) {
+                    largestRefreshRate = refreshRates[c];
+                }
+            }
+
+            return MS_IN_A_SECOND / largestRefreshRate;
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.M)
+    static class Api23Impl {
+        private Api23Impl() {
+            // Not instantiable
+        }
+
+        @DoNotInline
+        static float getFastestFrameTimeMs(Display display) {
+            Display.Mode[] displayModes = display.getSupportedModes();
+            float largestRefreshRate = displayModes[0].getRefreshRate();
+
+            for (int c = 1; c < displayModes.length; c++) {
+                float currentRefreshRate = displayModes[c].getRefreshRate();
+                if (currentRefreshRate > largestRefreshRate) {
+                    largestRefreshRate = currentRefreshRate;
+                }
+            }
+
+            return MS_IN_A_SECOND / largestRefreshRate;
+        }
+    }
+
+    @RequiresApi(Build.VERSION_CODES.R)
+    static class Api30Impl {
+        private Api30Impl() {
+            // Not instantiable
+        }
+
+        @DoNotInline
+        static Display getDisplayForContext(Context context) {
+            return context.getDisplay();
+        }
+    }
+}
diff --git a/inspection/OWNERS b/inspection/OWNERS
index 85c92a7..d238af3 100644
--- a/inspection/OWNERS
+++ b/inspection/OWNERS
@@ -1,3 +1,3 @@
+# Bug component: 1333602
 sergeyv@google.com
-davidherman@google.com
 yboyar@google.com
\ No newline at end of file
diff --git a/libraryversions.toml b/libraryversions.toml
index 1f802a0..bf78155 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -1,34 +1,31 @@
 [versions]
 ACTIVITY = "1.8.0-alpha02"
-ADS_IDENTIFIER = "1.0.0-alpha05"
+ADS_IDENTIFIER = "1.0.0-alpha06"
 ANNOTATION = "1.7.0-alpha01"
-ANNOTATION_KMP = "1.6.0-dev01"
 ANNOTATION_EXPERIMENTAL = "1.4.0-alpha01"
-ANNOTATION_EXPERIMENTAL_KMP = "1.4.0-dev01"
 APPACTIONS_INTERACTION = "1.0.0-alpha01"
 APPCOMPAT = "1.7.0-alpha03"
 APPSEARCH = "1.1.0-alpha03"
 ARCH_CORE = "2.3.0-alpha01"
 ASYNCLAYOUTINFLATER = "1.1.0-alpha02"
 AUTOFILL = "1.2.0-beta02"
-BENCHMARK = "1.2.0-alpha11"
+BENCHMARK = "1.2.0-alpha12"
 BIOMETRIC = "1.2.0-alpha06"
 BLUETOOTH = "1.0.0-alpha01"
 BROWSER = "1.6.0-alpha01"
 BUILDSRC_TESTS = "1.0.0-alpha01"
-CAMERA = "1.3.0-alpha04"
+CAMERA = "1.3.0-alpha05"
 CAMERA_PIPE = "1.0.0-alpha01"
 CARDVIEW = "1.1.0-alpha01"
 CAR_APP = "1.4.0-alpha01"
 COLLECTION = "1.3.0-alpha03"
-COLLECTION_KMP = "1.3.0-dev01"
 COMPOSE = "1.5.0-alpha01"
 COMPOSE_COMPILER = "1.4.3"
-COMPOSE_MATERIAL3 = "1.1.0-alpha08"
+COMPOSE_MATERIAL3 = "1.1.0-alpha09"
 COMPOSE_RUNTIME_TRACING = "1.0.0-alpha03"
-CONSTRAINTLAYOUT = "2.2.0-alpha08"
-CONSTRAINTLAYOUT_COMPOSE = "1.1.0-alpha08"
-CONSTRAINTLAYOUT_CORE = "1.1.0-alpha08"
+CONSTRAINTLAYOUT = "2.2.0-alpha09"
+CONSTRAINTLAYOUT_COMPOSE = "1.1.0-alpha09"
+CONSTRAINTLAYOUT_CORE = "1.1.0-alpha09"
 CONTENTPAGER = "1.1.0-alpha01"
 COORDINATORLAYOUT = "1.3.0-alpha01"
 CORE = "1.11.0-alpha01"
@@ -43,36 +40,35 @@
 CORE_ROLE = "1.2.0-alpha01"
 CORE_SPLASHSCREEN = "1.1.0-alpha01"
 CORE_UWB = "1.0.0-alpha05"
-CREDENTIALS = "1.0.0-alpha04"
+CREDENTIALS = "1.0.0-alpha05"
 CURSORADAPTER = "1.1.0-alpha01"
 CUSTOMVIEW = "1.2.0-alpha03"
 CUSTOMVIEW_POOLINGCONTAINER = "1.1.0-alpha01"
 DATASTORE = "1.1.0-alpha02"
-DATASTORE_KMP = "1.1.0-dev01"
 DOCUMENTFILE = "1.1.0-alpha02"
 DRAGANDDROP = "1.1.0-alpha01"
-DRAWERLAYOUT = "1.2.0-rc01"
+DRAWERLAYOUT = "1.3.0-alpha01"
 DYNAMICANIMATION = "1.1.0-alpha04"
 DYNAMICANIMATION_KTX = "1.0.0-alpha04"
 EMOJI = "1.2.0-alpha03"
-EMOJI2 = "1.3.0-rc01"
+EMOJI2 = "1.4.0-alpha01"
 EMOJI2_QUARANTINE = "1.0.0-alpha03"
 ENTERPRISE = "1.1.0-rc01"
 EXIFINTERFACE = "1.4.0-alpha01"
-FRAGMENT = "1.6.0-alpha07"
+FRAGMENT = "1.6.0-alpha08"
 FUTURES = "1.2.0-alpha01"
 GLANCE = "1.0.0-alpha06"
 GLANCE_TEMPLATE = "1.0.0-alpha01"
 GRAPHICS_CORE = "1.0.0-alpha03"
-GRAPHICS_SHAPES = "1.0.0-alpha01"
 GRAPHICS_FILTERS = "1.0.0-alpha01"
+GRAPHICS_SHAPES = "1.0.0-alpha01"
 GRIDLAYOUT = "1.1.0-alpha01"
 HEALTH_CONNECT = "1.0.0-alpha11"
 HEALTH_SERVICES_CLIENT = "1.0.0-beta03"
 HEIFWRITER = "1.1.0-alpha02"
 HILT = "1.1.0-alpha02"
 HILT_NAVIGATION_COMPOSE = "1.1.0-alpha02"
-INPUT_MOTIONPREDICTION = "1.0.0-alpha03"
+INPUT_MOTIONPREDICTION = "1.0.0-beta01"
 INSPECTION = "1.0.0"
 INTERPOLATOR = "1.1.0-alpha01"
 JAVASCRIPTENGINE = "1.0.0-alpha04"
@@ -90,7 +86,7 @@
 MEDIA2 = "1.3.0-alpha01"
 MEDIAROUTER = "1.4.0-beta02"
 METRICS = "1.0.0-alpha04"
-NAVIGATION = "2.6.0-alpha07"
+NAVIGATION = "2.6.0-alpha08"
 PAGING = "3.2.0-alpha05"
 PAGING_COMPOSE = "1.0.0-alpha19"
 PALETTE = "1.1.0-alpha01"
@@ -100,9 +96,9 @@
 PRIVACYSANDBOX_ADS = "1.0.0-beta01"
 PRIVACYSANDBOX_PLUGINS = "1.0.0-alpha01"
 PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha01"
-PRIVACYSANDBOX_TOOLS = "1.0.0-alpha03"
-PRIVACYSANDBOX_UI = "1.0.0-alpha01"
-PROFILEINSTALLER = "1.3.0-rc01"
+PRIVACYSANDBOX_TOOLS = "1.0.0-alpha04"
+PRIVACYSANDBOX_UI = "1.0.0-alpha02"
+PROFILEINSTALLER = "1.4.0-alpha01"
 RECOMMENDATION = "1.1.0-alpha01"
 RECYCLERVIEW = "1.4.0-alpha01"
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
@@ -130,8 +126,8 @@
 TESTSCREENSHOT = "1.0.0-alpha01"
 TEST_UIAUTOMATOR = "2.3.0-alpha03"
 TEXT = "1.0.0-alpha01"
-TRACING = "1.2.0-beta01"
-TRACING_PERFETTO = "1.0.0-alpha12"
+TRACING = "1.2.0-beta02"
+TRACING_PERFETTO = "1.0.0-alpha13"
 TRANSITION = "1.5.0-alpha01"
 TV = "1.0.0-alpha04"
 TVPROVIDER = "1.1.0-alpha02"
@@ -142,17 +138,17 @@
 VIEWPAGER = "1.1.0-alpha02"
 VIEWPAGER2 = "1.2.0-alpha01"
 WEAR = "1.3.0-alpha05"
-WEAR_COMPOSE = "1.2.0-alpha06"
+WEAR_COMPOSE = "1.2.0-alpha07"
 WEAR_COMPOSE_MATERIAL3 = "1.0.0-alpha01"
 WEAR_INPUT = "1.2.0-alpha03"
 WEAR_INPUT_TESTING = "1.2.0-alpha03"
 WEAR_ONGOING = "1.1.0-alpha01"
 WEAR_PHONE_INTERACTIONS = "1.1.0-alpha04"
-WEAR_PROTOLAYOUT = "1.0.0-alpha05"
+WEAR_PROTOLAYOUT = "1.0.0-alpha06"
 WEAR_REMOTE_INTERACTIONS = "1.1.0-alpha01"
-WEAR_TILES = "1.2.0-alpha01"
+WEAR_TILES = "1.2.0-alpha02"
 WEAR_WATCHFACE = "1.2.0-alpha07"
-WEBKIT = "1.7.0-alpha03"
+WEBKIT = "1.7.0-beta01"
 WINDOW = "1.1.0-beta01"
 WINDOW_EXTENSIONS = "1.1.0-beta01"
 WINDOW_EXTENSIONS_CORE = "1.0.0-beta01"
@@ -178,7 +174,7 @@
 CAMERA = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA" }
 CARDVIEW = { group = "androidx.cardview", atomicGroupVersion = "versions.CARDVIEW" }
 CAR_APP = { group = "androidx.car.app", atomicGroupVersion = "versions.CAR_APP" }
-COLLECTION = { group = "androidx.collection", atomicGroupVersion = "versions.COLLECTION", multiplatformGroupVersion = "versions.COLLECTION_KMP" }
+COLLECTION = { group = "androidx.collection", atomicGroupVersion = "versions.COLLECTION" }
 COMPOSE_ANIMATION = { group = "androidx.compose.animation", atomicGroupVersion = "versions.COMPOSE" }
 COMPOSE_COMPILER = { group = "androidx.compose.compiler", atomicGroupVersion = "versions.COMPOSE_COMPILER" }
 COMPOSE_DESKTOP = { group = "androidx.compose.desktop", atomicGroupVersion = "versions.COMPOSE" }
@@ -196,7 +192,7 @@
 CREDENTIALS = { group = "androidx.credentials", atomicGroupVersion = "versions.CREDENTIALS" }
 CURSORADAPTER = { group = "androidx.cursoradapter", atomicGroupVersion = "versions.CURSORADAPTER" }
 CUSTOMVIEW = { group = "androidx.customview" }
-DATASTORE = { group = "androidx.datastore", atomicGroupVersion = "versions.DATASTORE", multiplatformGroupVersion = "versions.DATASTORE_KMP" }
+DATASTORE = { group = "androidx.datastore", atomicGroupVersion = "versions.DATASTORE" }
 DOCUMENTFILE = { group = "androidx.documentfile", atomicGroupVersion = "versions.DOCUMENTFILE" }
 DRAGANDDROP = { group = "androidx.draganddrop", atomicGroupVersion = "versions.DRAGANDDROP" }
 DRAWERLAYOUT = { group = "androidx.drawerlayout", atomicGroupVersion = "versions.DRAWERLAYOUT" }
@@ -208,7 +204,7 @@
 FRAGMENT = { group = "androidx.fragment", atomicGroupVersion = "versions.FRAGMENT" }
 GLANCE = { group = "androidx.glance", atomicGroupVersion = "versions.GLANCE" }
 GLANCE_TEMPLATE = { group = "androidx.template", atomicGroupVersion = "versions.GLANCE_TEMPLATE" }
-GRAPHICS = { group = "androidx.graphics"}
+GRAPHICS = { group = "androidx.graphics" }
 GRAPHICS_FILTERS = { group = "androidx.graphics.filters", atomicGroupVersion = "versions.GRAPHICS_FILTERS" }
 GRIDLAYOUT = { group = "androidx.gridlayout", atomicGroupVersion = "versions.GRIDLAYOUT" }
 HEALTH = { group = "androidx.health" }
@@ -217,7 +213,7 @@
 HILT = { group = "androidx.hilt" }
 INPUT = { group = "androidx.input" }
 INSPECTION = { group = "androidx.inspection", atomicGroupVersion = "versions.INSPECTION" }
-INSPECTION_EXTENSIONS = { group = "androidx.inspection.extensions", atomicGroupVersion = "versions.SQLITE_INSPECTOR", overrideInclude = [ ":sqlite:sqlite-inspection"] }
+INSPECTION_EXTENSIONS = { group = "androidx.inspection.extensions", atomicGroupVersion = "versions.SQLITE_INSPECTOR", overrideInclude = [ ":sqlite:sqlite-inspection" ] }
 INTERPOLATOR = { group = "androidx.interpolator", atomicGroupVersion = "versions.INTERPOLATOR" }
 JAVASCRIPTENGINE = { group = "androidx.javascriptengine", atomicGroupVersion = "versions.JAVASCRIPTENGINE" }
 LEANBACK = { group = "androidx.leanback" }
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
index 42355cd..ec45470 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
+++ b/lifecycle/lifecycle-viewmodel-savedstate/build.gradle
@@ -32,7 +32,7 @@
 dependencies {
     api("androidx.annotation:annotation:1.0.0")
     api("androidx.core:core-ktx:1.2.0")
-    api("androidx.savedstate:savedstate:1.2.0")
+    api("androidx.savedstate:savedstate:1.2.1")
     api(projectOrArtifact(":lifecycle:lifecycle-livedata-core"))
     api(projectOrArtifact(":lifecycle:lifecycle-viewmodel"))
     api(libs.kotlinStdlib)
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/MediaDescriptionCompat.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/MediaDescriptionCompat.aidl
new file mode 100644
index 0000000..587736c
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/MediaDescriptionCompat.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2017, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media;
+@JavaOnlyStableParcelable
+parcelable MediaDescriptionCompat;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/MediaMetadataCompat.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/MediaMetadataCompat.aidl
new file mode 100644
index 0000000..d6d7965
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/MediaMetadataCompat.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media;
+@JavaOnlyStableParcelable
+parcelable MediaMetadataCompat;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/RatingCompat.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/RatingCompat.aidl
new file mode 100644
index 0000000..e08b9e1
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/RatingCompat.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media;
+@JavaOnlyStableParcelable
+parcelable RatingCompat;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl
new file mode 100644
index 0000000..628871f
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaControllerCallback.aidl
@@ -0,0 +1,49 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+/* @hide */
+interface IMediaControllerCallback {
+  oneway void onEvent(String event, in android.os.Bundle extras);
+  oneway void onSessionDestroyed();
+  oneway void onPlaybackStateChanged(in android.support.v4.media.session.PlaybackStateCompat state);
+  oneway void onMetadataChanged(in android.support.v4.media.MediaMetadataCompat metadata);
+  oneway void onQueueChanged(in List<android.support.v4.media.session.MediaSessionCompat.QueueItem> queue);
+  oneway void onQueueTitleChanged(CharSequence title);
+  oneway void onExtrasChanged(in android.os.Bundle extras);
+  oneway void onVolumeInfoChanged(in android.support.v4.media.session.ParcelableVolumeInfo info);
+  oneway void onRepeatModeChanged(int repeatMode);
+  oneway void onShuffleModeChangedRemoved(boolean enabled);
+  oneway void onCaptioningEnabledChanged(boolean enabled);
+  oneway void onShuffleModeChanged(int shuffleMode);
+  oneway void onSessionReady();
+}
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl
new file mode 100644
index 0000000..0a33361
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/IMediaSession.aidl
@@ -0,0 +1,87 @@
+/* Copyright (C) 2014 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+/* @hide */
+interface IMediaSession {
+  void sendCommand(String command, in android.os.Bundle args, in android.support.v4.media.session.MediaSessionCompat.ResultReceiverWrapper cb) = 0;
+  boolean sendMediaButton(in android.view.KeyEvent mediaButton) = 1;
+  void registerCallbackListener(in android.support.v4.media.session.IMediaControllerCallback cb) = 2;
+  void unregisterCallbackListener(in android.support.v4.media.session.IMediaControllerCallback cb) = 3;
+  boolean isTransportControlEnabled() = 4;
+  String getPackageName() = 5;
+  String getTag() = 6;
+  android.app.PendingIntent getLaunchPendingIntent() = 7;
+  long getFlags() = 8;
+  android.support.v4.media.session.ParcelableVolumeInfo getVolumeAttributes() = 9;
+  void adjustVolume(int direction, int flags, String packageName) = 10;
+  void setVolumeTo(int value, int flags, String packageName) = 11;
+  android.support.v4.media.MediaMetadataCompat getMetadata() = 26;
+  android.support.v4.media.session.PlaybackStateCompat getPlaybackState() = 27;
+  List<android.support.v4.media.session.MediaSessionCompat.QueueItem> getQueue() = 28;
+  CharSequence getQueueTitle() = 29;
+  android.os.Bundle getExtras() = 30;
+  int getRatingType() = 31;
+  boolean isCaptioningEnabled() = 44;
+  int getRepeatMode() = 36;
+  boolean isShuffleModeEnabledRemoved() = 37;
+  int getShuffleMode() = 46;
+  void addQueueItem(in android.support.v4.media.MediaDescriptionCompat description) = 40;
+  void addQueueItemAt(in android.support.v4.media.MediaDescriptionCompat description, int index) = 41;
+  void removeQueueItem(in android.support.v4.media.MediaDescriptionCompat description) = 42;
+  void removeQueueItemAt(int index) = 43;
+  android.os.Bundle getSessionInfo() = 49;
+  void prepare() = 32;
+  void prepareFromMediaId(String uri, in android.os.Bundle extras) = 33;
+  void prepareFromSearch(String string, in android.os.Bundle extras) = 34;
+  void prepareFromUri(in android.net.Uri uri, in android.os.Bundle extras) = 35;
+  void play() = 12;
+  void playFromMediaId(String uri, in android.os.Bundle extras) = 13;
+  void playFromSearch(String string, in android.os.Bundle extras) = 14;
+  void playFromUri(in android.net.Uri uri, in android.os.Bundle extras) = 15;
+  void skipToQueueItem(long id) = 16;
+  void pause() = 17;
+  void stop() = 18;
+  void next() = 19;
+  void previous() = 20;
+  void fastForward() = 21;
+  void rewind() = 22;
+  void seekTo(long pos) = 23;
+  void rate(in android.support.v4.media.RatingCompat rating) = 24;
+  void rateWithExtras(in android.support.v4.media.RatingCompat rating, in android.os.Bundle extras) = 50;
+  void setPlaybackSpeed(float speed) = 48;
+  void setCaptioningEnabled(boolean enabled) = 45;
+  void setRepeatMode(int repeatMode) = 38;
+  void setShuffleModeEnabledRemoved(boolean shuffleMode) = 39;
+  void setShuffleMode(int shuffleMode) = 47;
+  void sendCustomAction(String action, in android.os.Bundle args) = 25;
+}
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.QueueItem.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.QueueItem.aidl
new file mode 100644
index 0000000..375b6d7
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.QueueItem.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+@JavaOnlyStableParcelable
+parcelable MediaSessionCompat.QueueItem;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.ResultReceiverWrapper.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.ResultReceiverWrapper.aidl
new file mode 100644
index 0000000..62d1ec0
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.ResultReceiverWrapper.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+@JavaOnlyStableParcelable
+parcelable MediaSessionCompat.ResultReceiverWrapper;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.Token.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.Token.aidl
new file mode 100644
index 0000000..490d4d0
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/MediaSessionCompat.Token.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+@JavaOnlyStableParcelable
+parcelable MediaSessionCompat.Token;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/ParcelableVolumeInfo.aidl
new file mode 100644
index 0000000..16d3fe0
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/ParcelableVolumeInfo.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+@JavaOnlyStableParcelable
+parcelable ParcelableVolumeInfo;
diff --git a/media/media/api/aidlRelease/current/android/support/v4/media/session/PlaybackStateCompat.aidl b/media/media/api/aidlRelease/current/android/support/v4/media/session/PlaybackStateCompat.aidl
new file mode 100644
index 0000000..682e2ac
--- /dev/null
+++ b/media/media/api/aidlRelease/current/android/support/v4/media/session/PlaybackStateCompat.aidl
@@ -0,0 +1,35 @@
+/* Copyright 2014, The Android Open Source Project
+**
+** Licensed under the Apache License, Version 2.0 (the "License");
+** you may not use this file except in compliance with the License.
+** You may obtain a copy of the License at
+**
+**     http://www.apache.org/licenses/LICENSE-2.0
+**
+** Unless required by applicable law or agreed to in writing, software
+** distributed under the License is distributed on an "AS IS" BASIS,
+** WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+** See the License for the specific language governing permissions and
+** limitations under the License.
+*/
+///////////////////////////////////////////////////////////////////////////////
+// THIS FILE IS IMMUTABLE. DO NOT EDIT IN ANY CASE.                          //
+///////////////////////////////////////////////////////////////////////////////
+
+// This file is a snapshot of an AIDL file. Do not edit it manually. There are
+// two cases:
+// 1). this is a frozen version file - do not edit this in any case.
+// 2). this is a 'current' file. If you make a backwards compatible change to
+//     the interface (from the latest frozen version), the build system will
+//     prompt you to update this file with `m <name>-update-api`.
+//
+// You must not make a backward incompatible change to any AIDL file built
+// with the aidl_interface module type with versions property set. The module
+// type is used to build AIDL files in a way that they can be used across
+// independently updatable components of the system. If a device is shipped
+// with such a backward incompatible change, it has a high risk of breaking
+// later when a module using the interface is updated, e.g., Mainline modules.
+
+package android.support.v4.media.session;
+@JavaOnlyStableParcelable
+parcelable PlaybackStateCompat;
diff --git a/media/media/build.gradle b/media/media/build.gradle
index 91a6177..0787b8d 100644
--- a/media/media/build.gradle
+++ b/media/media/build.gradle
@@ -19,6 +19,7 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("androidx.stableaidl")
 }
 
 dependencies {
@@ -46,6 +47,10 @@
 
     buildTypes.all {
         consumerProguardFiles "proguard-rules.pro"
+
+        stableAidl {
+            version 1
+        }
     }
     namespace "androidx.media"
 }
diff --git a/media/media/src/main/aidl/android/support/v4/media/RatingCompat.aidl b/media/media/src/main/aidl/android/support/v4/media/RatingCompat.aidl
deleted file mode 100644
index 223fd5c..0000000
--- a/media/media/src/main/aidl/android/support/v4/media/RatingCompat.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 android.support.v4.media;
-
-parcelable RatingCompat;
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl b/media/media/src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl
deleted file mode 100644
index d0c2f6f..0000000
--- a/media/media/src/main/aidl/android/support/v4/media/session/MediaSessionCompat.aidl
+++ /dev/null
@@ -1,20 +0,0 @@
-/* Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 android.support.v4.media.session;
-
-parcelable MediaSessionCompat.Token;
-parcelable MediaSessionCompat.QueueItem;
-parcelable MediaSessionCompat.ResultReceiverWrapper;
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/PlaybackStateCompat.aidl b/media/media/src/main/aidl/android/support/v4/media/session/PlaybackStateCompat.aidl
deleted file mode 100644
index 3d4ef59..0000000
--- a/media/media/src/main/aidl/android/support/v4/media/session/PlaybackStateCompat.aidl
+++ /dev/null
@@ -1,18 +0,0 @@
-/* Copyright 2014, The Android Open Source Project
-**
-** Licensed under the Apache License, Version 2.0 (the "License");
-** you may not use this file except in compliance with the License.
-** You may obtain a copy of the License at
-**
-**     http://www.apache.org/licenses/LICENSE-2.0
-**
-** Unless required by applicable law or agreed to in writing, software
-** distributed under the License is distributed on an "AS IS" BASIS,
-** WITHOUT 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 android.support.v4.media.session;
-
-parcelable PlaybackStateCompat;
diff --git a/media/media/src/main/aidl/android/support/v4/media/MediaDescriptionCompat.aidl b/media/media/src/main/stableAidl/android/support/v4/media/MediaDescriptionCompat.aidl
similarity index 91%
rename from media/media/src/main/aidl/android/support/v4/media/MediaDescriptionCompat.aidl
rename to media/media/src/main/stableAidl/android/support/v4/media/MediaDescriptionCompat.aidl
index f002cdd..c9c0d6d 100644
--- a/media/media/src/main/aidl/android/support/v4/media/MediaDescriptionCompat.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/MediaDescriptionCompat.aidl
@@ -15,4 +15,4 @@
 
 package android.support.v4.media;
 
-parcelable MediaDescriptionCompat;
+@JavaOnlyStableParcelable parcelable MediaDescriptionCompat;
diff --git a/media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl b/media/media/src/main/stableAidl/android/support/v4/media/MediaMetadataCompat.aidl
similarity index 91%
copy from media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl
copy to media/media/src/main/stableAidl/android/support/v4/media/MediaMetadataCompat.aidl
index 6d36b97..0c69e05 100644
--- a/media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/MediaMetadataCompat.aidl
@@ -15,4 +15,4 @@
 
 package android.support.v4.media;
 
-parcelable MediaMetadataCompat;
+@JavaOnlyStableParcelable parcelable MediaMetadataCompat;
diff --git a/media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl b/media/media/src/main/stableAidl/android/support/v4/media/RatingCompat.aidl
similarity index 92%
rename from media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl
rename to media/media/src/main/stableAidl/android/support/v4/media/RatingCompat.aidl
index 6d36b97..37f054d 100644
--- a/media/media/src/main/aidl/android/support/v4/media/MediaMetadataCompat.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/RatingCompat.aidl
@@ -15,4 +15,4 @@
 
 package android.support.v4.media;
 
-parcelable MediaMetadataCompat;
+@JavaOnlyStableParcelable parcelable RatingCompat;
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl b/media/media/src/main/stableAidl/android/support/v4/media/session/IMediaControllerCallback.aidl
similarity index 100%
rename from media/media/src/main/aidl/android/support/v4/media/session/IMediaControllerCallback.aidl
rename to media/media/src/main/stableAidl/android/support/v4/media/session/IMediaControllerCallback.aidl
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl b/media/media/src/main/stableAidl/android/support/v4/media/session/IMediaSession.aidl
similarity index 99%
rename from media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
rename to media/media/src/main/stableAidl/android/support/v4/media/session/IMediaSession.aidl
index 878ea1c..2b46a0e 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/IMediaSession.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/session/IMediaSession.aidl
@@ -24,6 +24,7 @@
 import android.support.v4.media.session.ParcelableVolumeInfo;
 import android.support.v4.media.session.PlaybackStateCompat;
 import android.support.v4.media.session.MediaSessionCompat;
+import android.net.Uri;
 import android.os.Bundle;
 import android.view.KeyEvent;
 
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/media/media/src/main/stableAidl/android/support/v4/media/session/MediaSessionCompat.aidl
similarity index 75%
copy from media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
copy to media/media/src/main/stableAidl/android/support/v4/media/session/MediaSessionCompat.aidl
index 2e77c4f..25b1b07 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/session/MediaSessionCompat.aidl
@@ -15,4 +15,6 @@
 
 package android.support.v4.media.session;
 
-parcelable ParcelableVolumeInfo;
+@JavaOnlyStableParcelable parcelable MediaSessionCompat.Token;
+@JavaOnlyStableParcelable parcelable MediaSessionCompat.QueueItem;
+@JavaOnlyStableParcelable parcelable MediaSessionCompat.ResultReceiverWrapper;
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/media/media/src/main/stableAidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
similarity index 91%
rename from media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
rename to media/media/src/main/stableAidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
index 2e77c4f..6317531 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
@@ -15,4 +15,4 @@
 
 package android.support.v4.media.session;
 
-parcelable ParcelableVolumeInfo;
+@JavaOnlyStableParcelable parcelable ParcelableVolumeInfo;
diff --git a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl b/media/media/src/main/stableAidl/android/support/v4/media/session/PlaybackStateCompat.aidl
similarity index 91%
copy from media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
copy to media/media/src/main/stableAidl/android/support/v4/media/session/PlaybackStateCompat.aidl
index 2e77c4f..63add46 100644
--- a/media/media/src/main/aidl/android/support/v4/media/session/ParcelableVolumeInfo.aidl
+++ b/media/media/src/main/stableAidl/android/support/v4/media/session/PlaybackStateCompat.aidl
@@ -15,4 +15,4 @@
 
 package android.support.v4.media.session;
 
-parcelable ParcelableVolumeInfo;
+@JavaOnlyStableParcelable parcelable PlaybackStateCompat;
diff --git a/media/media/src/main/stableAidlImports/android/app/PendingIntent.aidl b/media/media/src/main/stableAidlImports/android/app/PendingIntent.aidl
new file mode 100644
index 0000000..4212dfa
--- /dev/null
+++ b/media/media/src/main/stableAidlImports/android/app/PendingIntent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.app;
+
+@JavaOnlyStableParcelable parcelable PendingIntent;
diff --git a/media/media/src/main/stableAidlImports/android/content/Intent.aidl b/media/media/src/main/stableAidlImports/android/content/Intent.aidl
new file mode 100644
index 0000000..0c8c241
--- /dev/null
+++ b/media/media/src/main/stableAidlImports/android/content/Intent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.content;
+
+@JavaOnlyStableParcelable parcelable Intent;
diff --git a/media/media/src/main/stableAidlImports/android/net/Uri.aidl b/media/media/src/main/stableAidlImports/android/net/Uri.aidl
new file mode 100644
index 0000000..5ec5a66
--- /dev/null
+++ b/media/media/src/main/stableAidlImports/android/net/Uri.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.net;
+
+@JavaOnlyStableParcelable parcelable Uri;
diff --git a/media/media/src/main/stableAidlImports/android/os/Bundle.aidl b/media/media/src/main/stableAidlImports/android/os/Bundle.aidl
new file mode 100644
index 0000000..9642d31
--- /dev/null
+++ b/media/media/src/main/stableAidlImports/android/os/Bundle.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.os;
+
+@JavaOnlyStableParcelable parcelable Bundle;
diff --git a/media/media/src/main/stableAidlImports/android/view/KeyEvent.aidl b/media/media/src/main/stableAidlImports/android/view/KeyEvent.aidl
new file mode 100644
index 0000000..cfaff66
--- /dev/null
+++ b/media/media/src/main/stableAidlImports/android/view/KeyEvent.aidl
@@ -0,0 +1,19 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.view;
+
+@JavaOnlyStableParcelable parcelable KeyEvent;
diff --git a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
index 9b21b8f..726c980 100644
--- a/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
+++ b/media2/media2-player/src/androidTest/java/androidx/media2/player/MediaPlayerTest.java
@@ -464,6 +464,7 @@
         mPlayer.reset();
     }
 
+    @Ignore("Test disabled due to flakiness, see b/272342480")
     @Test
     @LargeTest
     public void seekModes() throws Exception {
diff --git a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderDescriptorTest.java b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderDescriptorTest.java
index 1245faf..cd2edf1 100644
--- a/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderDescriptorTest.java
+++ b/mediarouter/mediarouter/src/androidTest/java/androidx/mediarouter/media/MediaRouteProviderDescriptorTest.java
@@ -31,6 +31,7 @@
 /**
  * Test for {@link MediaRouteProviderDescriptor}.
  */
+@SmallTest
 @RunWith(AndroidJUnit4.class)
 public class MediaRouteProviderDescriptorTest {
 
@@ -43,70 +44,100 @@
     private static final String FAKE_MEDIA_ROUTE_NAME_3 = "fakeMediaRouteName3";
     private static final String FAKE_MEDIA_ROUTE_NAME_4 = "fakeMediaRouteName4";
 
+    private static final MediaRouteDescriptor FAKE_MEDIA_ROUTE_1 = new MediaRouteDescriptor
+            .Builder(FAKE_MEDIA_ROUTE_ID_1, FAKE_MEDIA_ROUTE_NAME_1)
+            .build();
+    private static final MediaRouteDescriptor FAKE_MEDIA_ROUTE_2 = new MediaRouteDescriptor
+            .Builder(FAKE_MEDIA_ROUTE_ID_2, FAKE_MEDIA_ROUTE_NAME_2)
+            .build();
+    private static final MediaRouteDescriptor FAKE_MEDIA_ROUTE_3 = new MediaRouteDescriptor
+            .Builder(FAKE_MEDIA_ROUTE_ID_3, FAKE_MEDIA_ROUTE_NAME_3)
+            .build();
+    private static final MediaRouteDescriptor FAKE_MEDIA_ROUTE_4 = new MediaRouteDescriptor
+            .Builder(FAKE_MEDIA_ROUTE_ID_4, FAKE_MEDIA_ROUTE_NAME_4)
+            .build();
+
     @Test
-    @SmallTest
-    public void testBuilder() {
-        // Tests for empty descriptor
+    public void defaultBuilder_createsEmptyDescriptor() {
         MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
         MediaRouteProviderDescriptor descriptor = builder.build();
         assertTrue(descriptor.getRoutes().isEmpty());
+    }
 
-        // Tests for addRoute()
-        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_1,
-                FAKE_MEDIA_ROUTE_NAME_1).build());
-        builder.addRoute(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_2,
-                FAKE_MEDIA_ROUTE_NAME_2).build());
-        descriptor = builder.build();
+    @Test
+    public void builderAddRoute_addsRoute() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
+        builder.addRoute(FAKE_MEDIA_ROUTE_1);
+        builder.addRoute(FAKE_MEDIA_ROUTE_2);
+        MediaRouteProviderDescriptor descriptor = builder.build();
         List<MediaRouteDescriptor> routes = descriptor.getRoutes();
+
         assertEquals(2, routes.size());
         assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
         assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
         assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
         assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
+    }
 
-        // Tests for addRoutes()
-        List<MediaRouteDescriptor> otherRoutes = new ArrayList<>();
-        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_3,
-                FAKE_MEDIA_ROUTE_NAME_3).build());
-        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_4,
-                FAKE_MEDIA_ROUTE_NAME_4).build());
-        builder.addRoutes(otherRoutes);
-        descriptor = builder.build();
-        routes = descriptor.getRoutes();
-        assertEquals(4, routes.size());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_1, routes.get(0).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_1, routes.get(0).getName());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_2, routes.get(1).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_2, routes.get(1).getName());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(2).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(2).getName());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(3).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(3).getName());
+    @Test
+    public void builderAddRoutes_addsRoutes() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
+        List<MediaRouteDescriptor> routesList = new ArrayList<>();
+        routesList.add(FAKE_MEDIA_ROUTE_3);
+        routesList.add(FAKE_MEDIA_ROUTE_4);
+        builder.addRoutes(routesList);
+        MediaRouteProviderDescriptor descriptor = builder.build();
 
-        // Tests for setRoutes()
-        builder.setRoutes(otherRoutes);
-        descriptor = builder.build();
-        routes = descriptor.getRoutes();
-        assertEquals(2, routes.size());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(0).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(0).getName());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(1).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(1).getName());
+        List<MediaRouteDescriptor> descriptorRoutes = descriptor.getRoutes();
+        assertEquals(2, descriptorRoutes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, descriptorRoutes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, descriptorRoutes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, descriptorRoutes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, descriptorRoutes.get(1).getName());
+    }
 
-        // Tests setRoutes() for side effects
-        otherRoutes.add(new MediaRouteDescriptor.Builder(FAKE_MEDIA_ROUTE_ID_1,
-                FAKE_MEDIA_ROUTE_NAME_1).build());
-        descriptor = builder.build();
-        routes = descriptor.getRoutes();
-        assertEquals(2, routes.size());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_3, routes.get(0).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, routes.get(0).getName());
-        assertEquals(FAKE_MEDIA_ROUTE_ID_4, routes.get(1).getId());
-        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, routes.get(1).getName());
+    @Test
+    public void builderSetRoutes_clearsListAndAddsRoutes() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
 
-        // Tests setRoutes against null
+        List<MediaRouteDescriptor> routesList = new ArrayList<>();
+        routesList.add(FAKE_MEDIA_ROUTE_3);
+        routesList.add(FAKE_MEDIA_ROUTE_4);
+        builder.setRoutes(routesList);
+
+        MediaRouteProviderDescriptor descriptor = builder.build();
+        List<MediaRouteDescriptor> descriptorRoutes = descriptor.getRoutes();
+        assertEquals(2, descriptorRoutes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, descriptorRoutes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, descriptorRoutes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, descriptorRoutes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, descriptorRoutes.get(1).getName());
+    }
+
+    @Test
+    public void builderSetRoutes_deepCopiesList() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
+
+        List<MediaRouteDescriptor> routesList = new ArrayList<>();
+        routesList.add(FAKE_MEDIA_ROUTE_3);
+        routesList.add(FAKE_MEDIA_ROUTE_4);
+        builder.setRoutes(routesList);
+        routesList.add(FAKE_MEDIA_ROUTE_1);
+
+        MediaRouteProviderDescriptor descriptor = builder.build();
+        List<MediaRouteDescriptor> descriptorRoutes = descriptor.getRoutes();
+        assertEquals(2, descriptorRoutes.size());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_3, descriptorRoutes.get(0).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_3, descriptorRoutes.get(0).getName());
+        assertEquals(FAKE_MEDIA_ROUTE_ID_4, descriptorRoutes.get(1).getId());
+        assertEquals(FAKE_MEDIA_ROUTE_NAME_4, descriptorRoutes.get(1).getName());
+    }
+
+    @Test
+    public void builderSetRoutes_withNull_createsEmptyDescriptor() {
+        MediaRouteProviderDescriptor.Builder builder = new MediaRouteProviderDescriptor.Builder();
         builder.setRoutes(null);
-        descriptor = builder.build();
+        MediaRouteProviderDescriptor descriptor = builder.build();
         assertTrue(descriptor.getRoutes().isEmpty());
     }
 }
diff --git a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderDescriptor.java b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderDescriptor.java
index c7e4d1f..ee1b64a 100644
--- a/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderDescriptor.java
+++ b/mediarouter/mediarouter/src/main/java/androidx/mediarouter/media/MediaRouteProviderDescriptor.java
@@ -42,9 +42,13 @@
     final List<MediaRouteDescriptor> mRoutes;
     final boolean mSupportsDynamicGroupRoute;
 
-    MediaRouteProviderDescriptor(List<MediaRouteDescriptor> routes,
+    MediaRouteProviderDescriptor(@NonNull List<MediaRouteDescriptor> routes,
                                  boolean supportsDynamicGroupRoute) {
-        mRoutes = (routes == null) ? Collections.emptyList() : routes;
+        if (routes.isEmpty()) {
+            mRoutes = Collections.emptyList();
+        } else {
+            mRoutes = Collections.unmodifiableList(new ArrayList<>(routes));
+        }
         mSupportsDynamicGroupRoute = supportsDynamicGroupRoute;
     }
 
@@ -108,9 +112,9 @@
             return mBundle;
         }
         mBundle = new Bundle();
-        if (mRoutes != null && !mRoutes.isEmpty()) {
+        if (!mRoutes.isEmpty()) {
             final int count = mRoutes.size();
-            ArrayList<Bundle> routeBundles = new ArrayList<Bundle>(count);
+            ArrayList<Bundle> routeBundles = new ArrayList<>(count);
             for (int i = 0; i < count; i++) {
                 routeBundles.add(mRoutes.get(i).asBundle());
             }
@@ -131,12 +135,10 @@
         if (bundle == null) {
             return null;
         }
-        List<MediaRouteDescriptor> routes = null;
+        List<MediaRouteDescriptor> routes = new ArrayList<>();
         ArrayList<Bundle> routeBundles = bundle.getParcelableArrayList(KEY_ROUTES);
-        if (routeBundles != null && !routeBundles.isEmpty()) {
-            final int count = routeBundles.size();
-            routes = new ArrayList<MediaRouteDescriptor>(count);
-            for (int i = 0; i < count; i++) {
+        if (routeBundles != null) {
+            for (int i = 0; i < routeBundles.size(); i++) {
                 routes.add(MediaRouteDescriptor.fromBundle(routeBundles.get(i)));
             }
         }
@@ -149,7 +151,7 @@
      * Builder for {@link MediaRouteProviderDescriptor}.
      */
     public static final class Builder {
-        private List<MediaRouteDescriptor> mRoutes;
+        private final List<MediaRouteDescriptor> mRoutes = new ArrayList<>();
         private boolean mSupportsDynamicGroupRoute = false;
 
         /**
@@ -166,7 +168,7 @@
             if (descriptor == null) {
                 throw new IllegalArgumentException("descriptor must not be null");
             }
-            mRoutes = descriptor.mRoutes;
+            mRoutes.addAll(descriptor.getRoutes());
             mSupportsDynamicGroupRoute = descriptor.mSupportsDynamicGroupRoute;
         }
 
@@ -179,9 +181,7 @@
                 throw new IllegalArgumentException("route must not be null");
             }
 
-            if (mRoutes == null) {
-                mRoutes = new ArrayList<MediaRouteDescriptor>();
-            } else if (mRoutes.contains(route)) {
+            if (mRoutes.contains(route)) {
                 throw new IllegalArgumentException("route descriptor already added");
             }
             mRoutes.add(route);
@@ -210,10 +210,9 @@
          */
         @NonNull
         Builder setRoutes(@Nullable Collection<MediaRouteDescriptor> routes) {
-            if (routes == null || routes.isEmpty()) {
-                mRoutes = null;
-            } else {
-                mRoutes = new ArrayList<>(routes);
+            mRoutes.clear();
+            if (routes != null) {
+                mRoutes.addAll(routes);
             }
             return this;
         }
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 6ce6aef..eff9ca8 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -34,11 +34,11 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
-    api("androidx.lifecycle:lifecycle-common:2.6.0")
-    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0")
-    api("androidx.savedstate:savedstate-ktx:1.2.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.0")
+    api("androidx.lifecycle:lifecycle-common:2.6.1")
+    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
+    api("androidx.savedstate:savedstate-ktx:1.2.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.6.1")
     implementation("androidx.core:core-ktx:1.1.0")
     implementation("androidx.collection:collection-ktx:1.1.0")
     implementation("androidx.profileinstaller:profileinstaller:1.2.1")
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index e6984b1..3e660ca 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -33,7 +33,7 @@
     api("androidx.compose.runtime:runtime:1.0.1")
     api("androidx.compose.runtime:runtime-saveable:1.0.1")
     api("androidx.compose.ui:ui:1.0.1")
-    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.0")
+    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.6.1")
     api(projectOrArtifact(":navigation:navigation-runtime-ktx"))
 
     androidTestImplementation(projectOrArtifact(":compose:material:material"))
diff --git a/navigation/navigation-compose/samples/build.gradle b/navigation/navigation-compose/samples/build.gradle
index b0506a9..b71e29c 100644
--- a/navigation/navigation-compose/samples/build.gradle
+++ b/navigation/navigation-compose/samples/build.gradle
@@ -35,7 +35,7 @@
     implementation("androidx.compose.material:material:1.0.1")
     implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.0.1")
     implementation("androidx.lifecycle:lifecycle-runtime-ktx:2.5.0-rc01")
-    implementation("androidx.savedstate:savedstate-ktx:1.2.0")
+    implementation("androidx.savedstate:savedstate-ktx:1.2.1")
 }
 
 androidx {
diff --git a/navigation/navigation-runtime/build.gradle b/navigation/navigation-runtime/build.gradle
index 7787fd6..f3c6511 100644
--- a/navigation/navigation-runtime/build.gradle
+++ b/navigation/navigation-runtime/build.gradle
@@ -26,8 +26,8 @@
 dependencies {
     api(project(":navigation:navigation-common"))
     api("androidx.activity:activity-ktx:1.6.1")
-    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.0")
-    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.0")
+    api("androidx.lifecycle:lifecycle-runtime-ktx:2.6.1")
+    api("androidx.lifecycle:lifecycle-viewmodel-ktx:2.6.1")
     api("androidx.annotation:annotation-experimental:1.1.0")
     implementation('androidx.collection:collection:1.0.0')
 
diff --git a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
index bb6e488..4541c3a 100644
--- a/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
+++ b/navigation/navigation-runtime/src/main/java/androidx/navigation/ActivityNavigator.kt
@@ -513,6 +513,7 @@
          * @see NavOptions.popEnterAnim
          * @see NavOptions.popExitAnim
          */
+        @Suppress("DEPRECATION")
         @JvmStatic
         public fun applyPopAnimationsToPendingTransition(activity: Activity) {
             val intent = activity.intent ?: return
diff --git a/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
index 09f10d7..f49daaa 100644
--- a/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
+++ b/navigation/navigation-testing/src/androidTest/java/androidx/navigation/testing/TestNavigatorStateTest.kt
@@ -380,7 +380,8 @@
 
     @Navigator.Name("test")
     internal class TestTransitionNavigator : Navigator<NavDestination>() {
-        val testLifecycle = TestLifecycleOwner().lifecycle
+        private val testLifecycleOwner = TestLifecycleOwner()
+        val testLifecycle = testLifecycleOwner.lifecycle
 
         override fun createDestination(): NavDestination = NavDestination(this)
 
diff --git a/paging/paging-common/src/test/kotlin/androidx/paging/GarbageCollectionTestHelper.kt b/paging/paging-common/src/test/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
index de6cf9a..0f92723 100644
--- a/paging/paging-common/src/test/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
+++ b/paging/paging-common/src/test/kotlin/androidx/paging/GarbageCollectionTestHelper.kt
@@ -21,6 +21,7 @@
 import java.lang.ref.WeakReference
 import java.util.concurrent.atomic.AtomicBoolean
 import kotlin.concurrent.thread
+import kotlin.random.Random
 import kotlin.reflect.KClass
 import kotlin.time.Duration.Companion.seconds
 
@@ -41,7 +42,7 @@
         thread {
             val leak: ArrayList<ByteArray> = ArrayList()
             do {
-                val arraySize = (Math.random() * 1000).toInt()
+                val arraySize = Random.nextInt(1000)
                 leak.add(ByteArray(arraySize))
                 System.gc()
             } while (continueTriggeringGc.get())
diff --git a/paging/paging-common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt b/paging/paging-common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
index 7af01a1..dc2c9fb 100644
--- a/paging/paging-common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
+++ b/paging/paging-common/src/test/kotlin/androidx/paging/ItemKeyedDataSourceTest.kt
@@ -30,6 +30,7 @@
 import org.mockito.ArgumentCaptor
 import org.mockito.Mockito.verify
 import org.mockito.Mockito.verifyNoMoreInteractions
+import kotlin.random.Random
 import kotlin.test.assertFailsWith
 
 @Suppress("DEPRECATION")
@@ -501,8 +502,8 @@
             Item(
                 names[it % 10],
                 it,
-                Math.random() * 1000,
-                (Math.random() * 200).toInt().toString() + " fake st."
+                Random.nextDouble(1000.0),
+                Random.nextInt(200).toString() + " fake st."
             )
         }.sortedWith(ITEM_COMPARATOR)
     }
diff --git a/paging/paging-common/src/test/kotlin/androidx/paging/PagingSourceTest.kt b/paging/paging-common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
index e2d59c1..bf9b097 100644
--- a/paging/paging-common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
+++ b/paging/paging-common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
@@ -26,6 +26,7 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import kotlin.random.Random
 import kotlin.test.assertFailsWith
 
 @RunWith(JUnit4::class)
@@ -426,8 +427,8 @@
             Item(
                 names[it % 10],
                 it,
-                Math.random() * 1000,
-                (Math.random() * 200).toInt().toString() + " fake st."
+                Random.nextDouble(1000.0),
+                Random.nextInt(200).toString() + " fake st."
             )
         }.sortedWith(ITEM_COMPARATOR)
 
diff --git a/paging/paging-compose/api/current.txt b/paging/paging-compose/api/current.txt
index 5f5b021..e7b0de3 100644
--- a/paging/paging-compose/api/current.txt
+++ b/paging/paging-compose/api/current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, optional kotlin.coroutines.CoroutineContext context);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?>? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/api/public_plus_experimental_current.txt b/paging/paging-compose/api/public_plus_experimental_current.txt
index 5f5b021..e7b0de3 100644
--- a/paging/paging-compose/api/public_plus_experimental_current.txt
+++ b/paging/paging-compose/api/public_plus_experimental_current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, optional kotlin.coroutines.CoroutineContext context);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?>? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/api/restricted_current.txt b/paging/paging-compose/api/restricted_current.txt
index 5f5b021..e7b0de3 100644
--- a/paging/paging-compose/api/restricted_current.txt
+++ b/paging/paging-compose/api/restricted_current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>, optional kotlin.coroutines.CoroutineContext context);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?>? contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/build.gradle b/paging/paging-compose/build.gradle
index 0fc9b87..f1b7b79 100644
--- a/paging/paging-compose/build.gradle
+++ b/paging/paging-compose/build.gradle
@@ -35,9 +35,9 @@
     }
 
     implementation(libs.kotlinStdlib)
-    api("androidx.compose.foundation:foundation:1.0.5")
+    api("androidx.compose.foundation:foundation:1.2.1")
     api(project(":paging:paging-common"))
-    api("androidx.compose.runtime:runtime:1.0.5")
+    api("androidx.compose.runtime:runtime:1.2.1")
 
     androidTestImplementation(projectOrArtifact(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
diff --git a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index f6d53233..c0a818d 100644
--- a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -20,13 +20,17 @@
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.testutils.TestDispatcher
 import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -43,6 +47,7 @@
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.isActive
+import kotlinx.coroutines.runBlocking
 import org.junit.Assert.assertFalse
 import org.junit.Ignore
 import org.junit.Rule
@@ -245,6 +250,164 @@
     }
 
     @Test
+    fun differentContentTypes() {
+        val pager = createPager()
+
+        lateinit var state: LazyListState
+        val itemsSizePx = 30f
+        val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+        rule.setContent {
+            state = rememberLazyListState()
+
+            val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+
+            for (i in 0 until lazyPagingItems.itemCount) {
+                lazyPagingItems[i]
+            }
+
+            LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
+                val content = @Composable { tag: String ->
+                    Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
+                }
+                item(contentType = "not-to-reuse--1") {
+                    content("-1")
+                }
+                item(contentType = "reuse") {
+                    content("0")
+                }
+                items(
+                    items = lazyPagingItems,
+                    contentType = { if (it == 8) "reuse" else "not-to-reuse-$it" }
+                ) {
+                    content("$it")
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(2)
+                // now items -1 and 0 are put into reusables
+            }
+        }
+
+        rule.onNodeWithTag("-1")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("0")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(8)
+                // item 8 should reuse slot 1
+            }
+        }
+
+        rule.onNodeWithTag("-1")
+            .assertExists()
+            .assertIsNotDisplayed()
+        // node reused
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+        rule.onNodeWithTag("7")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("8")
+            .assertIsDisplayed()
+        rule.onNodeWithTag("9")
+            .assertIsDisplayed()
+    }
+
+    @Test
+    fun nullContentTypeWithPlaceholders() {
+        val config = PagingConfig(
+            pageSize = 1,
+            enablePlaceholders = true,
+            maxSize = 200,
+            initialLoadSize = 3,
+            prefetchDistance = 0,
+        )
+        val pager = Pager(
+            config = config,
+            pagingSourceFactory = { TestPagingSource(items = items, loadDelay = 0) }
+        )
+
+        lateinit var state: LazyListState
+        val itemsSizePx = 30f
+        val itemsSizeDp = with(rule.density) { itemsSizePx.toDp() }
+
+        var loadedItem6 = false
+
+        rule.setContent {
+            state = rememberLazyListState()
+
+            val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+            // Trigger page fetch until all items 1-6 are loaded
+            for (i in 0 until minOf(lazyPagingItems.itemCount, 6)) {
+                lazyPagingItems[i]
+                loadedItem6 = lazyPagingItems.peek(i) == 6
+            }
+
+            LazyColumn(Modifier.height(itemsSizeDp * 2.5f), state) {
+                val content = @Composable { tag: String ->
+                    Spacer(Modifier.height(itemsSizeDp).width(10.dp).testTag(tag))
+                }
+                item(contentType = "not-to-reuse--1") {
+                    content("-1")
+                }
+                item(contentType = null) {
+                    content("0")
+                }
+                items(
+                    items = lazyPagingItems,
+                    key = { it },
+                    // item 7 would be null, which should default to contentType null
+                    contentType = { "not-to-reuse-$it" }
+                ) {
+                    content("$it")
+                }
+            }
+        }
+
+        rule.waitUntil {
+            loadedItem6
+        }
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(2)
+                // now items -1 and 0 are put into reusables
+            }
+        }
+
+        rule.onNodeWithTag("-1")
+            .assertExists()
+            .assertIsNotDisplayed()
+        rule.onNodeWithTag("0")
+            .assertExists()
+            .assertIsNotDisplayed()
+
+        rule.runOnIdle {
+            runBlocking {
+                state.scrollToItem(6)
+                // item 7 which is null should reuse slot 1
+            }
+        }
+
+        rule.onNodeWithTag("-1")
+            .assertExists()
+            .assertIsNotDisplayed()
+        // node reused
+        rule.onNodeWithTag("0")
+            .assertDoesNotExist()
+        rule.onNodeWithTag("null")
+            .assertExists()
+            .assertIsNotDisplayed()
+    }
+
+    @Test
     fun itemSnapshotList() {
         lateinit var lazyPagingItems: LazyPagingItems<Int>
         val pager = createPager()
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index 7ba0385..ca90180 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -296,6 +296,9 @@
  * When you specify the key the scroll position will be maintained based on the key, which
  * means if you add/remove items before the current visible item the item with the given key
  * will be kept as the first visible one.
+ * @param contentType a factory of the content types for the item. The item compositions of the
+ * same type could be reused more efficiently. Note that null is a valid type and items of such
+ * type will be considered compatible.
  * @param itemContent the content displayed by a single item. In case the item is `null`, the
  * [itemContent] method should handle the logic of displaying a placeholder instead of the main
  * content displayed by an item which is not `null`.
@@ -303,6 +306,7 @@
 public fun <T : Any> LazyListScope.items(
     items: LazyPagingItems<T>,
     key: ((item: T) -> Any)? = null,
+    contentType: ((item: T) -> Any?)? = null,
     itemContent: @Composable LazyItemScope.(value: T?) -> Unit
 ) {
     items(
@@ -314,6 +318,12 @@
             } else {
                 key(item)
             }
+        },
+        contentType = { index ->
+            if (contentType == null) null else {
+                val item = items.peek(index)
+                if (item == null) null else contentType(item)
+            }
         }
     ) { index ->
         itemContent(items[index])
@@ -335,6 +345,9 @@
  * When you specify the key the scroll position will be maintained based on the key, which
  * means if you add/remove items before the current visible item the item with the given key
  * will be kept as the first visible one.
+ * @param contentType a factory of the content types for the item. The item compositions of the
+ * same type could be reused more efficiently. Note that null is a valid type and items of such
+ * type will be considered compatible.
  * @param itemContent the content displayed by a single item. In case the item is `null`, the
  * [itemContent] method should handle the logic of displaying a placeholder instead of the main
  * content displayed by an item which is not `null`.
@@ -342,6 +355,7 @@
 public fun <T : Any> LazyListScope.itemsIndexed(
     items: LazyPagingItems<T>,
     key: ((index: Int, item: T) -> Any)? = null,
+    contentType: ((index: Int, item: T) -> Any?)? = null,
     itemContent: @Composable LazyItemScope.(index: Int, value: T?) -> Unit
 ) {
     items(
@@ -353,6 +367,12 @@
             } else {
                 key(index, item)
             }
+        },
+        contentType = { index ->
+            if (contentType == null) null else {
+                val item = items.peek(index)
+                if (item == null) null else contentType(index, item)
+            }
         }
     ) { index ->
         itemContent(index, items[index])
diff --git a/percentlayout/OWNERS b/percentlayout/OWNERS
index c49ddc7..073b112 100644
--- a/percentlayout/OWNERS
+++ b/percentlayout/OWNERS
@@ -1,2 +1,4 @@
 # Bug component: 461485
 alanv@google.com
+aelias@google.com
+ryanmentley@google.com
diff --git a/playground-common/playground.properties b/playground-common/playground.properties
index 59d26ac..c1daf8e 100644
--- a/playground-common/playground.properties
+++ b/playground-common/playground.properties
@@ -25,6 +25,6 @@
 kotlin.code.style=official
 # Disable docs
 androidx.enableDocumentation=false
-androidx.playground.snapshotBuildId=9614968
+androidx.playground.snapshotBuildId=9725822
 androidx.playground.metalavaBuildId=9692962
 androidx.studio.type=playground
diff --git a/privacysandbox/OWNERS b/privacysandbox/OWNERS
index d52e717..0e5347a 100644
--- a/privacysandbox/OWNERS
+++ b/privacysandbox/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1314839
 abz@google.com
 ltenorio@google.com
 nicoroulet@google.com
diff --git a/privacysandbox/ads/ads-adservices-java/build.gradle b/privacysandbox/ads/ads-adservices-java/build.gradle
index af73f43..5915e9c 100644
--- a/privacysandbox/ads/ads-adservices-java/build.gradle
+++ b/privacysandbox/ads/ads-adservices-java/build.gradle
@@ -50,7 +50,7 @@
 
 android {
     compileSdk = 33
-    compileSdkExtension = 4
+    compileSdkExtension = 5
     namespace "androidx.privacysandbox.ads.adservices.java"
 }
 
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
index e3f1235..79b940f0 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/endtoend/measurement/MeasurementManagerTest.java
@@ -94,8 +94,8 @@
 
     @Test
     public void testRegisterSource_NoServerSetup_NoErrors() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         assertThat(mMeasurementManager.registerSourceAsync(
                 SOURCE_REGISTRATION_URI,
@@ -105,8 +105,8 @@
 
     @Test
     public void testRegisterTrigger_NoServerSetup_NoErrors() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         assertThat(mMeasurementManager.registerTriggerAsync(TRIGGER_REGISTRATION_URI).get())
                 .isNotNull();
@@ -115,8 +115,8 @@
     @Test
     @SdkSuppress(minSdkVersion = 33)
     public void registerWebSource_NoErrors() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         WebSourceParams webSourceParams =
                 new WebSourceParams(SOURCE_REGISTRATION_URI, false);
@@ -137,8 +137,8 @@
     @Test
     @SdkSuppress(minSdkVersion = 33)
     public void registerWebTrigger_NoErrors() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         WebTriggerParams webTriggerParams =
                 new WebTriggerParams(TRIGGER_REGISTRATION_URI, false);
@@ -155,8 +155,8 @@
     @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withNoRange_withCallback_NoErrors()
             throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -173,8 +173,8 @@
     @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withEmptyLists_withRange_withCallback_NoErrors()
             throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -193,8 +193,8 @@
     @SdkSuppress(minSdkVersion = 33)
     public void testDeleteRegistrations_withRequest_withInvalidArguments_withCallback_hasError()
             throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         DeletionRequest deletionRequest =
                 new DeletionRequest.Builder(
@@ -215,8 +215,8 @@
     @Test
     @SdkSuppress(minSdkVersion = 33)
     public void testMeasurementApiStatus_returnResultStatus() throws Exception {
-        // Skip the test if SDK extension 4 is not present.
-        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 4);
+        // Skip the test if SDK extension 5 is not present.
+        Assume.assumeTrue(AdServicesInfo.INSTANCE.version() >= 5);
 
         int result = mMeasurementManager.getMeasurementApiStatusAsync().get();
         assertThat(result).isEqualTo(1);
diff --git a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
index e4bdbb1..ba83a13 100644
--- a/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/androidTest/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFuturesTest.kt
@@ -64,16 +64,16 @@
     fun testMeasurementOlderVersions() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 4", sdkExtVersion < 5)
         assertThat(from(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testDeleteRegistrationsAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
 
@@ -107,11 +107,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterSourceAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val inputEvent = mock(InputEvent::class.java)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
@@ -140,11 +140,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterTriggerAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
         val answer = { args: InvocationOnMock ->
@@ -169,11 +169,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebSourceAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
         val answer = { args: InvocationOnMock ->
@@ -208,11 +208,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebTriggerAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
         val answer = { args: InvocationOnMock ->
@@ -244,11 +244,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatusAsync() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = from(mContext)
         val state = MeasurementManager.MEASUREMENT_API_STATE_DISABLED
@@ -271,7 +271,7 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     companion object {
 
         private val uri1: Uri = Uri.parse("www.abc.com")
diff --git a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
index dd39ab0..9590fb5 100644
--- a/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
+++ b/privacysandbox/ads/ads-adservices-java/src/main/java/androidx/privacysandbox/ads/adservices/java/measurement/MeasurementManagerFutures.kt
@@ -110,7 +110,7 @@
     @RequiresPermission(AdServicesPermissions.ACCESS_ADSERVICES_ATTRIBUTION)
     abstract fun getMeasurementApiStatusAsync(): ListenableFuture<Int>
 
-    private class Api33Ext4JavaImpl(
+    private class Api33Ext5JavaImpl(
         private val mMeasurementManager: MeasurementManager
     ) : MeasurementManagerFutures() {
         @DoNotInline
@@ -180,7 +180,7 @@
          */
         @JvmStatic
         fun from(context: Context): MeasurementManagerFutures? {
-            return obtain(context)?.let { Api33Ext4JavaImpl(it) }
+            return obtain(context)?.let { Api33Ext5JavaImpl(it) }
         }
     }
 }
\ No newline at end of file
diff --git a/privacysandbox/ads/ads-adservices/build.gradle b/privacysandbox/ads/ads-adservices/build.gradle
index 1c3ab60..70c6da1 100644
--- a/privacysandbox/ads/ads-adservices/build.gradle
+++ b/privacysandbox/ads/ads-adservices/build.gradle
@@ -45,7 +45,7 @@
 
 android {
     compileSdk = 33
-    compileSdkExtension = 4
+    compileSdkExtension = 5
     namespace "androidx.privacysandbox.ads.adservices"
 }
 
diff --git a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
index 0d05aa5..d489862 100644
--- a/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
+++ b/privacysandbox/ads/ads-adservices/src/androidTest/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManagerTest.kt
@@ -60,16 +60,16 @@
     fun testMeasurementOlderVersions() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("maxSdkVersion = API 33 ext 3", sdkExtVersion < 4)
+        Assume.assumeTrue("maxSdkVersion = API 33 ext 4", sdkExtVersion < 5)
         assertThat(obtain(mContext)).isEqualTo(null)
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testDeleteRegistrations() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
 
@@ -105,11 +105,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterSource() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val inputEvent = mock(InputEvent::class.java)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
@@ -140,11 +140,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterTrigger() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
@@ -171,11 +171,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebSource() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
@@ -212,11 +212,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testRegisterWebTrigger() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
@@ -251,11 +251,11 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatus() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
         val state = MeasurementManager.MEASUREMENT_API_STATE_ENABLED
@@ -279,16 +279,16 @@
     }
 
     @Test
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     fun testMeasurementApiStatusUnknown() {
         val sdkExtVersion = SdkExtensions.getExtensionVersion(SdkExtensions.AD_SERVICES)
 
-        Assume.assumeTrue("minSdkVersion = API 33 ext 4", sdkExtVersion >= 4)
+        Assume.assumeTrue("minSdkVersion = API 33 ext 5", sdkExtVersion >= 5)
         val measurementManager = mockMeasurementManager(mContext)
         val managerCompat = obtain(mContext)
         val answer = { args: InvocationOnMock ->
             val receiver = args.getArgument<OutcomeReceiver<Int, Exception>>(1)
-            receiver.onResult(5 /* Greater than values returned in SdkExtensions.AD_SERVICES = 4 */)
+            receiver.onResult(6 /* Greater than values returned in SdkExtensions.AD_SERVICES = 5 */)
             null
         }
         doAnswer(answer).`when`(measurementManager).getMeasurementApiStatus(any(), any())
@@ -307,7 +307,7 @@
     }
 
     @SdkSuppress(minSdkVersion = 30)
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
     companion object {
 
         private val uri1: Uri = Uri.parse("www.abc.com")
diff --git a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
index ee47562..e316ab0 100644
--- a/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
+++ b/privacysandbox/ads/ads-adservices/src/main/java/androidx/privacysandbox/ads/adservices/measurement/MeasurementManager.kt
@@ -21,6 +21,7 @@
 import android.content.Context
 import android.net.Uri
 import android.os.ext.SdkExtensions
+import android.util.Log
 import android.view.InputEvent
 import androidx.annotation.DoNotInline
 import androidx.annotation.RequiresExtension
@@ -90,8 +91,8 @@
     abstract suspend fun getMeasurementApiStatus(): Int
 
     @SuppressLint("NewApi", "ClassVerificationFailure")
-    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 4)
-    private class Api33Ext4Impl(
+    @RequiresExtension(extension = SdkExtensions.AD_SERVICES, version = 5)
+    private class Api33Ext5Impl(
         private val mMeasurementManager: android.adservices.measurement.MeasurementManager
     ) : MeasurementManager() {
         constructor(context: Context) : this(
@@ -250,8 +251,9 @@
         @JvmStatic
         @SuppressLint("NewApi", "ClassVerificationFailure")
         fun obtain(context: Context): MeasurementManager? {
-            return if (AdServicesInfo.version() >= 4) {
-                Api33Ext4Impl(context)
+            Log.d("MeasurementManager", "AdServicesInfo.version=${AdServicesInfo.version()}")
+            return if (AdServicesInfo.version() >= 5) {
+                Api33Ext5Impl(context)
             } else {
                 null
             }
diff --git a/privacysandbox/tools/tools-core/build.gradle b/privacysandbox/tools/tools-core/build.gradle
index 340c2e1..a1b06db 100644
--- a/privacysandbox/tools/tools-core/build.gradle
+++ b/privacysandbox/tools/tools-core/build.gradle
@@ -29,10 +29,12 @@
     api(libs.kotlinStdlib)
     api(libs.protobufLite)
     api(libs.kotlinPoet)
+    implementation(libs.guavaAndroid)
     implementation project(path: ':privacysandbox:tools:tools')
 
     testImplementation(libs.junit)
     testImplementation(libs.truth)
+    testImplementation(project(":internal-testutils-truth"))
     testImplementation(project(":privacysandbox:tools:tools-testing"))
     testImplementation(project(":room:room-compiler-processing-testing"))
     // Include android jar for compilation of generated sources.
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlInterfaceSpec.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlInterfaceSpec.kt
index 3453725..b166ba3 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlInterfaceSpec.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlInterfaceSpec.kt
@@ -17,6 +17,7 @@
 package androidx.privacysandbox.tools.core.generator.poet
 
 import androidx.privacysandbox.tools.core.model.Type
+import java.lang.IllegalStateException
 
 /** AIDL file with a single interface. */
 internal data class AidlInterfaceSpec(
@@ -51,9 +52,18 @@
         }
 
     class Builder(val type: Type) {
-        val methods = mutableListOf<AidlMethodSpec>()
+        private val methods = mutableListOf<AidlMethodSpec>()
 
         fun addMethod(method: AidlMethodSpec) {
+            val methodsByTxId = methods.associateBy(AidlMethodSpec::transactionId)
+            methodsByTxId[method.transactionId]?.let { conflictMethod ->
+                throw IllegalStateException(
+                    // TODO(b/271114359): Update this error message when manual IDs are possible
+                    "Methods '${method.name}' and '${conflictMethod.name}' in interface " +
+                        "'${type.simpleName}' have the same AIDL transaction ID. Please " +
+                        "change one of the methods' name or type signature."
+                )
+            }
             methods.add(method)
         }
 
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlMethodSpec.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlMethodSpec.kt
index ce117d6..edac2d2 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlMethodSpec.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/poet/AidlMethodSpec.kt
@@ -16,11 +16,35 @@
 
 package androidx.privacysandbox.tools.core.generator.poet
 
-internal data class AidlMethodSpec(val name: String, val parameters: List<AidlParameterSpec>) {
-    override fun toString() = "void $name(${parameters.joinToString(", ")});"
+import com.google.common.hash.Hashing
+
+private val AIDL_MAX_TRANSACTION_ID = 16777114UL
+
+/** The number of transaction IDs that we reserve for our own purposes. */
+private val RESERVED_ID_COUNT = 100UL
+
+private val JAVA_MAX_METHOD_COUNT = 65535UL
+
+internal data class AidlMethodSpec(
+    val name: String,
+    val parameters: List<AidlParameterSpec>,
+    val transactionId: Int,
+) {
+    constructor(name: String, parameters: List<AidlParameterSpec>) : this(
+        name,
+        parameters,
+        aidlTransactionId(name, parameters)
+    )
+
+    override fun toString(): String {
+        val params = parameters.joinToString(", ")
+
+        return "void $name($params) = $transactionId;"
+    }
 
     class Builder(val name: String) {
-        val parameters = mutableListOf<AidlParameterSpec>()
+        private val parameters = mutableListOf<AidlParameterSpec>()
+        private var transactionId: Int? = null
 
         fun addParameter(parameter: AidlParameterSpec) {
             parameters.add(parameter)
@@ -30,6 +54,35 @@
             addParameter(AidlParameterSpec(name, type, isIn = type.isList || type.isParcelable))
         }
 
-        fun build() = AidlMethodSpec(name, parameters)
+        fun build(): AidlMethodSpec {
+            val txId = transactionId
+            if (txId == null) {
+                return AidlMethodSpec(name, parameters)
+            }
+            // TODO(b/271114359): Add special handling for manually-supplied transaction IDs
+            return AidlMethodSpec(name, parameters, txId)
+        }
     }
-}
\ No newline at end of file
+}
+
+// This method must remain backwards-compatible to ensure SDK compatibility.
+internal fun aidlTransactionId(name: String, parameters: List<AidlParameterSpec>): Int {
+    val hash = Hashing.farmHashFingerprint64().hashString(
+        signature(name, parameters),
+        Charsets.UTF_8,
+    ).asLong().toULong()
+    val maxValue = AIDL_MAX_TRANSACTION_ID - RESERVED_ID_COUNT - JAVA_MAX_METHOD_COUNT + 1UL
+
+    // toInt is safe because $maxValue is well under 2^32 (in fact, under 2^24)
+    return (hash % maxValue).toInt()
+}
+
+// This method must remain backwards-compatible to ensure SDK compatibility.
+private fun signature(name: String, parameters: List<AidlParameterSpec>): String {
+    val params = parameters.joinToString(",") { it.type.signature() }
+    return "$name($params)"
+}
+
+// This method must remain backwards-compatible to ensure SDK compatibility.
+private fun AidlTypeSpec.signature(): String =
+    innerType.qualifiedName + (if (isList) "[]" else "")
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/proto/androidx/privacysandbox/tools/core/privacy_sandbox_tools_protocol.proto b/privacysandbox/tools/tools-core/src/main/proto/androidx/privacysandbox/tools/core/privacy_sandbox_tools_protocol.proto
index 788e3a7..8989f471 100644
--- a/privacysandbox/tools/tools-core/src/main/proto/androidx/privacysandbox/tools/core/privacy_sandbox_tools_protocol.proto
+++ b/privacysandbox/tools/tools-core/src/main/proto/androidx/privacysandbox/tools/core/privacy_sandbox_tools_protocol.proto
@@ -26,5 +26,5 @@
 // This message is stored in every SDK API descriptor so consumers can tweak code generation
 // according to what's expected by the compiled SDK.
 message ToolMetadata {
-  int32 code_generation_version = 1;
+  int32 code_generation_version = 2;
 }
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceSpecTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceSpecTest.kt
new file mode 100644
index 0000000..731b89e
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlInterfaceSpecTest.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.generator.poet.AidlInterfaceSpec
+import androidx.privacysandbox.tools.core.generator.poet.AidlMethodSpec
+import androidx.privacysandbox.tools.core.model.Type
+import androidx.testutils.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AidlInterfaceSpecTest {
+
+    @Test
+    fun transactionIdConflict_throws() {
+        val methodA = AidlMethodSpec("methodA", listOf(), transactionId = 5)
+        val methodB = AidlMethodSpec("methodB", listOf(), transactionId = 5)
+        val interfaceBuilder = AidlInterfaceSpec.Builder(Type("com.mysdk", "MySdk"))
+
+        interfaceBuilder.addMethod(methodA)
+        val thrown = assertThrows<IllegalStateException> {
+            interfaceBuilder.addMethod(methodB)
+        }
+        thrown.hasMessageThat().contains("'methodA'")
+        thrown.hasMessageThat().contains("'methodB'")
+        thrown.hasMessageThat().contains("'MySdk'")
+    }
+}
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlMethodSpecTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlMethodSpecTest.kt
new file mode 100644
index 0000000..57bd6bdc
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlMethodSpecTest.kt
@@ -0,0 +1,62 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.privacysandbox.tools.core.generator
+
+import androidx.privacysandbox.tools.core.generator.poet.AidlMethodSpec
+import androidx.privacysandbox.tools.core.generator.poet.AidlParameterSpec
+import androidx.privacysandbox.tools.core.generator.poet.AidlTypeKind
+import androidx.privacysandbox.tools.core.generator.poet.AidlTypeSpec
+import androidx.privacysandbox.tools.core.model.Type
+import com.google.common.hash.Hashing
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@RunWith(JUnit4::class)
+class AidlMethodSpecTest {
+
+    @Test
+    fun automaticallyCreatesTransactionId() {
+        val name = "methodName"
+        val parameters = listOf(
+            AidlParameterSpec(
+                "foo", AidlTypeSpec(
+                    Type("", "boolean"),
+                    AidlTypeKind.PRIMITIVE,
+                    isList = true,
+                )
+            ),
+            AidlParameterSpec(
+                "bar",
+                AidlTypeSpec(
+                    Type("com.mysdk", "IListStringTransactionCallback"),
+                    AidlTypeKind.INTERFACE
+                )
+            ),
+        )
+        val method = AidlMethodSpec(name, parameters)
+
+        val expectedSignature = "methodName(boolean[],com.mysdk.IListStringTransactionCallback)"
+        val expectedHash =
+            Hashing.farmHashFingerprint64().hashString(expectedSignature, Charsets.UTF_8).asLong()
+                .toULong()
+        val expectedTransactionId = expectedHash % (0xffff9bUL - 100UL - 65535UL)
+
+        Truth.assertThat(method.transactionId).isEqualTo(expectedTransactionId.toInt())
+    }
+}
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMyCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMyCallback.aidl
index b8695d1..d8a5848 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMyCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMyCallback.aidl
@@ -1,5 +1,5 @@
 package com.mysdk;
 
 oneway interface IMyCallback {
-    void onComplete(boolean result);
+    void onComplete(boolean result) = 9379493;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMySdk.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMySdk.aidl
index f8290e6..52c3c82 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMySdk.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlcallbackgeneratortest/output/com/mysdk/IMySdk.aidl
@@ -3,5 +3,5 @@
 import com.mysdk.IMyCallback;
 
 oneway interface IMySdk {
-    void doStuff(IMyCallback callback);
+    void doStuff(IMyCallback callback) = 1359883;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/ICancellationSignal.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/ICancellationSignal.aidl
index e336980..75e32f9 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/ICancellationSignal.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/ICancellationSignal.aidl
@@ -1,5 +1,5 @@
 package com.mysdk;
 
 oneway interface ICancellationSignal {
-    void cancel();
+    void cancel() = 12202374;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
index 98e2b50..2a532b7 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterface.aidl
@@ -3,6 +3,6 @@
 import com.mysdk.IMyInterfaceTransactionCallback;
 
 oneway interface IMyInterface {
-    void methodWithInterfaceParam(IMyInterface myInterface);
-    void suspendMethodWithInterfaceReturn(IMyInterfaceTransactionCallback transactionCallback);
+    void methodWithInterfaceParam(IMyInterface myInterface) = 5537946;
+    void suspendMethodWithInterfaceReturn(IMyInterfaceTransactionCallback transactionCallback) = 13841773;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterfaceTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterfaceTransactionCallback.aidl
index e920d2e..9c609c8 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterfaceTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMyInterfaceTransactionCallback.aidl
@@ -5,7 +5,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IMyInterfaceTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess(IMyInterface result);
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess(IMyInterface result) = 11329676;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMySdk.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMySdk.aidl
index e1e90b0..fd5c9d5 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMySdk.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlinterfacegeneratortest/output/com/mysdk/IMySdk.aidl
@@ -4,6 +4,6 @@
 import com.mysdk.IMyInterfaceTransactionCallback;
 
 oneway interface IMySdk {
-    void methodWithInterfaceParam(IMyInterface myInterface);
-    void suspendMethodWithInterfaceReturn(IMyInterfaceTransactionCallback transactionCallback);
+    void methodWithInterfaceParam(IMyInterface myInterface) = 5537946;
+    void suspendMethodWithInterfaceReturn(IMyInterfaceTransactionCallback transactionCallback) = 13841773;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/ICancellationSignal.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/ICancellationSignal.aidl
index e336980..75e32f9 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/ICancellationSignal.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/ICancellationSignal.aidl
@@ -1,5 +1,5 @@
 package com.mysdk;
 
 oneway interface ICancellationSignal {
-    void cancel();
+    void cancel() = 12202374;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IListStringTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IListStringTransactionCallback.aidl
index c42fc32..40b4da6 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IListStringTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IListStringTransactionCallback.aidl
@@ -4,7 +4,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IListStringTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess(in String[] result);
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess(in String[] result) = 13628579;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IMySdk.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IMySdk.aidl
index f806882..a9b0b45 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IMySdk.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IMySdk.aidl
@@ -5,9 +5,9 @@
 import com.mysdk.IUnitTransactionCallback;
 
 oneway interface IMySdk {
-    void methodWithoutReturnValue();
-    void suspendMethodWithLists(in int[] l, IListStringTransactionCallback transactionCallback);
-    void suspendMethodWithNullables(in int[] maybeInt, IListStringTransactionCallback transactionCallback);
-    void suspendMethodWithReturnValue(boolean a, int b, long c, float d, double e, char f, int g, IStringTransactionCallback transactionCallback);
-    void suspendMethodWithoutReturnValue(IUnitTransactionCallback transactionCallback);
+    void methodWithoutReturnValue() = 12054020;
+    void suspendMethodWithLists(in int[] l, IListStringTransactionCallback transactionCallback) = 669054;
+    void suspendMethodWithNullables(in int[] maybeInt, IListStringTransactionCallback transactionCallback) = 13840793;
+    void suspendMethodWithReturnValue(boolean a, int b, long c, float d, double e, char f, int g, IStringTransactionCallback transactionCallback) = 737811;
+    void suspendMethodWithoutReturnValue(IUnitTransactionCallback transactionCallback) = 2878300;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IStringTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IStringTransactionCallback.aidl
index 1cb6671..52a8620 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IStringTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IStringTransactionCallback.aidl
@@ -4,7 +4,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IStringTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess(String result);
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess(String result) = 3868755;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
index 3e1856f..25f6ec0 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlservicegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
@@ -4,7 +4,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IUnitTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess();
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess() = 3578307;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ICancellationSignal.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ICancellationSignal.aidl
index e336980..75e32f9 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ICancellationSignal.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/ICancellationSignal.aidl
@@ -1,5 +1,5 @@
 package com.mysdk;
 
 oneway interface ICancellationSignal {
-    void cancel();
+    void cancel() = 12202374;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IListOuterValueTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IListOuterValueTransactionCallback.aidl
index 3ba8dc8..75ca335 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IListOuterValueTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IListOuterValueTransactionCallback.aidl
@@ -5,7 +5,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IListOuterValueTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess(in ParcelableOuterValue[] result);
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess(in ParcelableOuterValue[] result) = 3688211;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IMySdk.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IMySdk.aidl
index 8618670..758220a 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IMySdk.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IMySdk.aidl
@@ -6,9 +6,9 @@
 import com.mysdk.ParcelableOuterValue;
 
 oneway interface IMySdk {
-    void methodReceivingValue(in ParcelableOuterValue value);
-    void suspendMethodReceivingValue(in ParcelableOuterValue inputValue, IUnitTransactionCallback transactionCallback);
-    void suspendMethodThatReturnsValue(IOuterValueTransactionCallback transactionCallback);
-    void suspendMethodWithListsOfValues(in ParcelableOuterValue[] inputValues, IListOuterValueTransactionCallback transactionCallback);
-    void suspendMethodWithNullableValues(in ParcelableOuterValue maybeValue, IOuterValueTransactionCallback transactionCallback);
+    void methodReceivingValue(in ParcelableOuterValue value) = 860096;
+    void suspendMethodReceivingValue(in ParcelableOuterValue inputValue, IUnitTransactionCallback transactionCallback) = 1452795;
+    void suspendMethodThatReturnsValue(IOuterValueTransactionCallback transactionCallback) = 2938984;
+    void suspendMethodWithListsOfValues(in ParcelableOuterValue[] inputValues, IListOuterValueTransactionCallback transactionCallback) = 14073433;
+    void suspendMethodWithNullableValues(in ParcelableOuterValue maybeValue, IOuterValueTransactionCallback transactionCallback) = 12446561;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IOuterValueTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IOuterValueTransactionCallback.aidl
index 929fee2..23dae2a 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IOuterValueTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IOuterValueTransactionCallback.aidl
@@ -5,7 +5,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IOuterValueTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess(in ParcelableOuterValue result);
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess(in ParcelableOuterValue result) = 6937269;
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
index 3e1856f..25f6ec0 100644
--- a/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
+++ b/privacysandbox/tools/tools-core/src/test/test-data/aidlvaluegeneratortest/output/com/mysdk/IUnitTransactionCallback.aidl
@@ -4,7 +4,7 @@
 import com.mysdk.PrivacySandboxThrowableParcel;
 
 oneway interface IUnitTransactionCallback {
-    void onCancellable(ICancellationSignal cancellationSignal);
-    void onFailure(in PrivacySandboxThrowableParcel throwableParcel);
-    void onSuccess();
+    void onCancellable(ICancellationSignal cancellationSignal) = 6802168;
+    void onFailure(in PrivacySandboxThrowableParcel throwableParcel) = 12699996;
+    void onSuccess() = 3578307;
 }
\ No newline at end of file
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 da9789e..d720829 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
@@ -42,6 +42,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Assume
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -80,6 +81,7 @@
         })
     }
 
+    @Ignore // b/271299184
     @Test
     fun testChangingSandboxedSdkViewLayoutChangesChildLayout() {
         val adapter = TestSandboxedUiAdapter(
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index caa887e..1f6736c 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -48,7 +48,7 @@
 
     defaultConfig {
         multiDexEnabled = true
-        testInstrumentationRunner "androidx.testutils.ActivityRecyclingAndroidJUnitRunner"
+        testInstrumentationRunner "androidx.recyclerview.test.TestRunner"
         multiDexEnabled true
     }
     namespace "androidx.recyclerview"
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt
new file mode 100644
index 0000000..05f598a
--- /dev/null
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/test/TestRunner.kt
@@ -0,0 +1,33 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.recyclerview.test
+
+import android.os.Bundle
+import androidx.recyclerview.widget.RecyclerView
+import androidx.testutils.ActivityRecyclingAndroidJUnitRunner
+
+class TestRunner : ActivityRecyclingAndroidJUnitRunner() {
+    override fun onCreate(arguments: Bundle?) {
+        super.onCreate(arguments)
+        RecyclerView.setDebugAssertionsEnabled(true)
+    }
+
+    override fun onDestroy() {
+        RecyclerView.setDebugAssertionsEnabled(false)
+        super.onDestroy()
+    }
+}
\ No newline at end of file
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
index 3c9ae08..ba9d036 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/LinearLayoutManagerTest.java
@@ -831,6 +831,19 @@
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
     @Test
     public void hiddenNoneRemoveViewAccessibility() throws Throwable {
+        // TODO(b/263592347): remove the RecyclerView.setDebugAssertionsEnabled calls
+        //  and combine this into the impl method
+        // This is just a separate method to temporarily wrap the whole thing in a try/finally
+        // block without messing with git history too much.
+        RecyclerView.setDebugAssertionsEnabled(false);
+        try {
+            hiddenNoneRemoveViewAccessibilityImpl();
+        } finally {
+            RecyclerView.setDebugAssertionsEnabled(true);
+        }
+    }
+
+    public void hiddenNoneRemoveViewAccessibilityImpl() throws Throwable {
         final Config config = new Config();
         int adapterSize = 1000;
         final boolean[] firstItemSpecialSize = new boolean[] {false};
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json
new file mode 100644
index 0000000..6a4c8e9
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json
@@ -0,0 +1,863 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "4effe9c46eb525253fa7650696035fa5",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity14",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "name"
+            ],
+            "referencedColumns": [
+              "name"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity14",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "name"
+            ],
+            "referencedColumns": [
+              "name"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity14_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity14_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `index` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "index",
+            "columnName": "index",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity18",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `entity7Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "entity7Id",
+            "columnName": "entity7Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id27"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity7Id, Entity7.name AS userNameAndId FROM Entity25 INNER JOIN Entity7 ON Entity25.entity7Id = Entity7.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4effe9c46eb525253fa7650696035fa5')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json
new file mode 100644
index 0000000..dfc2b4c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json
@@ -0,0 +1,947 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "e6ac760b64fa91da605ebdfefc93c88e",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`addedInV1`) REFERENCES `Entity13_V2`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity10_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity10_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Entity13_V2",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "addedInV1"
+            ],
+            "referencedColumns": [
+              "addedInV1"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity12_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity12_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_V2_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_V2_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT DEFAULT '1', `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13_V2",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13_V2`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity24",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `entity1Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "entity1Id",
+            "columnName": "entity1Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity26",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity26_addedInV2",
+            "unique": true,
+            "columnNames": [
+              "addedInV2"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity26_addedInV2` ON `${TABLE_NAME}` (`addedInV2`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id27"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity1Id, Entity1.name AS userNameAndId FROM Entity25 INNER JOIN Entity1 ON Entity25.entity1Id = Entity1.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e6ac760b64fa91da605ebdfefc93c88e')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json
new file mode 100644
index 0000000..86da3aa
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json
@@ -0,0 +1,963 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "7a7934a196e6d6eb3055b9957c91490c",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` TEXT NOT NULL DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "name"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `Entity27`(`id27`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity27",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "id"
+            ],
+            "referencedColumns": [
+              "id27"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`addedInV1`) REFERENCES `Entity13_V2`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity10_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity10_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Entity13_V2",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "addedInV1"
+            ],
+            "referencedColumns": [
+              "addedInV1"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity12_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity12_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_V2_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_V2_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT NOT NULL DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT NOT NULL DEFAULT '1', `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "name"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13_V2",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13_V2`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity24",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `entity1Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entity1Id",
+            "columnName": "entity1Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity26",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity26_addedInV2",
+            "unique": true,
+            "columnNames": [
+              "addedInV2"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity26_addedInV2` ON `${TABLE_NAME}` (`addedInV2`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id27"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity1Id, Entity1.name AS userNameAndId FROM Entity25 INNER JOIN Entity1 ON Entity25.entity1Id = Entity1.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7a7934a196e6d6eb3055b9957c91490c')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json
new file mode 100644
index 0000000..4710de2
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json
@@ -0,0 +1,105 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "5eac636b72950b38807bb59226d793cc",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `embeddedId1` INTEGER, `embeddedId2` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId1` INTEGER NOT NULL, `embeddedId2` INTEGER, PRIMARY KEY(`embeddedId1`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "embeddedId1"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId2` INTEGER NOT NULL, PRIMARY KEY(`embeddedId2`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "embeddedId2"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5eac636b72950b38807bb59226d793cc')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json
new file mode 100644
index 0000000..ac313e1
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json
@@ -0,0 +1,119 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "0332bc74d0bb19e6c041a67ff5273a96",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `embeddedId1` INTEGER, `addedInV2` INTEGER DEFAULT 1, `embeddedId2` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "embeddedEntity1.addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": false,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId1` INTEGER NOT NULL, `addedInV2` INTEGER NOT NULL DEFAULT 1, `embeddedId2` INTEGER, PRIMARY KEY(`embeddedId1`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "embeddedId1"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId2` INTEGER NOT NULL, PRIMARY KEY(`embeddedId2`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "embeddedId2"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0332bc74d0bb19e6c041a67ff5273a96')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
new file mode 100644
index 0000000..a47d59f
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
@@ -0,0 +1,47 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "250b199947a14a67da3f70823e90f8bc",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '250b199947a14a67da3f70823e90f8bc')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
new file mode 100644
index 0000000..96cda7c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas-ksp/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "553bb9e0bb6c2673f0687f76e66f6304",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '553bb9e0bb6c2673f0687f76e66f6304')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json
new file mode 100644
index 0000000..6a4c8e9
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/1.json
@@ -0,0 +1,863 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "4effe9c46eb525253fa7650696035fa5",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity14",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "name"
+            ],
+            "referencedColumns": [
+              "name"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`name`) REFERENCES `Entity14`(`name`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity14",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "name"
+            ],
+            "referencedColumns": [
+              "name"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity14_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity14_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `index` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "index",
+            "columnName": "index",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity18",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS3",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, matchinfo=fts3)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `entity7Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "entity7Id",
+            "columnName": "entity7Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id27"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity7Id, Entity7.name AS userNameAndId FROM Entity25 INNER JOIN Entity7 ON Entity25.entity7Id = Entity7.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '4effe9c46eb525253fa7650696035fa5')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json
new file mode 100644
index 0000000..dfc2b4c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/2.json
@@ -0,0 +1,947 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "e6ac760b64fa91da605ebdfefc93c88e",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`addedInV1`) REFERENCES `Entity13_V2`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity10_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity10_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Entity13_V2",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "addedInV1"
+            ],
+            "referencedColumns": [
+              "addedInV1"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity12_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity12_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_V2_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_V2_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `renamedInV2` TEXT DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT DEFAULT '1', `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": false,
+            "defaultValue": "'1'"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "name"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13_V2",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13_V2`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity24",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "rowid"
+          ],
+          "autoGenerate": true
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `entity1Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "entity1Id",
+            "columnName": "entity1Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity26",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [
+          {
+            "name": "index_Entity26_addedInV2",
+            "unique": true,
+            "columnNames": [
+              "addedInV2"
+            ],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity26_addedInV2` ON `${TABLE_NAME}` (`addedInV2`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id27"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity1Id, Entity1.name AS userNameAndId FROM Entity25 INNER JOIN Entity1 ON Entity25.entity1Id = Entity1.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e6ac760b64fa91da605ebdfefc93c88e')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json
new file mode 100644
index 0000000..86da3aa
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.AutoMigrationDb/3.json
@@ -0,0 +1,963 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 3,
+    "identityHash": "7a7934a196e6d6eb3055b9957c91490c",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity3",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity4",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity5",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` TEXT NOT NULL DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity6",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity7",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity8",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "name"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity9",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`id`) REFERENCES `Entity27`(`id27`) ON UPDATE NO ACTION ON DELETE NO ACTION )",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": [
+          {
+            "table": "Entity27",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "id"
+            ],
+            "referencedColumns": [
+              "id27"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity10",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`), FOREIGN KEY(`addedInV1`) REFERENCES `Entity13_V2`(`addedInV1`) ON UPDATE NO ACTION ON DELETE NO ACTION DEFERRABLE INITIALLY DEFERRED)",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity10_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity10_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": [
+          {
+            "table": "Entity13_V2",
+            "onDelete": "NO ACTION",
+            "onUpdate": "NO ACTION",
+            "columns": [
+              "addedInV1"
+            ],
+            "referencedColumns": [
+              "addedInV1"
+            ]
+          }
+        ]
+      },
+      {
+        "tableName": "Entity11",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity12",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity12_name",
+            "unique": true,
+            "columnNames": [
+              "name"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity12_name` ON `${TABLE_NAME}` (`name`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity13_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity13_V2_addedInV1",
+            "unique": true,
+            "columnNames": [
+              "addedInV1"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity13_V2_addedInV1` ON `${TABLE_NAME}` (`addedInV1`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity14",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity15",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity16",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity17",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT NOT NULL DEFAULT '1', PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity19_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity20_V2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `renamedInV2` TEXT NOT NULL DEFAULT '1', `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`name`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "renamedInV2",
+            "columnName": "renamedInV2",
+            "affinity": "TEXT",
+            "notNull": true,
+            "defaultValue": "'1'"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "name"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "Entity13_V2",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_UPDATE BEFORE UPDATE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_BEFORE_DELETE BEFORE DELETE ON `Entity13_V2` BEGIN DELETE FROM `Entity21` WHERE `docid`=OLD.`rowid`; END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_UPDATE AFTER UPDATE ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END",
+          "CREATE TRIGGER IF NOT EXISTS room_fts_content_sync_Entity21_AFTER_INSERT AFTER INSERT ON `Entity13_V2` BEGIN INSERT INTO `Entity21`(`docid`, `name`, `addedInV1`) VALUES (NEW.`rowid`, NEW.`name`, NEW.`addedInV1`); END"
+        ],
+        "tableName": "Entity21",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, content=`Entity13_V2`)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS4",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity22",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS4(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity23",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "ftsVersion": "FTS3",
+        "ftsOptions": {
+          "tokenizer": "simple",
+          "tokenizerArgs": [],
+          "contentTable": "",
+          "languageIdColumnName": "",
+          "matchInfo": "FTS4",
+          "notIndexedColumns": [],
+          "prefixSizes": [],
+          "preferredOrder": "ASC"
+        },
+        "contentSyncTriggers": [],
+        "tableName": "Entity24",
+        "createSql": "CREATE VIRTUAL TABLE IF NOT EXISTS `${TABLE_NAME}` USING FTS3(`name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1)",
+        "fields": [
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": true,
+          "columnNames": [
+            "rowid"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity25",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `entity1Id` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "entity1Id",
+            "columnName": "entity1Id",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity26",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV2` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [
+          {
+            "name": "index_Entity26_addedInV2",
+            "unique": true,
+            "columnNames": [
+              "addedInV2"
+            ],
+            "orders": [],
+            "createSql": "CREATE UNIQUE INDEX IF NOT EXISTS `index_Entity26_addedInV2` ON `${TABLE_NAME}` (`addedInV2`)"
+          }
+        ],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity27",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id27` INTEGER NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id27`))",
+        "fields": [
+          {
+            "fieldPath": "id27",
+            "columnName": "id27",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id27"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [
+      {
+        "viewName": "Entity25Detail",
+        "createSql": "CREATE VIEW `${VIEW_NAME}` AS SELECT Entity25.id, Entity25.name, Entity25.entity1Id, Entity1.name AS userNameAndId FROM Entity25 INNER JOIN Entity1 ON Entity25.entity1Id = Entity1.id"
+      }
+    ],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '7a7934a196e6d6eb3055b9957c91490c')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json
new file mode 100644
index 0000000..4710de2
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/1.json
@@ -0,0 +1,105 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "5eac636b72950b38807bb59226d793cc",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `embeddedId1` INTEGER, `embeddedId2` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId1` INTEGER NOT NULL, `embeddedId2` INTEGER, PRIMARY KEY(`embeddedId1`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "embeddedId1"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId2` INTEGER NOT NULL, PRIMARY KEY(`embeddedId2`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "embeddedId2"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '5eac636b72950b38807bb59226d793cc')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json
new file mode 100644
index 0000000..ac313e1
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.EmbeddedAutoMigrationDb/2.json
@@ -0,0 +1,119 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "0332bc74d0bb19e6c041a67ff5273a96",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `embeddedId1` INTEGER, `addedInV2` INTEGER DEFAULT 1, `embeddedId2` INTEGER, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": false
+          },
+          {
+            "fieldPath": "embeddedEntity1.addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": false,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity1.embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId1` INTEGER NOT NULL, `addedInV2` INTEGER NOT NULL DEFAULT 1, `embeddedId2` INTEGER, PRIMARY KEY(`embeddedId1`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId1",
+            "columnName": "embeddedId1",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "embeddedEntity2.embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": false
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "embeddedId1"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "EmbeddedEntity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`embeddedId2` INTEGER NOT NULL, PRIMARY KEY(`embeddedId2`))",
+        "fields": [
+          {
+            "fieldPath": "embeddedId2",
+            "columnName": "embeddedId2",
+            "affinity": "INTEGER",
+            "notNull": true
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "embeddedId2"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '0332bc74d0bb19e6c041a67ff5273a96')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
new file mode 100644
index 0000000..a47d59f
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/1.json
@@ -0,0 +1,47 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "250b199947a14a67da3f70823e90f8bc",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '250b199947a14a67da3f70823e90f8bc')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
new file mode 100644
index 0000000..96cda7c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/schemas/androidx.room.integration.kotlintestapp.migration.ProvidedAutoMigrationSpecTest.ProvidedAutoMigrationDb/2.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "553bb9e0bb6c2673f0687f76e66f6304",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '553bb9e0bb6c2673f0687f76e66f6304')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt
index 589d690..474ae6c 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/TestDatabase.kt
@@ -18,22 +18,22 @@
 
 import androidx.room.Database
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.dao.CounterDao
-import androidx.room.androidx.room.integration.kotlintestapp.dao.UsersDao
-import androidx.room.androidx.room.integration.kotlintestapp.vo.Counter
-import androidx.room.androidx.room.integration.kotlintestapp.vo.User
 import androidx.room.integration.kotlintestapp.dao.AbstractDao
 import androidx.room.integration.kotlintestapp.dao.BooksDao
+import androidx.room.integration.kotlintestapp.dao.CounterDao
 import androidx.room.integration.kotlintestapp.dao.DependencyDao
 import androidx.room.integration.kotlintestapp.dao.DerivedDao
+import androidx.room.integration.kotlintestapp.dao.UsersDao
 import androidx.room.integration.kotlintestapp.vo.Author
 import androidx.room.integration.kotlintestapp.vo.Book
 import androidx.room.integration.kotlintestapp.vo.BookAuthor
+import androidx.room.integration.kotlintestapp.vo.Counter
 import androidx.room.integration.kotlintestapp.vo.DataClassFromDependency
 import androidx.room.integration.kotlintestapp.vo.EntityWithJavaPojoList
 import androidx.room.integration.kotlintestapp.vo.JavaEntity
 import androidx.room.integration.kotlintestapp.vo.NoArgClass
 import androidx.room.integration.kotlintestapp.vo.Publisher
+import androidx.room.integration.kotlintestapp.vo.User
 
 @Database(
     entities = [
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/CounterDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/CounterDao.kt
index 62e7709..b4d769c6 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/CounterDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/CounterDao.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.dao
+package androidx.room.integration.kotlintestapp.dao
 
 import androidx.room.Dao
 import androidx.room.Query
 import androidx.room.Upsert
-import androidx.room.androidx.room.integration.kotlintestapp.vo.Counter
+import androidx.room.integration.kotlintestapp.vo.Counter
 
 @Dao
 interface CounterDao {
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/UsersDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/UsersDao.kt
index 7a7167a..cc1795a 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/UsersDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/dao/UsersDao.kt
@@ -14,12 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.dao
+package androidx.room.integration.kotlintestapp.dao
 
 import androidx.room.Dao
 import androidx.room.Insert
 import androidx.room.Query
-import androidx.room.androidx.room.integration.kotlintestapp.vo.User
+import androidx.room.integration.kotlintestapp.vo.User
 
 @Dao
 interface UsersDao {
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationDb.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationDb.kt
new file mode 100644
index 0000000..e5df42c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationDb.kt
@@ -0,0 +1,583 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.migration
+
+import androidx.room.AutoMigration
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.DatabaseView
+import androidx.room.DeleteColumn
+import androidx.room.DeleteTable
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Fts3
+import androidx.room.Fts4
+import androidx.room.FtsOptions
+import androidx.room.Index
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RenameColumn
+import androidx.room.RenameTable
+import androidx.room.RoomDatabase
+import androidx.room.migration.AutoMigrationSpec
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+@Database(
+    version = AutoMigrationDb.LATEST_VERSION,
+    entities = [
+        AutoMigrationDb.Entity1::class,
+        AutoMigrationDb.Entity2::class,
+        AutoMigrationDb.Entity3::class,
+        AutoMigrationDb.Entity4::class,
+        AutoMigrationDb.Entity5::class,
+        AutoMigrationDb.Entity6::class,
+        AutoMigrationDb.Entity7::class,
+        AutoMigrationDb.Entity8::class,
+        AutoMigrationDb.Entity9::class,
+        AutoMigrationDb.Entity10::class,
+        AutoMigrationDb.Entity11::class,
+        AutoMigrationDb.Entity12::class,
+        AutoMigrationDb.Entity13_V2::class,
+        AutoMigrationDb.Entity14::class,
+        AutoMigrationDb.Entity15::class,
+        AutoMigrationDb.Entity16::class,
+        AutoMigrationDb.Entity17::class,
+        AutoMigrationDb.Entity19_V2::class,
+        AutoMigrationDb.Entity20_V2::class,
+        AutoMigrationDb.Entity21::class,
+        AutoMigrationDb.Entity22::class,
+        AutoMigrationDb.Entity23::class,
+        AutoMigrationDb.Entity24::class,
+        AutoMigrationDb.Entity25::class,
+        AutoMigrationDb.Entity26::class,
+        AutoMigrationDb.Entity27::class],
+    autoMigrations = [AutoMigration(
+        from = 1,
+        to = 2,
+        spec = AutoMigrationDb.SimpleAutoMigration1::class
+    ), AutoMigration(from = 2, to = 3)],
+    views = [AutoMigrationDb.Entity25Detail::class],
+    exportSchema = true
+)
+abstract class AutoMigrationDb : RoomDatabase() {
+    internal abstract fun dao(): AutoMigrationDao
+
+    /**
+     * No change between versions.
+     */
+    @Entity
+    data class Entity1(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+    ) {
+
+        companion object {
+            const val TABLE_NAME = "Entity1"
+        }
+    }
+
+    /**
+     * A new simple column added to Entity 2 with a default value.
+     */
+    @Entity
+    data class Entity2(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+
+        @ColumnInfo(defaultValue = "2")
+        var addedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity2"
+        }
+    }
+
+    /**
+     * Added Entity 3 to the schema. No foreign keys, views, indices added.
+     */
+    @Entity
+    data class Entity3(
+        @PrimaryKey
+        var id: Int,
+        var name: String
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity3"
+        }
+    }
+
+    /**
+     * Changing the default value of ‘addedInV1’ in Entity 4.
+     */
+    @Entity
+    data class Entity4(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "2")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity4"
+        }
+    }
+
+    /**
+     * Changing the affinity of ‘addedInV1’ in Entity 5.
+     */
+    @Entity
+    internal class Entity5(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: String
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity5"
+        }
+    }
+
+    /**
+     * Changing the nullability of ‘addedInV1’ in Entity 6.
+     */
+    @Entity
+    internal class Entity6(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        // @ColumnInfo(defaultValue = "1") - now nullable
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity6"
+        }
+    }
+
+    /**
+     * No change between versions.
+     */
+    @Entity
+    data class Entity7(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity7"
+        }
+    }
+
+    /**
+     * Change the primary key of Entity 8.
+     */
+    @Entity
+    data class Entity8(
+        var id: Int,
+
+        @PrimaryKey
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity8"
+        }
+    }
+
+    /**
+     * Add a foreign key to Entity 9.
+     */
+    @Entity(
+        foreignKeys = [ForeignKey(
+            entity = Entity27::class,
+            parentColumns = ["id27"],
+            childColumns = ["id"]
+        )]
+    )
+    data class Entity9(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity9"
+        }
+    }
+
+    /**
+     * Change the foreign key added in Entity 10 to ‘addedInV1’. Add index for addedInV1 on
+     * Entity 10. The reference table of the foreign key has been renamed from Entity13 to
+     * Entity13_V2.
+     */
+    @Entity(
+        foreignKeys = [ForeignKey(
+            entity = Entity13_V2::class,
+            parentColumns = ["addedInV1"],
+            childColumns = ["addedInV1"],
+            deferred = true
+        )], indices = [Index(value = ["addedInV1"], unique = true)]
+    )
+    data class Entity10(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity10"
+        }
+    }
+
+    /**
+     * Remove the foreign key in Entity 11.
+     */
+    @Entity
+    data class Entity11(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity11"
+        }
+    }
+
+    /**
+     * Add an index ‘name’ to Entity 12.
+     */
+    @Entity(indices = [Index(value = ["name"], unique = true)])
+    data class Entity12(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity12"
+        }
+    }
+
+    /**
+     * Rename to Entity13_V2, it is a table referenced by the foreign key in Entity10. Change the
+     * index added in Entity 13 to ‘addedInV1’.
+     */
+    @Entity(indices = [Index(value = ["addedInV1"], unique = true)])
+    data class Entity13_V2(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity13"
+        }
+    }
+
+    /**
+     * Remove the index ‘name’ added in Entity 14.
+     */
+    @Entity
+    data class Entity14(
+        @PrimaryKey
+        var id: Int,
+        var name: String
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity14"
+        }
+    }
+
+    /**
+     * Deleting the column ‘addedInV1’ from Entity 15.
+     */
+    @Entity
+    data class Entity15(
+        @PrimaryKey
+        var id: Int,
+        var name: String
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity15"
+        }
+    }
+
+    /**
+     * Renaming the column ‘addedInV1’ from Entity 16 to ‘renamedInV2’.
+     */
+    @Entity
+    data class Entity16(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var renamedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity16"
+        }
+    }
+
+    /**
+     * Renaming the column ‘addedInV1’ from Entity 17 to ‘renamedInV2’. Changing the affinity of
+     * this column.
+     */
+    @Entity
+    data class Entity17(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var renamedInV2: String
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity17"
+        }
+    }
+
+    /**
+     * Deleted Entity 18.
+     *
+     * Rename Entity19 to ‘Entity19_V2’.
+     */
+    @Entity
+    data class Entity19_V2(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity19_V2"
+        }
+    }
+
+    /**
+     * Rename Entity20 to ‘Entity20_V2’. Rename the column ‘addedInV1’ to ‘renamedInV2’. Change
+     * the primary key of this table to ‘name’. Change the affinity of the column ‘renamedInV2’.
+     * Add new column ‘addedInV2’.
+     */
+    @Entity
+    data class Entity20_V2(
+        var id: Int,
+
+        @PrimaryKey
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var renamedInV2: String,
+
+        @ColumnInfo(defaultValue = "2")
+        var addedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity20_V2"
+        }
+    }
+
+    /**
+     * The content table of this FTS table has been renamed from Entity13 to Entity13_V2.
+     */
+    @Entity
+    @Fts4(contentEntity = Entity13_V2::class)
+    data class Entity21(
+        @PrimaryKey
+        var rowid: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity21"
+        }
+    }
+
+    /**
+     * Change the options of the table from FTS3 to FTS4.
+     */
+    @Entity
+    @Fts4(matchInfo = FtsOptions.MatchInfo.FTS4)
+    data class Entity22(
+        @PrimaryKey
+        var rowid: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity22"
+        }
+    }
+
+    @Entity
+    @Fts3
+    data class Entity23(
+        @PrimaryKey
+        var rowid: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+
+        @ColumnInfo(defaultValue = "2")
+        var addedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity23"
+        }
+    }
+
+    @Entity
+    @Fts3
+    internal class Entity24(
+        @PrimaryKey
+        var rowid: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity24"
+        }
+    }
+
+    @DatabaseView(
+        "SELECT Entity25.id, Entity25.name, Entity25.entity1Id, Entity1.name AS userNameAndId " +
+            "FROM Entity25 INNER JOIN Entity1 ON Entity25.entity1Id = Entity1.id "
+    )
+    internal class Entity25Detail {
+        var id = 0
+        var name: String? = null
+        var entity1Id: String? = null
+    }
+
+    /**
+     * Change the view between versions to use Entity1 instead of Entity7.
+     */
+    @Entity
+    data class Entity25(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var entity1Id: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity25"
+        }
+    }
+
+    /**
+     * Added a new table that has an index.
+     */
+    @Entity(indices = [Index(value = ["addedInV2"], unique = true)])
+    data class Entity26(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity26"
+        }
+    }
+
+    /**
+     * No change between versions.
+     */
+    @Entity
+    data class Entity27(
+        @PrimaryKey
+        var id27: Int,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity27"
+        }
+    }
+
+    @Dao
+    internal interface AutoMigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        fun getAllEntity1s(): List<Entity1>
+    }
+
+    @DeleteTable(tableName = "Entity18")
+    @RenameTable(fromTableName = "Entity19", toTableName = "Entity19_V2")
+    @RenameTable(fromTableName = "Entity20", toTableName = "Entity20_V2")
+    @RenameTable(fromTableName = "Entity13", toTableName = "Entity13_V2")
+    @RenameColumn(tableName = "Entity16", fromColumnName = "index", toColumnName = "renamedInV2")
+    @RenameColumn(
+        tableName = "Entity17",
+        fromColumnName = "addedInV1",
+        toColumnName = "renamedInV2"
+    )
+    @RenameColumn(
+        tableName = "Entity20",
+        fromColumnName = "addedInV1",
+        toColumnName = "renamedInV2"
+    )
+    @DeleteColumn(tableName = "Entity15", columnName = "addedInV1")
+    @RenameColumn(tableName = "Entity25", fromColumnName = "entity7Id", toColumnName = "entity1Id")
+    internal class SimpleAutoMigration1 : AutoMigrationSpec {
+        override fun onPostMigrate(db: SupportSQLiteDatabase) {
+            // Do something
+        }
+    }
+
+    companion object {
+        const val LATEST_VERSION = 3
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationTest.kt
new file mode 100644
index 0000000..f59de14
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/AutoMigrationTest.kt
@@ -0,0 +1,113 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.migration
+
+import android.database.sqlite.SQLiteConstraintException
+import android.os.Build
+import androidx.room.testing.MigrationTestHelper
+import androidx.room.util.TableInfo.Companion.read
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.JELLY_BEAN) // Due to FTS table migrations
+class AutoMigrationTest {
+    @JvmField
+    @Rule
+    var helper: MigrationTestHelper = MigrationTestHelper(
+        InstrumentationRegistry.getInstrumentation(),
+        AutoMigrationDb::class.java
+    )
+
+    // Run this to create the very 1st version of the db.
+    fun createFirstVersion() {
+        val db = helper.createDatabase(TEST_DB, 1)
+        db.execSQL("INSERT INTO Entity9 (id, name) VALUES (1, 'row1')")
+        db.execSQL("INSERT INTO Entity9 (id, name) VALUES (2, 'row2')")
+        db.execSQL("INSERT INTO Entity27 (id27) VALUES (3)")
+        db.execSQL("INSERT INTO Entity27 (id27) VALUES (5)")
+        db.close()
+    }
+
+    @Test
+    fun goFromV1ToV2() {
+        createFirstVersion()
+        val db = helper.runMigrationsAndValidate(
+            TEST_DB,
+            2,
+            true
+        )
+        val info = read(db, AutoMigrationDb.Entity1.TABLE_NAME)
+        assertThat(info.columns.size).isEqualTo(3)
+    }
+
+    @Test
+    fun goFromV1ToV3() {
+        createFirstVersion()
+        try {
+            helper.runMigrationsAndValidate(
+                TEST_DB,
+                3,
+                true
+            )
+        } catch (e: SQLiteConstraintException) {
+            assertThat(e.message).isEqualTo(
+                """Foreign key violation(s) detected in 'Entity9'.
+Number of different violations discovered: 1
+Number of rows in violation: 2
+Violation(s) detected in the following constraint(s):
+	Parent Table = Entity27, Foreign Key Constraint Index = 0
+"""
+            )
+        }
+    }
+
+    @Test
+    fun testAutoMigrationWithNewEmbeddedField() {
+        val embeddedHelper = MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            EmbeddedAutoMigrationDb::class.java
+        )
+        val db = embeddedHelper.createDatabase(
+            "embedded-auto-migration-test",
+            1
+        )
+        db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')")
+        val info = read(
+            embeddedHelper.runMigrationsAndValidate(
+                "embedded-auto-migration-test",
+                2,
+                true
+            ),
+            EmbeddedAutoMigrationDb.EmbeddedEntity1.TABLE_NAME
+        )
+        assertThat(info.columns.size).isEqualTo(3)
+    }
+
+    companion object {
+        private const val TEST_DB = "auto-migration-test"
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/EmbeddedAutoMigrationDb.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/EmbeddedAutoMigrationDb.kt
new file mode 100644
index 0000000..cd17c62
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/EmbeddedAutoMigrationDb.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.migration
+
+import androidx.room.AutoMigration
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Embedded
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RoomDatabase
+import androidx.room.RoomWarnings
+import java.io.Serializable
+
+@Database(
+    version = EmbeddedAutoMigrationDb.LATEST_VERSION,
+    entities = [
+        EmbeddedAutoMigrationDb.Entity1::class,
+        EmbeddedAutoMigrationDb.EmbeddedEntity1::class,
+        EmbeddedAutoMigrationDb.EmbeddedEntity2::class],
+    autoMigrations = [AutoMigration(from = 1, to = 2)],
+    exportSchema = true
+)
+abstract class EmbeddedAutoMigrationDb : RoomDatabase() {
+    @Dao
+    internal interface EmbeddedAutoMigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        fun allEntity1s(): List<Entity1>
+    }
+
+    internal abstract fun dao(): EmbeddedAutoMigrationDao
+
+    /**
+     * No change between versions.
+     */
+    @Entity
+    @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+    data class Entity1(
+        @PrimaryKey
+        val id: Int,
+        var name: String,
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+        @Embedded
+        var embeddedEntity1: EmbeddedEntity1?
+    ) : Serializable {
+        companion object {
+            const val TABLE_NAME = "Entity1"
+        }
+    }
+
+    @Entity
+    @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+    data class EmbeddedEntity1(
+        @PrimaryKey
+        val embeddedId1: Int,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV2: Int,
+        @Embedded
+        var embeddedEntity2: EmbeddedEntity2?
+    ) : Serializable {
+        companion object {
+            const val TABLE_NAME = "EmbeddedEntity1"
+        }
+    }
+
+    @Entity
+    @SuppressWarnings(RoomWarnings.PRIMARY_KEY_FROM_EMBEDDED_IS_DROPPED)
+    data class EmbeddedEntity2(@PrimaryKey val embeddedId2: Int) : Serializable {
+        companion object {
+            const val TABLE_NAME = "EmbeddedEntity2"
+        }
+    }
+    companion object {
+        const val LATEST_VERSION = 2
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/ProvidedAutoMigrationSpecTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/ProvidedAutoMigrationSpecTest.kt
new file mode 100644
index 0000000..d67604a
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/migration/ProvidedAutoMigrationSpecTest.kt
@@ -0,0 +1,177 @@
+/*
+ * Copyright (C) 2017 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.migration
+
+import androidx.room.AutoMigration
+import androidx.room.ColumnInfo
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.ProvidedAutoMigrationSpec
+import androidx.room.RoomDatabase
+import androidx.room.migration.AutoMigrationSpec
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.framework.FrameworkSQLiteOpenHelperFactory
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import java.io.IOException
+import org.hamcrest.CoreMatchers
+import org.hamcrest.MatcherAssert
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class ProvidedAutoMigrationSpecTest {
+    private val mProvidedSpec = ProvidedAutoMigrationDb.MyProvidedAutoMigration("Hi")
+
+    @JvmField
+    @Rule
+    var helperWithoutSpec: MigrationTestHelper
+    var helperWithSpec: MigrationTestHelper
+
+    init {
+        helperWithoutSpec = MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            ProvidedAutoMigrationDb::class.java
+        )
+        val specs: MutableList<AutoMigrationSpec> = ArrayList()
+        specs.add(mProvidedSpec)
+        helperWithSpec = MigrationTestHelper(
+            InstrumentationRegistry.getInstrumentation(),
+            ProvidedAutoMigrationDb::class.java,
+            specs,
+            FrameworkSQLiteOpenHelperFactory()
+        )
+    }
+
+    @Database(
+        version = 2,
+        entities = [ProvidedAutoMigrationDb.Entity1::class, ProvidedAutoMigrationDb.Entity2::class],
+        autoMigrations = [AutoMigration(
+            from = 1,
+            to = 2,
+            spec = ProvidedAutoMigrationDb.MyProvidedAutoMigration::class
+        )],
+        exportSchema = true
+    )
+
+    abstract class ProvidedAutoMigrationDb : RoomDatabase() {
+
+        /**
+         * No change between versions.
+         */
+        @Entity
+        internal class Entity1 {
+            @PrimaryKey
+            var id = 0
+            var name: String? = null
+
+            @ColumnInfo(defaultValue = "1")
+            var addedInV1 = 0
+
+            companion object {
+                const val TABLE_NAME = "Entity1"
+            }
+        }
+
+        /**
+         * A new table added.
+         */
+        @Entity
+        internal class Entity2 {
+            @PrimaryKey
+            var id = 0
+            var name: String? = null
+
+            @ColumnInfo(defaultValue = "1")
+            var addedInV1 = 0
+
+            @ColumnInfo(defaultValue = "2")
+            var addedInV2 = 0
+
+            companion object {
+                const val TABLE_NAME = "Entity2"
+            }
+        }
+
+        @ProvidedAutoMigrationSpec
+        internal class MyProvidedAutoMigration(private val mPrefString: String) :
+            AutoMigrationSpec {
+            var mOnPostMigrateCalled = false
+            override fun onPostMigrate(db: SupportSQLiteDatabase) {
+                mOnPostMigrateCalled = true
+            }
+        }
+    }
+
+    // Run this to create the very 1st version of the db.
+    @Throws(IOException::class)
+    fun createFirstVersion() {
+        val db = helperWithoutSpec.createDatabase(TEST_DB, 1)
+        db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')")
+        db.close()
+    }
+
+    @Test
+    @Throws(IOException::class)
+    fun testOnPostMigrate() {
+        createFirstVersion()
+        helperWithSpec.runMigrationsAndValidate(
+            TEST_DB,
+            2,
+            true
+        )
+        MatcherAssert.assertThat(mProvidedSpec.mOnPostMigrateCalled, CoreMatchers.`is`(true))
+    }
+
+    /**
+     * Verifies that the user defined migration is selected over using an autoMigration.
+     */
+    @Test
+    @Throws(IOException::class)
+    fun testNoSpecProvidedInConfig() {
+        createFirstVersion()
+        try {
+            helperWithoutSpec.runMigrationsAndValidate(
+                TEST_DB,
+                2,
+                true
+            )
+        } catch (exception: IllegalArgumentException) {
+            MatcherAssert.assertThat(
+                exception.message,
+                CoreMatchers.containsString(
+                    "A required auto migration spec (androidx.room.integration." +
+                        "kotlintestapp" +
+                        ".migration.ProvidedAutoMigrationSpecTest" +
+                        ".ProvidedAutoMigrationDb.MyProvidedAutoMigration) has not " +
+                        "been provided."
+                )
+            )
+        }
+    }
+
+    companion object {
+        private const val TEST_DB = "auto-migration-test"
+    }
+}
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AmbiguousColumnResolverTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AmbiguousColumnResolverTest.kt
index 149c4d5..37b2916 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AmbiguousColumnResolverTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AmbiguousColumnResolverTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import android.content.Context
 import androidx.room.ColumnInfo
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InternalsTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InternalsTest.kt
index 8230b30..d289707 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InternalsTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InternalsTest.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import androidx.room.Dao
 import androidx.room.Database
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
index 1b5d195..4d4aa6b 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/InvalidationTrackerFlowTest.kt
@@ -14,10 +14,8 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
-import androidx.room.integration.kotlintestapp.test.TestDatabaseTest
-import androidx.room.integration.kotlintestapp.test.TestUtil
 import androidx.room.invalidationTrackerFlow
 import androidx.room.withTransaction
 import androidx.test.ext.junit.runners.AndroidJUnit4
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest.kt
index 6b12b01..b9f82b4 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/JvmNameInDaoTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import androidx.room.Dao
 import androidx.room.Database
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ListenableFuturePagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ListenableFuturePagingSourceTest.kt
index 720cb70..64faf0e 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ListenableFuturePagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/ListenableFuturePagingSourceTest.kt
@@ -36,9 +36,9 @@
 import androidx.paging.PagingState
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.ItemStore
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingDb
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingEntity
+import androidx.room.integration.kotlintestapp.testutil.ItemStore
+import androidx.room.integration.kotlintestapp.testutil.PagingDb
+import androidx.room.integration.kotlintestapp.testutil.PagingEntity
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
index 43badcf..b7749ed 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/MultiTypedPagingSourceTest.kt
@@ -22,11 +22,11 @@
 import androidx.room.InvalidationTracker
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.ItemStore
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingDb
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingEntity
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingEntityDao
 import androidx.room.awaitPendingRefresh
+import androidx.room.integration.kotlintestapp.testutil.ItemStore
+import androidx.room.integration.kotlintestapp.testutil.PagingDb
+import androidx.room.integration.kotlintestapp.testutil.PagingEntity
+import androidx.room.integration.kotlintestapp.testutil.PagingEntityDao
 import androidx.sqlite.db.SimpleSQLiteQuery
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.FlakyTest
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/NullableCollectionQueryParamTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/NullableCollectionQueryParamTest.kt
index 96df347..77938fb 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/NullableCollectionQueryParamTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/NullableCollectionQueryParamTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import androidx.room.Dao
 import androidx.room.Database
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx2PagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx2PagingSourceTest.kt
index 4f35335..2c5bb12 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx2PagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx2PagingSourceTest.kt
@@ -14,19 +14,16 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import androidx.paging.Pager
 import androidx.paging.PagingState
 import androidx.paging.rxjava2.RxPagingSource
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.ItemStore
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingDb
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingEntity
-import androidx.room.integration.kotlintestapp.test.CONFIG
-import androidx.room.integration.kotlintestapp.test.createExpected
-import androidx.room.integration.kotlintestapp.test.createItems
+import androidx.room.integration.kotlintestapp.testutil.ItemStore
+import androidx.room.integration.kotlintestapp.testutil.PagingDb
+import androidx.room.integration.kotlintestapp.testutil.PagingEntity
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -248,25 +245,27 @@
             block(collection)
         }
     }
-}
 
-private class RxPagingSourceImpl(
-    private val baseSource: RxPagingSource<Int, PagingEntity>,
-    private val initialLoadSingle: (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
-    private val nonInitialLoadSingle: (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
-) : RxPagingSource<Int, PagingEntity>() {
+    private class RxPagingSourceImpl(
+        private val baseSource: RxPagingSource<Int, PagingEntity>,
+        private val initialLoadSingle:
+            (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
+        private val nonInitialLoadSingle:
+            (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
+    ) : RxPagingSource<Int, PagingEntity>() {
 
-    val singles = mutableListOf<Single<LoadResult<Int, PagingEntity>>>()
+        val singles = mutableListOf<Single<LoadResult<Int, PagingEntity>>>()
 
-    override fun getRefreshKey(state: PagingState<Int, PagingEntity>): Int? {
-        return baseSource.getRefreshKey(state)
-    }
+        override fun getRefreshKey(state: PagingState<Int, PagingEntity>): Int? {
+            return baseSource.getRefreshKey(state)
+        }
 
-    override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PagingEntity>> {
-        return if (singles.isEmpty()) {
-            initialLoadSingle(params)
-        } else {
-            nonInitialLoadSingle(params)
-        }.also { singles.add(it) }
+        override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PagingEntity>> {
+            return if (singles.isEmpty()) {
+                initialLoadSingle(params)
+            } else {
+                nonInitialLoadSingle(params)
+            }.also { singles.add(it) }
+        }
     }
 }
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx3PagingSourceTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx3PagingSourceTest.kt
index 6bbf268..708a71f 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx3PagingSourceTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/Rx3PagingSourceTest.kt
@@ -21,9 +21,9 @@
 import androidx.paging.rxjava3.RxPagingSource
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.ItemStore
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingDb
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.PagingEntity
+import androidx.room.integration.kotlintestapp.testutil.ItemStore
+import androidx.room.integration.kotlintestapp.testutil.PagingDb
+import androidx.room.integration.kotlintestapp.testutil.PagingEntity
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
@@ -245,25 +245,27 @@
             block(collection)
         }
     }
-}
 
-private class RxPagingSourceImpl(
-    private val baseSource: RxPagingSource<Int, PagingEntity>,
-    private val initialLoadSingle: (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
-    private val nonInitialLoadSingle: (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
-) : RxPagingSource<Int, PagingEntity>() {
+    private class RxPagingSourceImpl(
+        private val baseSource: RxPagingSource<Int, PagingEntity>,
+        private val initialLoadSingle:
+            (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
+        private val nonInitialLoadSingle:
+            (LoadParams<Int>) -> Single<LoadResult<Int, PagingEntity>>,
+    ) : RxPagingSource<Int, PagingEntity>() {
 
-    val singles = mutableListOf<Single<LoadResult<Int, PagingEntity>>>()
+        val singles = mutableListOf<Single<LoadResult<Int, PagingEntity>>>()
 
-    override fun getRefreshKey(state: PagingState<Int, PagingEntity>): Int? {
-        return baseSource.getRefreshKey(state)
-    }
+        override fun getRefreshKey(state: PagingState<Int, PagingEntity>): Int? {
+            return baseSource.getRefreshKey(state)
+        }
 
-    override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PagingEntity>> {
-        return if (singles.isEmpty()) {
-            initialLoadSingle(params)
-        } else {
-            nonInitialLoadSingle(params)
-        }.also { singles.add(it) }
+        override fun loadSingle(params: LoadParams<Int>): Single<LoadResult<Int, PagingEntity>> {
+            return if (singles.isEmpty()) {
+                initialLoadSingle(params)
+            } else {
+                nonInitialLoadSingle(params)
+            }.also { singles.add(it) }
+        }
     }
 }
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
index 83eb3c2..f6ac286 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SuspendingQueryTest.kt
@@ -24,10 +24,10 @@
 import androidx.arch.core.executor.TaskExecutor
 import androidx.room.Room
 import androidx.room.RoomDatabase
-import androidx.room.androidx.room.integration.kotlintestapp.vo.Counter
 import androidx.room.integration.kotlintestapp.NewThreadDispatcher
 import androidx.room.integration.kotlintestapp.TestDatabase
 import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.room.integration.kotlintestapp.vo.Counter
 import androidx.room.withTransaction
 import androidx.sqlite.db.SupportSQLiteDatabase
 import androidx.sqlite.db.SupportSQLiteOpenHelper
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
index 9fd1ed6..6f4b575 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/SyncTriggersConcurrencyTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
 import androidx.room.Dao
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
index 5cf1dba..9db1459 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/TestDatabaseTest.kt
@@ -18,15 +18,15 @@
 
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
 import androidx.room.Room
-import androidx.room.androidx.room.integration.kotlintestapp.dao.UsersDao
-import androidx.room.androidx.room.integration.kotlintestapp.testutil.TestObserver
 import androidx.room.integration.kotlintestapp.TestDatabase
 import androidx.room.integration.kotlintestapp.dao.BooksDao
+import androidx.room.integration.kotlintestapp.dao.UsersDao
+import androidx.room.integration.kotlintestapp.testutil.TestObserver
 import androidx.test.core.app.ApplicationProvider
+import java.util.concurrent.TimeUnit
 import org.junit.After
 import org.junit.Before
 import org.junit.Rule
-import java.util.concurrent.TimeUnit
 
 abstract class TestDatabaseTest {
     @Rule
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
index b46ed98..d809b2a 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UpsertTest.kt
@@ -14,14 +14,12 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import android.database.sqlite.SQLiteConstraintException
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LiveData
 import androidx.lifecycle.testing.TestLifecycleOwner
-import androidx.room.integration.kotlintestapp.test.TestDatabaseTest
-import androidx.room.integration.kotlintestapp.test.TestUtil
 import androidx.room.integration.kotlintestapp.vo.Book
 import androidx.room.integration.kotlintestapp.vo.Lang
 import androidx.room.integration.kotlintestapp.vo.MiniBook
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UsersDaoTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UsersDaoTest.kt
index 896aaef9..9f47130 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UsersDaoTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/UsersDaoTest.kt
@@ -14,11 +14,10 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
-import androidx.room.androidx.room.integration.kotlintestapp.vo.Email
-import androidx.room.androidx.room.integration.kotlintestapp.vo.User
-import androidx.room.integration.kotlintestapp.test.TestDatabaseTest
+import androidx.room.integration.kotlintestapp.vo.Email
+import androidx.room.integration.kotlintestapp.vo.User
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
index 157aac5..277f983 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.test
+package androidx.room.integration.kotlintestapp.test
 
 import android.content.Context
 import androidx.arch.core.executor.testing.CountingTaskExecutorRule
@@ -23,7 +23,6 @@
 import androidx.room.RoomDatabase
 import androidx.room.integration.kotlintestapp.TestDatabase
 import androidx.room.integration.kotlintestapp.dao.BooksDao
-import androidx.room.integration.kotlintestapp.test.TestUtil
 import androidx.room.integration.kotlintestapp.test.TestUtil.Companion.BOOK_1
 import androidx.room.integration.kotlintestapp.vo.Book
 import androidx.test.core.app.ApplicationProvider
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/ItemStore.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/ItemStore.kt
index c232edb..c7600e3 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/ItemStore.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/ItemStore.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.testutil
+package androidx.room.integration.kotlintestapp.testutil
 
 import androidx.paging.AsyncPagingDataDiffer
 import androidx.paging.ItemSnapshotList
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingDb.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingDb.kt
index ec3efa3..9db8ebd 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingDb.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingDb.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.testutil
+package androidx.room.integration.kotlintestapp.testutil
 
 import androidx.room.Database
 import androidx.room.RoomDatabase
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntity.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntity.kt
index a75ccda..c6ac1e7 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntity.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntity.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.testutil
+package androidx.room.integration.kotlintestapp.testutil
 
 import androidx.recyclerview.widget.DiffUtil
 import androidx.room.Entity
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntityDao.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntityDao.kt
index ec38ef6..fdafdf0 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntityDao.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/PagingEntityDao.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.testutil
+package androidx.room.integration.kotlintestapp.testutil
 
 import androidx.paging.ListenableFuturePagingSource
 import androidx.paging.PagingSource
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/TestObserver.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/TestObserver.kt
index 2b89222..e437951 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/TestObserver.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/testutil/TestObserver.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.testutil
+package androidx.room.integration.kotlintestapp.testutil
 
 import androidx.lifecycle.Observer
 
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Album.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Album.kt
new file mode 100644
index 0000000..412e855
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Album.kt
@@ -0,0 +1,28 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Album(
+    @field:PrimaryKey val mAlbumId: Int,
+    val mAlbumName: String?,
+    val mAlbumArtist: String?,
+    val mAlbumReleaseYear: Int,
+    val mFeaturedArtist: String?
+)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumNameAndBandName.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumNameAndBandName.kt
new file mode 100644
index 0000000..96dec5c
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumNameAndBandName.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+data class AlbumNameAndBandName(val albumName: String?, val bandName: String?)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumWithSongs.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumWithSongs.kt
new file mode 100644
index 0000000..713fb9a
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/AlbumWithSongs.kt
@@ -0,0 +1,24 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+import androidx.room.Relation
+
+class AlbumWithSongs(
+    @field:Embedded val album: Album,
+    @field:Relation(parentColumn = "mAlbumName", entityColumn = "mAlbum") val songs: List<Song>
+)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Artist.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Artist.kt
new file mode 100644
index 0000000..b8e0922
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Artist.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Artist(
+    @field:PrimaryKey val mArtistId: Int,
+    val mArtistName: String?,
+    val mIsActive: Boolean
+)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Counter.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Counter.kt
index ce8fd1c..a47bfc1 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Counter.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Counter.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.vo
+package androidx.room.integration.kotlintestapp.vo
 
 import androidx.room.Entity
 import androidx.room.PrimaryKey
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Email.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Email.kt
index 330f9c3..f1a8063 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Email.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Email.kt
@@ -14,6 +14,6 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.vo
+package androidx.room.integration.kotlintestapp.vo
 
 data class Email(var id: String?, var address: String?)
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Playlist.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Playlist.kt
new file mode 100644
index 0000000..4ab5cfd
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Playlist.kt
@@ -0,0 +1,22 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Playlist(@field:PrimaryKey val mPlaylistId: Int)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistSongXRef.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistSongXRef.kt
new file mode 100644
index 0000000..64f939a
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistSongXRef.kt
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.ForeignKey
+import androidx.room.Index
+
+@Entity(
+    primaryKeys = ["mPlaylistId", "mSongId"],
+    foreignKeys = [ForeignKey(
+        entity = Playlist::class,
+        parentColumns = ["mPlaylistId"],
+        childColumns = ["mPlaylistId"],
+        onDelete = ForeignKey.CASCADE
+    ), ForeignKey(
+        entity = Song::class,
+        parentColumns = ["mSongId"],
+        childColumns = ["mSongId"],
+        onDelete = ForeignKey.CASCADE
+    )],
+    indices = [Index("mPlaylistId"), Index("mSongId")]
+)
+data class PlaylistSongXRef(val mPlaylistId: Int, val mSongId: Int)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistWithSongs.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistWithSongs.kt
new file mode 100644
index 0000000..ee43929
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/PlaylistWithSongs.kt
@@ -0,0 +1,35 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Embedded
+import androidx.room.Junction
+import androidx.room.Relation
+
+data class PlaylistWithSongs(
+    @Embedded
+    var playlist: Playlist,
+
+    @Relation(
+        parentColumn = "mPlaylistId",
+        entity = Song::class,
+        entityColumn = "mSongId",
+        associateBy = Junction(
+            PlaylistSongXRef::class
+        )
+    )
+    var songs: List<Song>
+)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/ReleasedAlbum.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/ReleasedAlbum.kt
new file mode 100644
index 0000000..811b5e315
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/ReleasedAlbum.kt
@@ -0,0 +1,18 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+data class ReleasedAlbum(val releaseYear: Int, val albumName: String?)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Song.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Song.kt
new file mode 100644
index 0000000..2b6cd04
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/Song.kt
@@ -0,0 +1,29 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+package androidx.room.integration.kotlintestapp.vo
+
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+
+@Entity
+data class Song(
+    @field:PrimaryKey val mSongId: Int,
+    val mTitle: String?,
+    val mArtist: String?,
+    val mAlbum: String?, // in seconds
+    val mLength: Int,
+    val mReleasedYear: Int
+)
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/User.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/User.kt
index ccef283..64cc0f7 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/User.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/vo/User.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.room.androidx.room.integration.kotlintestapp.vo
+package androidx.room.integration.kotlintestapp.vo
 
 import androidx.room.Embedded
 import androidx.room.Entity
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
index efaa56e..4b1a7e9 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XProcessingEnv.kt
@@ -19,9 +19,8 @@
 import androidx.room.compiler.codegen.XTypeName
 import androidx.room.compiler.processing.javac.JavacProcessingEnv
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
-import com.google.devtools.ksp.processing.CodeGenerator
-import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.squareup.javapoet.ArrayTypeName
 import com.squareup.javapoet.TypeName
 import com.squareup.kotlinpoet.javapoet.KClassName
@@ -206,15 +205,11 @@
         @JvmStatic
         @JvmOverloads
         fun create(
-            options: Map<String, String>,
+            symbolProcessorEnvironment: SymbolProcessorEnvironment,
             resolver: Resolver,
-            codeGenerator: CodeGenerator,
-            logger: KSPLogger,
             config: XProcessingEnvConfig = XProcessingEnvConfig.DEFAULT
         ): XProcessingEnv = KspProcessingEnv(
-            options = options,
-            codeGenerator = codeGenerator,
-            logger = logger,
+            delegate = symbolProcessorEnvironment,
             config = config
         ).also { it.resolver = resolver }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
index 0c6ab9a..1874984 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/compat/XConverters.kt
@@ -31,7 +31,6 @@
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
 import androidx.room.compiler.processing.XVariableElement
-import androidx.room.compiler.processing.compat.XConverters.toXProcessing
 import androidx.room.compiler.processing.javac.JavacAnnotation
 import androidx.room.compiler.processing.javac.JavacAnnotationValue
 import androidx.room.compiler.processing.javac.JavacElement
@@ -54,7 +53,10 @@
 import androidx.room.compiler.processing.ksp.KspProcessingEnv
 import androidx.room.compiler.processing.ksp.KspType
 import androidx.room.compiler.processing.ksp.KspTypeElement
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticContinuationParameterElement
 import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticPropertyMethodElement
+import androidx.room.compiler.processing.ksp.synthetic.KspSyntheticReceiverParameterElement
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.symbol.KSAnnotated
 import com.google.devtools.ksp.symbol.KSAnnotation
 import com.google.devtools.ksp.symbol.KSClassDeclaration
@@ -168,10 +170,20 @@
         (env as JavacProcessingEnv).wrap(this, null, null)
 
     @JvmStatic
+    fun XProcessingEnv.toKS(): SymbolProcessorEnvironment = (this as KspProcessingEnv).delegate
+
+    @JvmStatic
     fun XTypeElement.toKS(): KSClassDeclaration = (this as KspTypeElement).declaration
 
     @JvmStatic
-    fun XElement.toKS(): KSAnnotated = (this as KspElement).declaration
+    fun XElement.toKS(): KSAnnotated =
+        when (this) {
+            is KspElement -> this.declaration
+            is KspSyntheticPropertyMethodElement -> this.field.declaration
+            is KspSyntheticContinuationParameterElement -> this.enclosingElement.declaration
+            is KspSyntheticReceiverParameterElement -> this.enclosingElement.declaration
+            else -> error("Don't know how to convert element of type '${this::class}' to KSP")
+        }
 
     @JvmStatic
     fun XExecutableElement.toKS(): KSFunctionDeclaration =
@@ -249,6 +261,9 @@
         return when (this) {
             is JavacElement -> this.env
             is KspElement -> this.env
+            is KspSyntheticContinuationParameterElement -> this.env
+            is KspSyntheticPropertyMethodElement -> this.env
+            is KspSyntheticReceiverParameterElement -> this.env
             else -> error("Unexpected element: $this")
         }
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
index dade470..9a24b90 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
@@ -38,9 +38,7 @@
     private val logger = DelegateLogger(symbolProcessorEnvironment.logger)
 
     private val xEnv = KspProcessingEnv(
-        options = symbolProcessorEnvironment.options,
-        codeGenerator = symbolProcessorEnvironment.codeGenerator,
-        logger = logger,
+        delegate = symbolProcessorEnvironment,
         config = config
     )
 
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
index 75cd2c3..232bacd 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspProcessingEnv.kt
@@ -28,9 +28,8 @@
 import androidx.room.compiler.processing.javac.XTypeElementStore
 import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.getClassDeclarationByName
-import com.google.devtools.ksp.processing.CodeGenerator
-import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
+import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.symbol.ClassKind
 import com.google.devtools.ksp.symbol.KSClassDeclaration
 import com.google.devtools.ksp.symbol.KSFile
@@ -43,12 +42,13 @@
 import com.google.devtools.ksp.symbol.Variance
 
 internal class KspProcessingEnv(
-    override val options: Map<String, String>,
-    codeGenerator: CodeGenerator,
-    logger: KSPLogger,
+    val delegate: SymbolProcessorEnvironment,
     override val config: XProcessingEnvConfig,
 ) : XProcessingEnv {
     override val backend: XProcessingEnv.Backend = XProcessingEnv.Backend.KSP
+    override val options = delegate.options
+    private val logger = delegate.logger
+    private val codeGenerator = delegate.codeGenerator
 
     // No API to get this but Kotlin's default is 8, so go with it for now.
     // TODO: https://github.com/google/ksp/issues/810
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
index 9fe597a..620f91f 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticContinuationParameterElement.kt
@@ -39,7 +39,7 @@
  * this is what KAPT generates and Room needs it as long as it generates java code.
  */
 internal class KspSyntheticContinuationParameterElement(
-    private val env: KspProcessingEnv,
+    val env: KspProcessingEnv,
     override val enclosingElement: KspMethodElement
 ) : XExecutableParameterElement,
     XEquality,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
index fa49f87..2676100 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/DatabaseProcessingStep.kt
@@ -101,29 +101,37 @@
         databases?.forEach { db ->
             DatabaseWriter(db, context.codeLanguage).write(context.processingEnv)
             if (db.exportSchema) {
+                val qName = db.element.qualifiedName
+                val filename = "${db.version}.json"
+                val exportToResources =
+                    Context.BooleanProcessorOptions.EXPORT_SCHEMA_RESOURCE.getValue(env)
                 val schemaOutFolderPath = context.schemaOutFolderPath
-                if (schemaOutFolderPath == null) {
-                    context.logger.w(
-                        Warning.MISSING_SCHEMA_LOCATION, db.element,
-                        ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY
+                if (exportToResources) {
+                    context.logger.w(ProcessorErrors.EXPORTING_SCHEMA_TO_RESOURCES)
+                    val schemaFileOutputStream = env.filer.writeResource(
+                        filePath = Path.of("schemas", qName, filename),
+                        originatingElements = listOf(db.element)
                     )
-                } else {
+                    db.exportSchema(schemaFileOutputStream)
+                } else if (schemaOutFolderPath != null) {
                     val schemaOutFolder = SchemaFileResolver.RESOLVER.getFile(
                         Path.of(schemaOutFolderPath)
                     )
                     if (!schemaOutFolder.exists()) {
                         schemaOutFolder.mkdirs()
                     }
-                    val qName = db.element.qualifiedName
                     val dbSchemaFolder = File(schemaOutFolder, qName)
                     if (!dbSchemaFolder.exists()) {
                         dbSchemaFolder.mkdirs()
                     }
                     db.exportSchema(
-                        File(
-                            dbSchemaFolder,
-                            "${db.version}.json"
-                        )
+                        File(dbSchemaFolder, "${db.version}.json")
+                    )
+                } else {
+                    context.logger.w(
+                        warning = Warning.MISSING_SCHEMA_LOCATION,
+                        element = db.element,
+                        msg = ProcessorErrors.MISSING_SCHEMA_EXPORT_DIRECTORY
                     )
                 }
             }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/log/RLog.kt b/room/room-compiler/src/main/kotlin/androidx/room/log/RLog.kt
index cb6f8f9..e973919 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/log/RLog.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/log/RLog.kt
@@ -74,6 +74,10 @@
         printToMessager(messager, WARNING, msg.safeFormat(args), defaultElement)
     }
 
+    fun w(msg: String, vararg args: Any) {
+        printToMessager(messager, WARNING, msg.safeFormat(args), defaultElement)
+    }
+
     private data class DiagnosticMessage(
         val msg: String,
         val element: XElement?,
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
index fcca5f5..3221bd5 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/Context.kt
@@ -263,7 +263,8 @@
         INCREMENTAL("room.incremental", defaultValue = true),
         EXPAND_PROJECTION("room.expandProjection", defaultValue = false),
         USE_NULL_AWARE_CONVERTER("room.useNullAwareTypeAnalysis", defaultValue = false),
-        GENERATE_KOTLIN("room.generateKotlin", defaultValue = false);
+        GENERATE_KOTLIN("room.generateKotlin", defaultValue = false),
+        EXPORT_SCHEMA_RESOURCE("room.exportSchemaResource", defaultValue = false);
 
         /**
          * Returns the value of this option passed through the [XProcessingEnv]. If the value
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index b077b98..414e98f 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -1147,4 +1147,11 @@
         return "The DAO method return type ($typeName) with the nullable type argument " +
         "is meaningless because for now Room will never put a null value in a result."
     }
+
+    val EXPORTING_SCHEMA_TO_RESOURCES = "Schema export is set to be outputted as a resource" +
+        " (i.e. room.exportSchemaResource is set to true), this means Room will write the current" +
+        " schema version file into the produced JAR. Such flag must only be used for generating" +
+        " the schema file and extracting it from the JAR but not for production builds, otherwise" +
+        " the schema file will end up in the final artifact which is typically not desired. This" +
+        " warning serves as a reminder to use room.exportSchemaResource cautiously."
 }
\ No newline at end of file
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
index d65aedb..1bcd843b 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/vo/Database.kt
@@ -23,6 +23,7 @@
 import androidx.room.migration.bundle.DatabaseBundle
 import androidx.room.migration.bundle.SchemaBundle
 import java.io.File
+import java.io.OutputStream
 import org.apache.commons.codec.digest.DigestUtils
 
 /**
@@ -125,4 +126,9 @@
         }
         SchemaBundle.serialize(schemaBundle, file)
     }
+
+    fun exportSchema(outputStream: OutputStream) {
+        val schemaBundle = SchemaBundle(SchemaBundle.LATEST_FORMAT, bundle)
+        SchemaBundle.serialize(schemaBundle, outputStream)
+    }
 }
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
index 24cad40..0e1c0b7 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/writer/AutoMigrationWriter.kt
@@ -21,6 +21,7 @@
 import androidx.room.compiler.codegen.XCodeBlock
 import androidx.room.compiler.codegen.XFunSpec
 import androidx.room.compiler.codegen.XFunSpec.Builder.Companion.addStatement
+import androidx.room.compiler.codegen.XMemberName.Companion.packageMember
 import androidx.room.compiler.codegen.XTypeSpec
 import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.addOriginatingElement
 import androidx.room.compiler.codegen.XTypeSpec.Builder.Companion.addProperty
@@ -375,8 +376,8 @@
         migrateBuilder: XFunSpec.Builder
     ) {
         migrateBuilder.addStatement(
-            "%T.foreignKeyCheck(db, %S)",
-            RoomTypeNames.DB_UTIL,
+            "%M(db, %S)",
+            RoomTypeNames.DB_UTIL.packageMember("foreignKeyCheck"),
             tableName
         )
     }
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
index 68b7ff2..b32301e 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DatabaseProcessorTest.kt
@@ -18,6 +18,7 @@
 
 import COMMON
 import androidx.room.DatabaseProcessingStep
+import androidx.room.RoomProcessor
 import androidx.room.compiler.codegen.CodeLanguage
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XTypeElement
@@ -1475,6 +1476,30 @@
     }
 
     @Test
+    fun exportSchemaToJarResources() {
+        val dbSource = Source.java(
+            "foo.bar.MyDb",
+            """
+            package foo.bar;
+            import androidx.room.*;
+            @Database(entities = {User.class}, version = 1, exportSchema = true)
+            public abstract class MyDb extends RoomDatabase {}
+            """.trimIndent()
+        )
+        val lib = compileFiles(
+            sources = listOf(dbSource, USER),
+            annotationProcessors = listOf(RoomProcessor()),
+            options = mapOf("room.exportSchemaResource" to "true"),
+            includeSystemClasspath = false
+        )
+        assertThat(
+            lib.any { libDir ->
+                libDir.walkTopDown().any { it.endsWith("schemas/foo.bar.MyDb/1.json") }
+            }
+        ).isTrue()
+    }
+
+    @Test
     fun jvmNameOnDaoMethod() {
         val jvmNameInDaoGetter = Source.kotlin(
             "MyDb.kt",
diff --git a/room/room-migration/api/restricted_current.txt b/room/room-migration/api/restricted_current.txt
index 8d5b5f4..8c3e714 100644
--- a/room/room-migration/api/restricted_current.txt
+++ b/room/room-migration/api/restricted_current.txt
@@ -173,6 +173,7 @@
     method public int getFormatVersion();
     method public boolean isSchemaEqual(androidx.room.migration.bundle.SchemaBundle other);
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public static final void serialize(androidx.room.migration.bundle.SchemaBundle bundle, java.io.File file) throws java.io.IOException;
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public static final void serialize(androidx.room.migration.bundle.SchemaBundle bundle, java.io.OutputStream outputStream) throws java.io.IOException;
     property public androidx.room.migration.bundle.DatabaseBundle database;
     property public int formatVersion;
     field public static final androidx.room.migration.bundle.SchemaBundle.Companion Companion;
@@ -182,6 +183,7 @@
   public static final class SchemaBundle.Companion {
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=UnsupportedEncodingException::class) public androidx.room.migration.bundle.SchemaBundle deserialize(java.io.InputStream fis) throws java.io.UnsupportedEncodingException;
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public void serialize(androidx.room.migration.bundle.SchemaBundle bundle, java.io.File file) throws java.io.IOException;
+    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @kotlin.jvm.Throws(exceptionClasses=IOException::class) public void serialize(androidx.room.migration.bundle.SchemaBundle bundle, java.io.OutputStream outputStream) throws java.io.IOException;
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public interface SchemaEquality<T> {
diff --git a/room/room-migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.kt b/room/room-migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.kt
index 16ab264..23e9176 100644
--- a/room/room-migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.kt
+++ b/room/room-migration/src/main/java/androidx/room/migration/bundle/SchemaBundle.kt
@@ -17,7 +17,6 @@
 package androidx.room.migration.bundle
 
 import androidx.annotation.RestrictTo
-
 import com.google.gson.Gson
 import com.google.gson.GsonBuilder
 import com.google.gson.JsonElement
@@ -28,15 +27,14 @@
 import com.google.gson.reflect.TypeToken
 import com.google.gson.stream.JsonReader
 import com.google.gson.stream.JsonWriter
-
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
 import java.io.InputStream
 import java.io.InputStreamReader
+import java.io.OutputStream
 import java.io.OutputStreamWriter
 import java.io.UnsupportedEncodingException
-import kotlin.jvm.Throws
 
 /**
  * Data class that holds the information about a database schema export.
@@ -68,7 +66,7 @@
         public fun deserialize(fis: InputStream): SchemaBundle {
             InputStreamReader(fis, CHARSET).use { inputStream ->
                 return GSON.fromJson(inputStream, SchemaBundle::class.javaObjectType)
-                    ?: throw IllegalStateException("Invalid schema file")
+                    ?: throw IllegalStateException("Empty schema file")
             }
         }
 
@@ -78,8 +76,14 @@
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
         @JvmStatic
         public fun serialize(bundle: SchemaBundle, file: File) {
-            val fos = FileOutputStream(file, false)
-            OutputStreamWriter(fos, CHARSET).use { outputStreamWriter ->
+            serialize(bundle, FileOutputStream(file, false))
+        }
+
+        @Throws(IOException::class)
+        @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP_PREFIX)
+        @JvmStatic
+        public fun serialize(bundle: SchemaBundle, outputStream: OutputStream) {
+            OutputStreamWriter(outputStream, CHARSET).use { outputStreamWriter ->
                 GSON.toJson(bundle, outputStreamWriter)
             }
         }
diff --git a/room/room-testing/build.gradle b/room/room-testing/build.gradle
index 4cef2b2..3fc9bce 100644
--- a/room/room-testing/build.gradle
+++ b/room/room-testing/build.gradle
@@ -20,10 +20,14 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("kotlin-android")
+    id("com.google.devtools.ksp")
 }
 
 android {
     namespace "androidx.room.testing"
+    sourceSets {
+        androidTest.assets.srcDirs += files("$projectDir/schemas".toString())
+    }
 }
 
 dependencies {
@@ -32,8 +36,21 @@
     api(project(":sqlite:sqlite"))
     api(project(":sqlite:sqlite-framework"))
     api(project(":room:room-migration"))
-    implementation("androidx.arch.core:core-runtime:2.0.1")
     api(libs.junit)
+    implementation("androidx.arch.core:core-runtime:2.0.1")
+    androidTestImplementation(libs.truth)
+    androidTestImplementation(libs.kotlinStdlib)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.espressoCore)
+    kspAndroidTest(
+            project(path: ":room:room-compiler", configuration: "shadowAndImplementation")
+    )
+}
+
+ksp {
+    arg("room.schemaLocation", "$projectDir/schemas".toString())
 }
 
 androidx {
diff --git a/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/1.json b/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/1.json
new file mode 100644
index 0000000..a38401f
--- /dev/null
+++ b/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/1.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 1,
+    "identityHash": "6ffabe84cb2c6c45dbb6d478ab3cb00e",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT NOT NULL, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": true
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "autoGenerate": false,
+          "columnNames": [
+            "id"
+          ]
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, '6ffabe84cb2c6c45dbb6d478ab3cb00e')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/2.json b/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/2.json
new file mode 100644
index 0000000..052b6f5
--- /dev/null
+++ b/room/room-testing/schemas/androidx.room.testing.kotlintestapp.migration.SimpleAutoMigrationDb/2.json
@@ -0,0 +1,87 @@
+{
+  "formatVersion": 1,
+  "database": {
+    "version": 2,
+    "identityHash": "e6ac760b64fa91da605ebdfefc93c88e",
+    "entities": [
+      {
+        "tableName": "Entity1",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      },
+      {
+        "tableName": "Entity2",
+        "createSql": "CREATE TABLE IF NOT EXISTS `${TABLE_NAME}` (`id` INTEGER NOT NULL, `name` TEXT, `addedInV1` INTEGER NOT NULL DEFAULT 1, `addedInV2` INTEGER NOT NULL DEFAULT 2, PRIMARY KEY(`id`))",
+        "fields": [
+          {
+            "fieldPath": "id",
+            "columnName": "id",
+            "affinity": "INTEGER",
+            "notNull": true
+          },
+          {
+            "fieldPath": "name",
+            "columnName": "name",
+            "affinity": "TEXT",
+            "notNull": false
+          },
+          {
+            "fieldPath": "addedInV1",
+            "columnName": "addedInV1",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "1"
+          },
+          {
+            "fieldPath": "addedInV2",
+            "columnName": "addedInV2",
+            "affinity": "INTEGER",
+            "notNull": true,
+            "defaultValue": "2"
+          }
+        ],
+        "primaryKey": {
+          "columnNames": [
+            "id"
+          ],
+          "autoGenerate": false
+        },
+        "indices": [],
+        "foreignKeys": []
+      }
+    ],
+    "views": [],
+    "setupQueries": [
+      "CREATE TABLE IF NOT EXISTS room_master_table (id INTEGER PRIMARY KEY,identity_hash TEXT)",
+      "INSERT OR REPLACE INTO room_master_table (id,identity_hash) VALUES(42, 'e6ac760b64fa91da605ebdfefc93c88e')"
+    ]
+  }
+}
\ No newline at end of file
diff --git a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
new file mode 100644
index 0000000..cc8902e
--- /dev/null
+++ b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/AutoMigrationAndMigrationTest.kt
@@ -0,0 +1,99 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing.kotlintestapp.migration
+
+import android.database.sqlite.SQLiteException
+import androidx.room.migration.Migration
+import androidx.room.testing.MigrationTestHelper
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Test custom database migrations.
+ */
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+class AutoMigrationAndMigrationTest {
+    @JvmField
+    @Rule
+    var helper: MigrationTestHelper = MigrationTestHelper(
+        InstrumentationRegistry.getInstrumentation(),
+        SimpleAutoMigrationDb::class.java
+    )
+
+    // Run this to create the very 1st version of the db.
+    fun createFirstVersion() {
+        val db = helper.createDatabase(TEST_DB, 1)
+        db.execSQL("INSERT INTO Entity1 (id, name) VALUES (1, 'row1')")
+        db.execSQL("INSERT INTO Entity2 (id, name) VALUES (2, 'row2')")
+        db.close()
+    }
+
+    /**
+     * Verifies that the user defined migration is selected over using an autoMigration.
+     */
+    @Test
+    fun testAutoMigrationsNotProcessedBeforeCustomMigrations() {
+        createFirstVersion()
+        try {
+            helper.runMigrationsAndValidate(
+                TEST_DB,
+                2,
+                true,
+                MIGRATION_1_2
+            )
+        } catch (e: SQLiteException) {
+            assertThat(e.message).containsMatch("no such table: Entity0")
+        }
+    }
+
+    @Test
+    fun autoMigrationShouldBeAddedToMigrations_WhenManualDowngradeMigrationIsPresent() {
+        createFirstVersion()
+        helper.runMigrationsAndValidate(
+            TEST_DB,
+            2,
+            true,
+            MIGRATION_1_0
+        )
+        val config = helper.databaseConfiguration
+        assertThat(config).isNotNull()
+        assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotNull()
+        assertThat(config.migrationContainer.findMigrationPath(1, 2)).isNotEmpty()
+    }
+
+    companion object {
+        private const val TEST_DB = "auto-migration-test"
+        private val MIGRATION_1_2: Migration = object : Migration(1, 2) {
+            override fun migrate(db: SupportSQLiteDatabase) {
+                db.execSQL(
+                    "ALTER TABLE `Entity0` ADD COLUMN `addedInV2` INTEGER NOT NULL " +
+                        "DEFAULT 2"
+                )
+            }
+        }
+        private val MIGRATION_1_0: Migration = object : Migration(1, 0) {
+            override fun migrate(db: SupportSQLiteDatabase) {}
+        }
+    }
+}
\ No newline at end of file
diff --git a/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/SimpleAutoMigrationDb.kt b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/SimpleAutoMigrationDb.kt
new file mode 100644
index 0000000..7580b90
--- /dev/null
+++ b/room/room-testing/src/androidTest/java/androidx/room/testing/kotlintestapp/migration/SimpleAutoMigrationDb.kt
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.room.testing.kotlintestapp.migration
+
+import androidx.room.AutoMigration
+import androidx.room.ColumnInfo
+import androidx.room.Dao
+import androidx.room.Database
+import androidx.room.Entity
+import androidx.room.PrimaryKey
+import androidx.room.Query
+import androidx.room.RoomDatabase
+import androidx.room.migration.AutoMigrationSpec
+import androidx.sqlite.db.SupportSQLiteDatabase
+
+@Database(
+    version = SimpleAutoMigrationDb.LATEST_VERSION,
+    entities = [
+        SimpleAutoMigrationDb.Entity1::class,
+        SimpleAutoMigrationDb.Entity2::class],
+    autoMigrations = [AutoMigration(
+        from = 1,
+        to = 2,
+        spec = SimpleAutoMigrationDb.SimpleAutoMigration1::class
+    )],
+    exportSchema = true
+)
+abstract class SimpleAutoMigrationDb : RoomDatabase() {
+    internal abstract fun dao(): AutoMigrationDao
+
+    /**
+     * No change between versions.
+     */
+    @Entity
+    data class Entity1(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+    ) {
+
+        companion object {
+            const val TABLE_NAME = "Entity1"
+        }
+    }
+
+    /**
+     * A new simple column added to Entity 2 with a default value.
+     */
+    @Entity
+    data class Entity2(
+        @PrimaryKey
+        var id: Int,
+        var name: String,
+
+        @ColumnInfo(defaultValue = "1")
+        var addedInV1: Int,
+
+        @ColumnInfo(defaultValue = "2")
+        var addedInV2: Int
+    ) {
+        companion object {
+            const val TABLE_NAME = "Entity2"
+        }
+    }
+
+    @Dao
+    internal interface AutoMigrationDao {
+        @Query("SELECT * from Entity1 ORDER BY id ASC")
+        fun getAllEntity1s(): List<Entity1>
+    }
+
+    internal class SimpleAutoMigration1 : AutoMigrationSpec {
+        override fun onPostMigrate(db: SupportSQLiteDatabase) {
+            // Do something
+        }
+    }
+
+    companion object {
+        const val LATEST_VERSION = 1
+    }
+}
\ No newline at end of file
diff --git a/room/scripts/attach-async-profiler-to-tests-init-script.gradle b/room/scripts/attach-async-profiler-to-tests-init-script.gradle
index 97303cf..06d8f87 100644
--- a/room/scripts/attach-async-profiler-to-tests-init-script.gradle
+++ b/room/scripts/attach-async-profiler-to-tests-init-script.gradle
@@ -5,7 +5,9 @@
 taskGraph.addTaskExecutionGraphListener { graph ->
     graph.beforeTask { task ->
         if (task instanceof Test) {
-            task.jvmArgs(jvmArgs)
+            if (jvmArgs != null) {
+                task.jvmArgs(jvmArgs)
+            }
             // this environment variable is used to avoid running profiling tests
             // unless we are in a profiling execution
             task.environment("ANDROIDX_ROOM_ENABLE_PROFILE_TESTS", "true")
diff --git a/room/scripts/java-kotlin-codegen-comparison.sh b/room/scripts/java-kotlin-codegen-comparison.sh
new file mode 100755
index 0000000..e222937
--- /dev/null
+++ b/room/scripts/java-kotlin-codegen-comparison.sh
@@ -0,0 +1,30 @@
+#!/bin/bash
+
+#
+# Copyright 2023 The Android Open Source Project
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+#      http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+
+set -eu
+
+JAVA_GEN_TASKS=":room:integration-tests:room-testapp-kotlin:kspWithKspGenJavaDebugAndroidTestKotlin \
+:room:integration-tests:room-testapp-kotlin:compileWithKspGenJavaDebugAndroidTestKotlin \
+:room:integration-tests:room-testapp-kotlin:compileWithKspGenJavaDebugAndroidTestJavaWithJavac"
+KOTLIN_GEN_TASKS=":room:integration-tests:room-testapp-kotlin:kspWithKspGenKotlinDebugAndroidTestKotlin \
+:room:integration-tests:room-testapp-kotlin:compileWithKspGenKotlinDebugAndroidTestKotlin \
+:room:integration-tests:room-testapp-kotlin:compileWithKspGenKotlinDebugAndroidTestJavaWithJavac"
+
+kotlinc -script $(dirname $0)/tasks-comparison.kts -- \
+  -t "java-codegen" $JAVA_GEN_TASKS \
+  -t "kotlin-codegen" $KOTLIN_GEN_TASKS
diff --git a/room/scripts/ksp-kapt-comparison.sh b/room/scripts/ksp-kapt-comparison.sh
index 8de53fa..e10e4fc 100755
--- a/room/scripts/ksp-kapt-comparison.sh
+++ b/room/scripts/ksp-kapt-comparison.sh
@@ -1,97 +1,11 @@
 #!/bin/bash
-# This script runs kotlin test app compilation with ksp and kapt repeatedly to measure time spent for each of them.
-# Each build is executed once first (to cache all other tasks), and then N times for just ksp/kapt tasks.
-set -e
-declare -A totals
-declare -A taskTotals
 
-function log {
-    echo $1
-}
-SCRIPT_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" &> /dev/null && pwd )"
-PROJECT_DIR="$SCRIPT_DIR/.."
-# move to the project directory
-cd $PROJECT_DIR;
+set -eu
 
-KSP_TASK=":room:integration-tests:room-testapp-kotlin:kspWithKspGenJavaDebugAndroidTestKotlin"
-KAPT_TASK=":room:integration-tests:room-testapp-kotlin:kaptGenerateStubsWithKaptDebugAndroidTestKotlin \
-    :room:integration-tests:room-testapp-kotlin:kaptWithKaptDebugAndroidTestKotlin"
-# parses the given profile file, extracts task durations that we are interested in and adds them to the global tracking
-# usage: parseTimes profileFileURI prefix
-function parseTimes {
-    local filePath=$1
-    local prefix=$2
-    # get the times
-    local result=`curl -s $filePath|grep :$prefix -A1`
-    local total=0
-    local taskName="ERROR-$prefix"
-    while read -r line
-        do
-        if [[ "$line" == *"numeric"* ]]; then
-            local taskTime=`echo $line|awk -F'[>s<]' '{print $5*1000}'`
-            total=$(($total + $taskTime))
-            taskTotals[$taskName]=$((taskTotals[$taskName] + $taskTime))
-        elif [[ "$line" == *":"* ]]; then
-            taskName=`echo $line|awk -F'[><]' '{print $3}'|awk -F'[:]' '{print $NF}'`
-        fi
-    done <<< $result
-    echo "total time spent for in this run  $prefix: $total"
-    totals[$prefix]=$((totals[$prefix] + $total))
-}
+KSP_TASKS=":room:integration-tests:room-testapp-kotlin:kspWithKspGenJavaDebugAndroidTestKotlin"
+KAPT_TASKS=":room:integration-tests:room-testapp-kotlin:kaptGenerateStubsWithKaptDebugAndroidTestKotlin \
+:room:integration-tests:room-testapp-kotlin:kaptWithKaptDebugAndroidTestKotlin"
 
-# Runs the kotlin integration test app with either ksp or kapt then records the duration of tasks.
-# usage: runBuild ksp / runBuild kapt
-function runBuild {
-    local type=$1
-    local task=""
-    if [ "$type" = "ksp" ]; then
-        task=$KSP_TASK
-    elif [ "$type" = "kapt" ]; then
-        task=$KAPT_TASK
-    else
-        echo "bad arg '$type'"
-        exit 1
-    fi
-    local cmd="./gradlew --init-script \
-        $SCRIPT_DIR/rerun-requested-task-init-script.gradle \
-        --no-configuration-cache
-        --profile $task"
-    log "Executing $cmd"
-    local profileFile=`$cmd|grep "room"|awk '/profiling report at:/ {print $6}'`
-    log "result: $profileFile"
-    parseTimes $profileFile $type
-}
-
-# Runs the compilation with kapt and ksp for the given number of times
-# usage: runTest 3 ksp|kapt
-function runTest {
-    local limit=$1
-    local type=$2
-
-    for (( c=1; c<=$limit; c++ ))
-    do
-        echo "run #$c of $limit"
-        runBuild "$type"
-    done
-}
-
-function printData {
-    local -n data=$1
-    echo "$1:"
-    for i in "${!data[@]}"
-    do
-        echo "$i : ${data[$i]} ms"
-    done
-}
-
-# build once so all other tasks are cached
-./gradlew --stop
-./gradlew $KSP_TASK
-runTest 10 "ksp"
-
-./gradlew --stop
-./gradlew $KAPT_TASK
-runTest 10 "kapt"
-
-printData totals
-printData taskTotals
+kotlinc -script $(dirname $0)/tasks-comparison.kts -- \
+  -t "ksp" $KSP_TASKS \
+  -t "kapt" $KAPT_TASKS
diff --git a/room/scripts/profile.sh b/room/scripts/profile.sh
index 1e34203..6d9ca30 100755
--- a/room/scripts/profile.sh
+++ b/room/scripts/profile.sh
@@ -144,7 +144,9 @@
     $GRADLEW --no-daemon \
         --init-script $SCRIPT_DIR/rerun-requested-task-init-script.gradle \
         --init-script $SCRIPT_DIR/attach-async-profiler-to-tests-init-script.gradle \
-        -p $PROJECT_DIR $GRADLE_ARGS \
+        -p $PROJECT_DIR \
+        --no-configuration-cache \
+        $GRADLE_ARGS \
         -Dkotlin.compiler.execution.strategy="in-process"  \
         $AGENT_PARAMETER_NAME="-agentpath:$AGENT_PATH=start,event=cpu,$AGENT_PARAMS,interval=500000" #sample every .5 ms
 }
diff --git a/room/scripts/tasks-comparison.kts b/room/scripts/tasks-comparison.kts
new file mode 100644
index 0000000..b15afd5
--- /dev/null
+++ b/room/scripts/tasks-comparison.kts
@@ -0,0 +1,196 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import java.io.BufferedReader
+import java.io.File
+import java.util.concurrent.TimeUnit
+
+val currentDir = File(".").absolutePath
+check(currentDir.endsWith("/frameworks/support/.")) {
+    "Script needs to be executed from '<check-out>/frameworks/support', was '$currentDir'."
+}
+val scriptDir = File(currentDir, "room/scripts")
+
+check(args.size >= 6) { "Expected at least 6 args. See usage instructions."}
+val taskIds = args.count { it == "-t" }
+if (taskIds != 2) {
+    error("Exactly two tags are required per invocation. Found $taskIds")
+}
+
+val firstTagIndex = args.indexOfFirst { it == "-t" } + 1
+val firstTag = args[firstTagIndex]
+val firstTasks = extractTasks(firstTagIndex, args)
+check(firstTasks.isNotEmpty()) { "Task list for a tag must not be empty." }
+
+val secondTagIndex = args.indexOfLast { it == "-t" } + 1
+val secondTag = args[secondTagIndex]
+val secondTasks = extractTasks(secondTagIndex, args)
+check(secondTasks.isNotEmpty()) { "Task list for a tag must not be empty." }
+
+println("Comparing tasks groups!")
+println("First tag: $firstTag")
+println("Task list:\n${firstTasks.joinToString(separator = "\n")}")
+println("Second tag: $secondTag")
+println("Task list\n${secondTasks.joinToString(separator = "\n")}")
+
+cleanBuild(firstTasks)
+val firstResult = profile(firstTag, firstTasks)
+
+cleanBuild(secondTasks)
+val secondResult = profile(secondTag, secondTasks)
+
+crunchNumbers(firstResult)
+crunchNumbers(secondResult)
+
+fun extractTasks(tagIndex: Int, args: Array<String>): List<String> {
+   return buildList {
+       for (i in (tagIndex + 1) until args.size) {
+           if (args[i] == "-t") {
+               break
+           }
+           add(args[i])
+       }
+   }
+}
+
+fun cleanBuild(tasks: List<String>) {
+    println("Running initial build to cook cache...")
+    runCommand("./gradlew --stop")
+    runCommand("./gradlew ${tasks.joinToString(separator = " ")}")
+}
+
+fun profile(
+    tag: String,
+    tasks: List<String>,
+    amount: Int = 10
+): ProfileResult {
+    println("Profiling tasks for '$tag'...")
+    val allRunTimes = List(amount) { runNumber ->
+        val profileCmd = buildString {
+            append("./gradlew ")
+            append("--init-script $scriptDir/rerun-requested-task-init-script.gradle ")
+            append("--no-configuration-cache ")
+            append("--profile ")
+            append(tasks.joinToString(separator = " "))
+        }
+        val reportPath = runCommand(profileCmd, returnOutputStream = true)?.use { stream ->
+            stream.lineSequence().forEach { line ->
+                if (line.startsWith("See the profiling report at:")) {
+                    val scheme = "file://"
+                    return@use line.substring(
+                        line.indexOf(scheme) + scheme.length
+                    )
+                }
+            }
+            return@use null
+        }
+        checkNotNull(reportPath) { "Couldn't get report path!" }
+        println("Result at: $reportPath")
+        val taskTimes = mutableMapOf<String, Float>()
+        File(reportPath).bufferedReader().use { reader ->
+            while (true) {
+                val line = reader.readLine()
+                if (line == null) {
+                    return@use
+                }
+                tasks.forEach { taskName ->
+                    if (line.contains(">$taskName<")) {
+                        val timeValue = checkNotNull(reader.readLine())
+                            .drop("<td class=\"numeric\">".length)
+                            .let { it.substring(0, it.indexOf("s</td>")) }
+                            .toFloat()
+                        taskTimes[taskName] = taskTimes.getOrDefault(taskName, 0.0f) + timeValue
+                    }
+                }
+            }
+        }
+        println("Result of run #${runNumber + 1} of '$tag':")
+        taskTimes.forEach { taskName, time ->
+            println("$time - $taskName")
+        }
+        return@List taskTimes
+    }
+    return ProfileResult(tag, allRunTimes)
+}
+
+fun crunchNumbers(result: ProfileResult) {
+    println("--------------------")
+    println("Summary of profile for '${result.tag}'")
+    println("--------------------")
+    println("Total time (${result.numOfRuns} runs):")
+    println("  Min: ${result.minTotal()}")
+    println("  Avg: ${result.avgTotal()}")
+    println("  Max: ${result.maxTotal()}")
+    println("Per task times:")
+    result.tasks.forEach { taskName ->
+        println("  $taskName")
+        println(buildString {
+            append("  Min: ${result.minTask(taskName)}")
+            append("  Avg: ${result.avgTask(taskName)}")
+            append("  Max: ${result.maxTask(taskName)}")
+        })
+    }
+}
+
+fun runCommand(
+    command: String,
+    workingDir: File = File("."),
+    timeoutAmount: Long = 60,
+    timeoutUnit: TimeUnit = TimeUnit.SECONDS,
+    returnOutputStream: Boolean = false
+): BufferedReader? = runCatching {
+    println("Executing: $command")
+    val proc = ProcessBuilder("\\s".toRegex().split(command))
+        .directory(workingDir)
+        .apply {
+            if (returnOutputStream) {
+                redirectOutput(ProcessBuilder.Redirect.PIPE)
+            } else {
+                redirectOutput(ProcessBuilder.Redirect.INHERIT)
+            }
+        }
+        .redirectError(ProcessBuilder.Redirect.INHERIT)
+        .start()
+    proc.waitFor(timeoutAmount, timeoutUnit)
+    if (proc.exitValue() != 0) {
+        error("Non-zero exit code received: ${proc.exitValue()}")
+    }
+    return if (returnOutputStream) {
+        proc.inputStream.bufferedReader()
+    } else {
+        null
+    }
+}.onFailure { it.printStackTrace() }.getOrNull()
+
+data class ProfileResult(
+    val tag: String,
+    private val taskTimes: List<Map<String, Float>>
+) {
+    val numOfRuns = taskTimes.size
+    val tasks = taskTimes.first().keys
+
+    fun minTotal(): Float = taskTimes.minOf { it.values.sum() }
+
+    fun avgTotal(): Float = taskTimes.map { it.values.sum() }.sum() / taskTimes.size
+
+    fun maxTotal(): Float = taskTimes.maxOf { it.values.sum() }
+
+    fun minTask(name: String): Float = taskTimes.minOf { it.getValue(name) }
+
+    fun avgTask(name: String): Float = taskTimes.map { it.getValue(name) }.sum() / taskTimes.size
+
+    fun maxTask(name: String): Float = taskTimes.maxOf { it.getValue(name) }
+}
\ No newline at end of file
diff --git a/savedstate/savedstate/build.gradle b/savedstate/savedstate/build.gradle
index a605a56..23eb488 100644
--- a/savedstate/savedstate/build.gradle
+++ b/savedstate/savedstate/build.gradle
@@ -21,7 +21,7 @@
 
     api("androidx.annotation:annotation:1.1.0")
     implementation("androidx.arch.core:core-common:2.1.0")
-    implementation(project(":lifecycle:lifecycle-common"))
+    implementation("androidx.lifecycle:lifecycle-common:2.6.1")
     api(libs.kotlinStdlib)
 
     androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime"))
diff --git a/settings.gradle b/settings.gradle
index 1a4896a..b2f892c 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -365,7 +365,9 @@
 includeProject(":annotation:annotation-experimental-lint")
 includeProject(":annotation:annotation-experimental-lint-integration-tests", "annotation/annotation-experimental-lint/integration-tests")
 includeProject(":annotation:annotation-sampled")
+includeProject(":appactions:interaction:interaction-capabilities-communication", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-core", [BuildType.MAIN])
+includeProject(":appactions:interaction:interaction-capabilities-fitness", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-productivity", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-capabilities-safety", [BuildType.MAIN])
 includeProject(":appactions:interaction:interaction-proto", [BuildType.MAIN])
diff --git a/slice/slice-benchmark/build.gradle b/slice/slice-benchmark/build.gradle
index 308c45b..e353c79 100644
--- a/slice/slice-benchmark/build.gradle
+++ b/slice/slice-benchmark/build.gradle
@@ -41,7 +41,8 @@
 
 androidx {
     name = "Slices Benchmarks"
-    publish = Publish.NONE
+    publish = Publish.NONE // Library is deprecated pending removal.
+    disableDeviceTests = true
     mavenVersion = LibraryVersions.SLICE_BENCHMARK
     inceptionYear = "2018"
     description = "RecyclerView Benchmarks"
diff --git a/slice/slice-builders-ktx/build.gradle b/slice/slice-builders-ktx/build.gradle
index 7e26f93..7ef627a 100644
--- a/slice/slice-builders-ktx/build.gradle
+++ b/slice/slice-builders-ktx/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.Publish
+import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -45,7 +46,9 @@
 
 androidx {
     name = "Slice builders KTX"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    publish = Publish.SNAPSHOT_ONLY // Library is deprecated pending removal.
+    disableDeviceTests = true // Pending removal, don't run tests.
+    runApiTasks = new RunApiTasks.Yes() // Pending removal, but keep API files for now.
     mavenVersion = LibraryVersions.SLICE_BUILDERS_KTX
     inceptionYear = "2018"
     description = "A set of Kotlin extension methods built on top of slice-builders APIs."
diff --git a/slice/slice-builders/build.gradle b/slice/slice-builders/build.gradle
index c2fa4dd..d0ba1a4 100644
--- a/slice/slice-builders/build.gradle
+++ b/slice/slice-builders/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.Publish
+import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -31,7 +32,9 @@
 
 androidx {
     name = "Slice builders"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    publish = Publish.SNAPSHOT_ONLY // Library is deprecated pending removal.
+    disableDeviceTests = true // Pending removal, don't run tests.
+    runApiTasks = new RunApiTasks.Yes() // Pending removal, but keep API files for now.
     mavenVersion = LibraryVersions.SLICE
     inceptionYear = "2017"
     description = "A set of builders to create templates using SliceProvider APIs"
diff --git a/slice/slice-core/build.gradle b/slice/slice-core/build.gradle
index add74f3..2bab0a9 100644
--- a/slice/slice-core/build.gradle
+++ b/slice/slice-core/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.Publish
+import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -37,7 +38,9 @@
 
 androidx {
     name = "Common utilities for slices"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    publish = Publish.SNAPSHOT_ONLY // Library is deprecated pending removal.
+    disableDeviceTests = true // Pending removal, don't run tests.
+    runApiTasks = new RunApiTasks.Yes() // Pending removal, but keep API files for now.
     mavenVersion = LibraryVersions.SLICE
     inceptionYear = "2017"
     description = "The slices core library provides utilities for the slices view and provider libraries"
diff --git a/slice/slice-remotecallback/build.gradle b/slice/slice-remotecallback/build.gradle
index afbf75b..56db28d 100644
--- a/slice/slice-remotecallback/build.gradle
+++ b/slice/slice-remotecallback/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.Publish
+import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -37,7 +38,9 @@
 
 androidx {
     name = "Slice Remote Callback"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    publish = Publish.SNAPSHOT_ONLY // Library is deprecated pending removal.
+    disableDeviceTests = true // Pending removal, don't run tests.
+    runApiTasks = new RunApiTasks.Yes() // Pending removal, but keep API files for now.
     mavenVersion = LibraryVersions.SLICE_REMOTECALLBACK
     inceptionYear = "2019"
     description = "A library that handles PendingIntents in slices as remote callbacks"
diff --git a/slice/slice-test/build.gradle b/slice/slice-test/build.gradle
index e5a0a1fc..ff04df0 100644
--- a/slice/slice-test/build.gradle
+++ b/slice/slice-test/build.gradle
@@ -40,7 +40,8 @@
 androidx {
     name = "Slice test code"
     type = LibraryType.INTERNAL_TEST_LIBRARY
-    publish = Publish.NONE
+    publish = Publish.NONE // Library is deprecated pending removal.
+    disableDeviceTests = true
     mavenVersion = LibraryVersions.SLICE
     inceptionYear = "2017"
     description = "A library that holds common code for testing slices"
diff --git a/slice/slice-view/build.gradle b/slice/slice-view/build.gradle
index 1df018c..5dc4379 100644
--- a/slice/slice-view/build.gradle
+++ b/slice/slice-view/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.Publish
+import androidx.build.RunApiTasks
 
 plugins {
     id("AndroidXPlugin")
@@ -41,7 +42,9 @@
 
 androidx {
     name = "Slice views"
-    publish = Publish.SNAPSHOT_AND_RELEASE
+    publish = Publish.SNAPSHOT_ONLY // Library is deprecated pending removal.
+    disableDeviceTests = true // Pending removal, don't run tests.
+    runApiTasks = new RunApiTasks.Yes() // Pending removal, but keep API files for now.
     mavenVersion = LibraryVersions.SLICE
     inceptionYear = "2017"
     description = "A library that handles rendering of slice content into supported templates"
diff --git a/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
index f419abd..66d82ae 100644
--- a/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
+++ b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
@@ -17,6 +17,8 @@
 package androidx.sqlite.db.framework
 
 import android.content.Context
+import androidx.sqlite.db.SupportSQLiteDatabase
+import androidx.sqlite.db.SupportSQLiteOpenHelper
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -108,4 +110,53 @@
         val actual = db1.attachedDbs?.map { it.first to it.second }
         assertThat(expected).isEqualTo(actual)
     }
+
+    // b/271083856 and b/183028015
+    @Test
+    fun testFrameWorkSQLiteDatabase_onUpgrade_maxSqlCache() {
+        // Open and close DB at initial version.
+        openHelper.writableDatabase.use { db ->
+            db.execSQL("CREATE TABLE Foo (id INTEGER NOT NULL PRIMARY KEY, data TEXT)")
+            db.execSQL("INSERT INTO Foo (id, data) VALUES (1, 'bar')")
+        }
+
+        FrameworkSQLiteOpenHelper(
+            context,
+            dbName,
+            object : SupportSQLiteOpenHelper.Callback(10) {
+                override fun onCreate(db: SupportSQLiteDatabase) {}
+
+                override fun onUpgrade(
+                    db: SupportSQLiteDatabase,
+                    oldVersion: Int,
+                    newVersion: Int
+                ) {
+                    // Do a query, this query will get cached, but we expect it to get evicted if
+                    // androidx.sqlite workarounds this issue by reducing the cache size.
+                    db.query("SELECT * FROM Foo").let { c ->
+                        assertThat(c.moveToNext()).isTrue()
+                        assertThat(c.getString(1)).isEqualTo("bar")
+                        c.close()
+                    }
+                    // Alter table, specifically make it so that using a cached query will be
+                    // troublesome.
+                    db.execSQL("ALTER TABLE Foo RENAME TO Foo_old")
+                    db.execSQL("CREATE TABLE Foo (id INTEGER NOT NULL PRIMARY KEY)")
+                    // Do an irrelevant query to evict the last SELECT statement, sadly this is
+                    // required because we can only reduce the cache size to 1, and only SELECT or
+                    // UPDATE statement are cache.
+                    // See frameworks/base/core/java/android/database/sqlite/SQLiteConnection.java;l=1209
+                    db.query("SELECT * FROM Foo_old").close()
+                    // Do earlier query, checking it is not cached
+                    db.query("SELECT * FROM Foo").let { c ->
+                        assertThat(c.columnNames.toList()).containsExactly("id")
+                        assertThat(c.count).isEqualTo(0)
+                        c.close()
+                    }
+                }
+            },
+            useNoBackupDirectory = false,
+            allowDataLossOnRecovery = false
+        ).writableDatabase.close()
+    }
 }
\ No newline at end of file
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.kt b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.kt
index be7b101..65adc7e 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.kt
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteOpenHelper.kt
@@ -257,6 +257,13 @@
         }
 
         override fun onConfigure(db: SQLiteDatabase) {
+            if (!migrated && callback.version != db.version) {
+                // Reduce the prepared statement cache to the minimum allowed (1) to avoid
+                // issues with queries executed during migrations. Note that when a migration is
+                // done the connection is closed and re-opened to avoid stale connections, which
+                // in turns resets the cache max size. See b/271083856
+                db.setMaxSqlCacheSize(1)
+            }
             try {
                 callback.onConfigure(getWrappedDb(db))
             } catch (t: Throwable) {
diff --git a/text/text/src/androidTest/java/androidx/compose/ui/text/android/FontPaddingTest.kt b/text/text/src/androidTest/java/androidx/compose/ui/text/android/FontPaddingTest.kt
index f7e466f..d84f481 100644
--- a/text/text/src/androidTest/java/androidx/compose/ui/text/android/FontPaddingTest.kt
+++ b/text/text/src/androidTest/java/androidx/compose/ui/text/android/FontPaddingTest.kt
@@ -18,10 +18,8 @@
 
 import android.graphics.Typeface
 import android.text.TextPaint
-import android.os.Build
 import androidx.core.content.res.ResourcesCompat
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.fonts.R
@@ -181,12 +179,7 @@
     }
 
     @Test
-    @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun tallTypefaceTextIsTwiceTheHeightOfLatinTypefaceTextMultiLine() {
-        if (Build.VERSION.SDK_INT == 33 && Build.VERSION.CODENAME != "REL") {
-            return // b/262909049: Do not run this test on pre-release Android U.
-        }
-
         val latinLayout = TextLayout(latinTextMultiLine, typeface = latinTypeface)
         val tallLayout = TextLayout(tallTextMultiLine, typeface = tallTypeface)
 
@@ -212,7 +205,8 @@
             charSequence = text,
             textPaint = textPaint,
             includePadding = includePadding,
-            fallbackLineSpacing = false
+            fallbackLineSpacing = false,
+            width = fontSize * 1.5f
         )
     }
-}
\ No newline at end of file
+}
diff --git a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutTest.kt b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutTest.kt
index 2621b1e..4de574f 100644
--- a/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutTest.kt
+++ b/text/text/src/androidTest/java/androidx/compose/ui/text/android/TextLayoutTest.kt
@@ -53,7 +53,7 @@
 
     @Test
     fun constructor_default_values() {
-        val textLayout = TextLayout(charSequence = "", textPaint = TextPaint())
+        val textLayout = TextLayout(charSequence = "", textPaint = TextPaint(), width = 0f)
         val frameworkLayout = textLayout.layout
 
         assertThat(frameworkLayout.width).isEqualTo(0)
diff --git a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
index cb51b971..d55d854 100644
--- a/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
+++ b/text/text/src/main/java/androidx/compose/ui/text/android/TextLayout.kt
@@ -114,7 +114,7 @@
 @InternalPlatformTextApi
 class TextLayout constructor(
     charSequence: CharSequence,
-    width: Float = 0.0f,
+    width: Float,
     textPaint: TextPaint,
     @TextLayoutAlignment alignment: Int = DEFAULT_ALIGNMENT,
     ellipsize: TextUtils.TruncateAt? = null,
diff --git a/tracing/tracing-ktx/api/1.2.0-beta02.txt b/tracing/tracing-ktx/api/1.2.0-beta02.txt
new file mode 100644
index 0000000..13d99d1
--- /dev/null
+++ b/tracing/tracing-ktx/api/1.2.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class TraceKt {
+    method public static inline <T> T! trace(String label, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static inline <T> T! trace(kotlin.jvm.functions.Function0<java.lang.String> lazyLabel, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static suspend inline <T> Object? traceAsync(String methodName, int cookie, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+    method public static inline <T> T! traceAsync(kotlin.jvm.functions.Function0<java.lang.String> lazyMethodName, kotlin.jvm.functions.Function0<java.lang.Integer> lazyCookie, kotlin.jvm.functions.Function0<? extends T> block);
+  }
+
+}
+
diff --git a/tracing/tracing-ktx/api/public_plus_experimental_1.2.0-beta02.txt b/tracing/tracing-ktx/api/public_plus_experimental_1.2.0-beta02.txt
new file mode 100644
index 0000000..13d99d1
--- /dev/null
+++ b/tracing/tracing-ktx/api/public_plus_experimental_1.2.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class TraceKt {
+    method public static inline <T> T! trace(String label, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static inline <T> T! trace(kotlin.jvm.functions.Function0<java.lang.String> lazyLabel, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static suspend inline <T> Object? traceAsync(String methodName, int cookie, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+    method public static inline <T> T! traceAsync(kotlin.jvm.functions.Function0<java.lang.String> lazyMethodName, kotlin.jvm.functions.Function0<java.lang.Integer> lazyCookie, kotlin.jvm.functions.Function0<? extends T> block);
+  }
+
+}
+
diff --git a/tracing/tracing-ktx/api/res-1.2.0-beta02.txt b/tracing/tracing-ktx/api/res-1.2.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tracing/tracing-ktx/api/res-1.2.0-beta02.txt
diff --git a/tracing/tracing-ktx/api/restricted_1.2.0-beta02.txt b/tracing/tracing-ktx/api/restricted_1.2.0-beta02.txt
new file mode 100644
index 0000000..13d99d1
--- /dev/null
+++ b/tracing/tracing-ktx/api/restricted_1.2.0-beta02.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class TraceKt {
+    method public static inline <T> T! trace(String label, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static inline <T> T! trace(kotlin.jvm.functions.Function0<java.lang.String> lazyLabel, kotlin.jvm.functions.Function0<? extends T> block);
+    method public static suspend inline <T> Object? traceAsync(String methodName, int cookie, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T>);
+    method public static inline <T> T! traceAsync(kotlin.jvm.functions.Function0<java.lang.String> lazyMethodName, kotlin.jvm.functions.Function0<java.lang.Integer> lazyCookie, kotlin.jvm.functions.Function0<? extends T> block);
+  }
+
+}
+
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
index 149d7e0..6b374c9 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
@@ -25,7 +25,7 @@
 // Concept of version useful e.g. for human-readable error messages, and stable once released.
 // Does not replace the need for a binary verification mechanism (e.g. checksum check).
 // TODO: populate using CMake
-#define VERSION "1.0.0-alpha12"
+#define VERSION "1.0.0-alpha13"
 
 namespace tracing_perfetto {
     void RegisterWithPerfetto() {
diff --git a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
index 13e2f1d..7611798 100644
--- a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
+++ b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
@@ -30,7 +30,7 @@
         init {
             PerfettoNative.loadLib()
         }
-        const val libraryVersion = "1.0.0-alpha12" // TODO: get using reflection
+        const val libraryVersion = "1.0.0-alpha13" // TODO: get using reflection
     }
 
     @Test
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index 9ce873b..3ab9718 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -25,12 +25,12 @@
 
     // TODO(224510255): load from a file produced at build time
     object Metadata {
-        const val version = "1.0.0-alpha12"
+        const val version = "1.0.0-alpha13"
         val checksums = mapOf(
-            "arm64-v8a" to "bff106aafe19364627e77bca7f7f658441e35fdd61f9a519f5f814742bd22803",
-            "armeabi-v7a" to "b128da6ed9b69b0db7fa65215c50709d0b1bb5a73c697b4b292cb7326f263c2f",
-            "x86" to "534325ba590cb4f5775c0e0f31fb41521c7a212096f8263b1a1015962799678b",
-            "x86_64" to "56efe76f73b7c4635a42df5037d6ff5f39ddec239893ba716b37e05f6fe3afa3",
+            "arm64-v8a" to "3513a43f87bb7b455a626fbbe3e0f2b65585ad929d81825ce7126fcfe250a6bb",
+            "armeabi-v7a" to "de02d5fcc0f2cfacb2376f18836326886f23cd3df7359d9e0edae93bd6e3e7c8",
+            "x86" to "4ae9b924dc06ac16b0cc591b7bd61197f51efcbfebc0909fdf78778fef1f715c",
+            "x86_64" to "793b66df2d387626f005e15a4ab564acb6e32cd33dc17ec58e46e4fef05326c2",
         )
     }
 
diff --git a/tracing/tracing/api/1.2.0-beta02.txt b/tracing/tracing/api/1.2.0-beta02.txt
new file mode 100644
index 0000000..c883da2
--- /dev/null
+++ b/tracing/tracing/api/1.2.0-beta02.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class Trace {
+    method public static void beginAsyncSection(String, int);
+    method public static void beginSection(String);
+    method public static void endAsyncSection(String, int);
+    method public static void endSection();
+    method public static void forceEnableAppTracing();
+    method public static boolean isEnabled();
+    method public static void setCounter(String, int);
+  }
+
+}
+
diff --git a/tracing/tracing/api/public_plus_experimental_1.2.0-beta02.txt b/tracing/tracing/api/public_plus_experimental_1.2.0-beta02.txt
new file mode 100644
index 0000000..c883da2
--- /dev/null
+++ b/tracing/tracing/api/public_plus_experimental_1.2.0-beta02.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class Trace {
+    method public static void beginAsyncSection(String, int);
+    method public static void beginSection(String);
+    method public static void endAsyncSection(String, int);
+    method public static void endSection();
+    method public static void forceEnableAppTracing();
+    method public static boolean isEnabled();
+    method public static void setCounter(String, int);
+  }
+
+}
+
diff --git a/tracing/tracing/api/res-1.2.0-beta02.txt b/tracing/tracing/api/res-1.2.0-beta02.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tracing/tracing/api/res-1.2.0-beta02.txt
diff --git a/tracing/tracing/api/restricted_1.2.0-beta02.txt b/tracing/tracing/api/restricted_1.2.0-beta02.txt
new file mode 100644
index 0000000..c883da2
--- /dev/null
+++ b/tracing/tracing/api/restricted_1.2.0-beta02.txt
@@ -0,0 +1,15 @@
+// Signature format: 4.0
+package androidx.tracing {
+
+  public final class Trace {
+    method public static void beginAsyncSection(String, int);
+    method public static void beginSection(String);
+    method public static void endAsyncSection(String, int);
+    method public static void endSection();
+    method public static void forceEnableAppTracing();
+    method public static boolean isEnabled();
+    method public static void setCounter(String, int);
+  }
+
+}
+
diff --git a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
index e04fdbc..217c891 100644
--- a/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
+++ b/transition/transition/src/main/java/androidx/transition/FragmentTransitionSupport.java
@@ -35,7 +35,6 @@
 
 
 /**
- * @hide
  */
 // This is instantiated in androidx.fragment.app.FragmentTransition
 @SuppressWarnings("unused")
diff --git a/transition/transition/src/main/java/androidx/transition/Slide.java b/transition/transition/src/main/java/androidx/transition/Slide.java
index c707b97..ae54f98 100644
--- a/transition/transition/src/main/java/androidx/transition/Slide.java
+++ b/transition/transition/src/main/java/androidx/transition/Slide.java
@@ -59,7 +59,6 @@
     private CalculateSlide mSlideCalculator = sCalculateBottom;
     private int mSlideEdge = Gravity.BOTTOM;
 
-    /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Retention(RetentionPolicy.SOURCE)
     @IntDef({Gravity.LEFT, Gravity.TOP, Gravity.RIGHT, Gravity.BOTTOM, Gravity.START, Gravity.END})
diff --git a/transition/transition/src/main/java/androidx/transition/Transition.java b/transition/transition/src/main/java/androidx/transition/Transition.java
index 4bc389f..fc1a168 100644
--- a/transition/transition/src/main/java/androidx/transition/Transition.java
+++ b/transition/transition/src/main/java/androidx/transition/Transition.java
@@ -149,7 +149,6 @@
 
     private static final int MATCH_LAST = MATCH_ITEM_ID;
 
-    /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @IntDef({MATCH_INSTANCE, MATCH_NAME, MATCH_ID, MATCH_ITEM_ID})
     @Retention(RetentionPolicy.SOURCE)
@@ -864,7 +863,6 @@
      * This is called internally once all animations have been set up by the
      * transition hierarchy. \
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     protected void runAnimators() {
@@ -1708,7 +1706,6 @@
      * TransitionListener#onTransitionPause(Transition)} to all listeners
      * and pausing all running animators started by this transition.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     public void pause(@Nullable View sceneRoot) {
@@ -1735,7 +1732,6 @@
      * TransitionListener#onTransitionPause(Transition)} to all listeners
      * and pausing all running animators started by this transition.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     public void resume(@Nullable View sceneRoot) {
@@ -1877,7 +1873,6 @@
      * animation, and, when the animator ends, calls {@link #end()}.
      *
      * @param animator The Animator to be run during this transition.
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     protected void animate(@Nullable Animator animator) {
@@ -1910,7 +1905,6 @@
      * TransitionSet classes prior to a Transition subclass starting;
      * subclasses should not need to call it directly.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     protected void start() {
@@ -1937,7 +1931,6 @@
      * Animator and end() was called in the onAnimationEnd()
      * callback of the AnimatorListener.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     protected void end() {
@@ -1970,7 +1963,6 @@
     /**
      * Force the transition to move to its end state, ending all the animators.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     void forceToEnd(@Nullable ViewGroup sceneRoot) {
@@ -1996,7 +1988,6 @@
     /**
      * This method cancels a transition that is currently running.
      *
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     protected void cancel() {
diff --git a/transition/transition/src/main/java/androidx/transition/TransitionSet.java b/transition/transition/src/main/java/androidx/transition/TransitionSet.java
index ef268847..b1a1a3e 100644
--- a/transition/transition/src/main/java/androidx/transition/TransitionSet.java
+++ b/transition/transition/src/main/java/androidx/transition/TransitionSet.java
@@ -480,7 +480,6 @@
     }
 
     /**
-     * @hide
      */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -549,7 +548,7 @@
         }
     }
 
-    /** @hide
+    /**
      * @param sceneRoot */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -561,7 +560,7 @@
         }
     }
 
-    /** @hide
+    /**
      * @param sceneRoot */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
@@ -573,7 +572,6 @@
         }
     }
 
-    /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
     protected void cancel() {
@@ -584,7 +582,6 @@
         }
     }
 
-    /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @Override
     void forceToEnd(ViewGroup sceneRoot) {
diff --git a/transition/transition/src/main/java/androidx/transition/ViewOverlayApi14.java b/transition/transition/src/main/java/androidx/transition/ViewOverlayApi14.java
index 94f2618..e3ae7ba 100644
--- a/transition/transition/src/main/java/androidx/transition/ViewOverlayApi14.java
+++ b/transition/transition/src/main/java/androidx/transition/ViewOverlayApi14.java
@@ -279,7 +279,6 @@
         }
 
         /**
-         * @hide
          */
         @SuppressLint("BanUncheckedReflection") // This class is only used on APIs 14-17
         @RestrictTo(LIBRARY_GROUP_PREFIX)
diff --git a/transition/transition/src/main/java/androidx/transition/Visibility.java b/transition/transition/src/main/java/androidx/transition/Visibility.java
index aa582fc..a9f85bd 100644
--- a/transition/transition/src/main/java/androidx/transition/Visibility.java
+++ b/transition/transition/src/main/java/androidx/transition/Visibility.java
@@ -70,7 +70,6 @@
      */
     public static final int MODE_OUT = 0x2;
 
-    /** @hide */
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     @SuppressLint("UniqueConstants") // because MODE_IN and Fade.IN are aliases.
     @IntDef(flag = true, value = {MODE_IN, MODE_OUT, Fade.IN, Fade.OUT})
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
index 20041f0..deeafa0 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/FeaturedCarousel.kt
@@ -42,6 +42,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
@@ -88,10 +89,10 @@
 }
 
 @Composable
-fun Modifier.drawBorderOnFocus(borderColor: Color = Color.White): Modifier {
+fun Modifier.drawBorderOnFocus(borderColor: Color = Color.White, width: Dp = 5.dp): Modifier {
     var isFocused by remember { mutableStateOf(false) }
     return this
-        .border(5.dp, borderColor.copy(alpha = if (isFocused) 1f else 0.2f))
+        .border(width, borderColor.copy(alpha = if (isFocused) 1f else 0.2f))
         .onFocusChanged { isFocused = it.isFocused }
 }
 
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt
new file mode 100644
index 0000000..e36b1df
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleModalNavDrawer.kt
@@ -0,0 +1,114 @@
+/*
+ * 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.tv.integration.demos
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.shrinkHorizontally
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.DrawerValue
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.ModalNavigationDrawer
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun SampleModalDrawer() {
+    Row(Modifier.fillMaxSize()) {
+        Box(modifier = Modifier
+            .height(400.dp)
+            .width(400.dp)
+            .border(2.dp, Color.Magenta)) {
+            ModalNavigationDrawer(drawerContent = drawerContent()) {
+                Button(modifier = Modifier
+                    .height(100.dp)
+                    .fillMaxWidth(), onClick = {}) {
+                    Text("BUTTON")
+                }
+            }
+        }
+
+        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+            Box(
+                modifier = Modifier
+                    .height(400.dp)
+                    .width(400.dp)
+                    .border(2.dp, Color.Magenta)
+            ) {
+                ModalNavigationDrawer(drawerContent = drawerContent()) {
+                    Button(modifier = Modifier
+                        .height(100.dp)
+                        .fillMaxWidth(), onClick = {}) {
+                        Text("BUTTON")
+                    }
+                }
+            }
+        }
+    }
+}
+
+@Composable
+@OptIn(ExperimentalTvMaterial3Api::class)
+internal fun drawerContent(): @Composable (DrawerValue) -> Unit =
+    {
+        Column(Modifier.background(Color.Gray).fillMaxHeight()) {
+            NavigationRow(it, Color.Red, "Red")
+            NavigationRow(it, Color.Blue, "Blue")
+            NavigationRow(it, Color.Yellow, "Yellow")
+        }
+    }
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationRow(drawerValue: DrawerValue, color: Color, text: String) {
+    Row(Modifier.padding(10.dp).drawBorderOnFocus(width = 2.dp).focusable()) {
+        Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
+        AnimatedVisibility(
+            drawerValue == DrawerValue.Open,
+            // intentionally slow to test animation
+            exit = shrinkHorizontally(tween(2000))
+        ) {
+            Text(
+                text = text,
+                softWrap = false,
+                modifier = Modifier.padding(15.dp).width(50.dp),
+                textAlign = TextAlign.Center
+            )
+        }
+    }
+}
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt
new file mode 100644
index 0000000..cf1ed6a
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/SampleNavDrawer.kt
@@ -0,0 +1,72 @@
+/*
+ * 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.tv.integration.demos
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.material3.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.NavigationDrawer
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun SampleDrawer() {
+    Row(Modifier.fillMaxSize()) {
+        Box(modifier = Modifier
+            .height(400.dp)
+            .width(400.dp)
+            .border(2.dp, Color.Magenta)) {
+            NavigationDrawer(drawerContent = drawerContent()) {
+                Button(modifier = Modifier
+                    .height(100.dp)
+                    .fillMaxWidth(), onClick = {}) {
+                    Text("BUTTON")
+                }
+            }
+        }
+
+        CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+            Box(
+                modifier = Modifier
+                    .height(400.dp)
+                    .width(400.dp)
+                    .border(2.dp, Color.Magenta)
+            ) {
+                NavigationDrawer(drawerContent = drawerContent()) {
+                    Button(modifier = Modifier
+                        .height(100.dp)
+                        .fillMaxWidth(), onClick = {}) {
+                        Text("BUTTON")
+                    }
+                }
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
index 1efe4af..95a6f7e 100644
--- a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TopNavigation.kt
@@ -35,6 +35,8 @@
 import kotlinx.coroutines.delay
 
 enum class Navigation(val displayName: String, val action: @Composable () -> Unit) {
+  Drawer("Drawer", { SampleDrawer() }),
+  ModalDrawer("Modal Drawer", { SampleModalDrawer() }),
   LazyRowsAndColumns("Lazy Rows and Columns", { LazyRowsAndColumns() }),
   FeaturedCarousel("Featured Carousel", { FeaturedCarouselContent() }),
   ImmersiveList("Immersive List", { ImmersiveListContent() }),
diff --git a/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
new file mode 100644
index 0000000..a61b2f6
--- /dev/null
+++ b/tv/samples/src/main/java/androidx/tv/samples/NavigationDrawerSamples.kt
@@ -0,0 +1,93 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.samples
+
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.foundation.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.material3.Button
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.dp
+import androidx.tv.material3.DrawerValue
+import androidx.tv.material3.ExperimentalTvMaterial3Api
+import androidx.tv.material3.ModalNavigationDrawer
+import androidx.tv.material3.NavigationDrawer
+import androidx.tv.material3.Text
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun SampleNavigationDrawer() {
+    NavigationDrawer(drawerContent = drawerContent()) {
+        NonDrawerContent()
+    }
+}
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+fun SampleModalNavigationDrawer() {
+    ModalNavigationDrawer(drawerContent = drawerContent()) {
+        NonDrawerContent()
+    }
+}
+
+@Composable
+private fun NonDrawerContent() {
+    Button(modifier = Modifier
+        .height(100.dp)
+        .fillMaxWidth(), onClick = {}) {
+        Text("BUTTON")
+    }
+}
+
+@Composable
+@OptIn(ExperimentalTvMaterial3Api::class)
+private fun drawerContent(): @Composable (DrawerValue) -> Unit =
+    {
+        Column(Modifier.background(Color.Gray).fillMaxHeight()) {
+            NavigationRow(it, Color.Red, "Red")
+            NavigationRow(it, Color.Blue, "Blue")
+            NavigationRow(it, Color.Yellow, "Yellow")
+        }
+    }
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+@Composable
+private fun NavigationRow(drawerValue: DrawerValue, color: Color, text: String) {
+    Row(Modifier.padding(10.dp).focusable()) {
+        Box(Modifier.size(50.dp).background(color).padding(end = 20.dp))
+        AnimatedVisibility(visible = drawerValue == DrawerValue.Open) {
+            Text(
+                text = text,
+                softWrap = false,
+                modifier = Modifier.padding(15.dp).width(50.dp),
+                textAlign = TextAlign.Center
+            )
+        }
+    }
+}
\ No newline at end of file
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index 7b1c9eb..38f62bb 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -166,6 +166,26 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class DrawerState {
+    ctor public DrawerState(optional androidx.tv.material3.DrawerValue initialValue);
+    method public androidx.tv.material3.DrawerValue getCurrentValue();
+    method public void setValue(androidx.tv.material3.DrawerValue drawerValue);
+    property public final androidx.tv.material3.DrawerValue currentValue;
+    field public static final androidx.tv.material3.DrawerState.Companion Companion;
+  }
+
+  public static final class DrawerState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.tv.material3.DrawerState,androidx.tv.material3.DrawerValue> getSaver();
+    property public final androidx.compose.runtime.saveable.Saver<androidx.tv.material3.DrawerState,androidx.tv.material3.DrawerValue> Saver;
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public enum DrawerValue {
+    method public static androidx.tv.material3.DrawerValue valueOf(String value) throws java.lang.IllegalArgumentException, java.lang.NullPointerException;
+    method public static androidx.tv.material3.DrawerValue[] values();
+    enum_constant public static final androidx.tv.material3.DrawerValue Closed;
+    enum_constant public static final androidx.tv.material3.DrawerValue Open;
+  }
+
   @kotlin.RequiresOptIn(message="This tv-material API is experimental and likely to change or be removed in the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalTvMaterial3Api {
   }
 
@@ -189,8 +209,14 @@
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
   }
 
+  public final class IconKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Icon(androidx.compose.ui.graphics.vector.ImageVector imageVector, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Icon(androidx.compose.ui.graphics.ImageBitmap bitmap, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Icon(androidx.compose.ui.graphics.painter.Painter painter, String? contentDescription, optional androidx.compose.ui.Modifier modifier, optional long tint);
+  }
+
   @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ImmersiveListBackgroundScope implements androidx.compose.foundation.layout.BoxScope {
-    method @androidx.compose.animation.ExperimentalAnimationApi @androidx.compose.runtime.Composable public void AnimatedContent(int targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentScope<java.lang.Integer>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super java.lang.Integer,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable public void AnimatedContent(int targetState, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedContentTransitionScope<java.lang.Integer>,androidx.compose.animation.ContentTransform> transitionSpec, optional androidx.compose.ui.Alignment contentAlignment, kotlin.jvm.functions.Function2<? super androidx.compose.animation.AnimatedVisibilityScope,? super java.lang.Integer,kotlin.Unit> content);
     method @androidx.compose.runtime.Composable public void AnimatedVisibility(boolean visible, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.animation.EnterTransition enter, optional androidx.compose.animation.ExitTransition exit, optional String label, kotlin.jvm.functions.Function1<? super androidx.compose.animation.AnimatedVisibilityScope,kotlin.Unit> content);
   }
 
@@ -228,6 +254,12 @@
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void MaterialTheme(optional androidx.tv.material3.ColorScheme colorScheme, optional androidx.tv.material3.Shapes shapes, optional androidx.tv.material3.Typography typography, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
+  public final class NavigationDrawerKt {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void ModalNavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, optional long scrimColor, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void NavigationDrawer(kotlin.jvm.functions.Function1<? super androidx.tv.material3.DrawerValue,kotlin.Unit> drawerContent, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.DrawerState drawerState, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static androidx.tv.material3.DrawerState rememberDrawerState(androidx.tv.material3.DrawerValue initialValue);
+  }
+
   @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ScaleIndication implements androidx.compose.foundation.Indication {
     ctor public ScaleIndication(float scale);
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.IndicationInstance rememberUpdatedInstance(androidx.compose.foundation.interaction.InteractionSource interactionSource);
@@ -267,7 +299,8 @@
   }
 
   public final class SurfaceKt {
-    method @androidx.compose.runtime.Composable @androidx.compose.runtime.NonRestartableComposable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ClickableSurfaceShape shape, optional androidx.tv.material3.ClickableSurfaceColor color, optional androidx.tv.material3.ClickableSurfaceColor contentColor, optional androidx.tv.material3.ClickableSurfaceScale scale, optional androidx.tv.material3.ClickableSurfaceBorder border, optional androidx.tv.material3.ClickableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(kotlin.jvm.functions.Function0<kotlin.Unit> onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ClickableSurfaceShape shape, optional androidx.tv.material3.ClickableSurfaceColor color, optional androidx.tv.material3.ClickableSurfaceColor contentColor, optional androidx.tv.material3.ClickableSurfaceScale scale, optional androidx.tv.material3.ClickableSurfaceBorder border, optional androidx.tv.material3.ClickableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Surface(boolean checked, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> onCheckedChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional float tonalElevation, optional androidx.tv.material3.ToggleableSurfaceShape shape, optional androidx.tv.material3.ToggleableSurfaceColor color, optional androidx.tv.material3.ToggleableSurfaceColor contentColor, optional androidx.tv.material3.ToggleableSurfaceScale scale, optional androidx.tv.material3.ToggleableSurfaceBorder border, optional androidx.tv.material3.ToggleableSurfaceGlow glow, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
     method public static androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> getLocalAbsoluteTonalElevation();
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.unit.Dp> LocalAbsoluteTonalElevation;
   }
@@ -307,6 +340,31 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceBorder {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceColor {
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceDefaults {
+    method public androidx.tv.material3.ToggleableSurfaceBorder border(optional androidx.tv.material3.Border border, optional androidx.tv.material3.Border focusedBorder, optional androidx.tv.material3.Border pressedBorder, optional androidx.tv.material3.Border selectedBorder, optional androidx.tv.material3.Border disabledBorder, optional androidx.tv.material3.Border focusedSelectedBorder, optional androidx.tv.material3.Border focusedDisabledBorder, optional androidx.tv.material3.Border pressedSelectedBorder, optional androidx.tv.material3.Border selectedDisabledBorder, optional androidx.tv.material3.Border focusedSelectedDisabledBorder);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ToggleableSurfaceColor color(optional long color, optional long focusedColor, optional long pressedColor, optional long selectedColor, optional long disabledColor, optional long focusedSelectedColor, optional long pressedSelectedColor);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ToggleableSurfaceColor contentColor(optional long color, optional long focusedColor, optional long pressedColor, optional long selectedColor, optional long disabledColor, optional long focusedSelectedColor, optional long pressedSelectedColor);
+    method public androidx.tv.material3.ToggleableSurfaceGlow glow(optional androidx.tv.material3.Glow glow, optional androidx.tv.material3.Glow focusedGlow, optional androidx.tv.material3.Glow pressedGlow, optional androidx.tv.material3.Glow selectedGlow, optional androidx.tv.material3.Glow focusedSelectedGlow, optional androidx.tv.material3.Glow pressedSelectedGlow);
+    method public androidx.tv.material3.ToggleableSurfaceScale scale(optional float scale, optional float focusedScale, optional float pressedScale, optional float selectedScale, optional float disabledScale, optional float focusedSelectedScale, optional float focusedDisabledScale, optional float pressedSelectedScale, optional float selectedDisabledScale, optional float focusedSelectedDisabledScale);
+    method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ToggleableSurfaceShape shape(optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.ui.graphics.Shape focusedShape, optional androidx.compose.ui.graphics.Shape pressedShape, optional androidx.compose.ui.graphics.Shape selectedShape, optional androidx.compose.ui.graphics.Shape disabledShape, optional androidx.compose.ui.graphics.Shape focusedSelectedShape, optional androidx.compose.ui.graphics.Shape focusedDisabledShape, optional androidx.compose.ui.graphics.Shape pressedSelectedShape, optional androidx.compose.ui.graphics.Shape selectedDisabledShape, optional androidx.compose.ui.graphics.Shape focusedSelectedDisabledShape);
+    field public static final androidx.tv.material3.ToggleableSurfaceDefaults INSTANCE;
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceGlow {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceScale {
+  }
+
+  @androidx.compose.runtime.Immutable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class ToggleableSurfaceShape {
+  }
+
   @androidx.compose.runtime.Immutable public final class Typography {
     ctor public Typography(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
     method public androidx.tv.material3.Typography copy(optional androidx.compose.ui.text.TextStyle displayLarge, optional androidx.compose.ui.text.TextStyle displayMedium, optional androidx.compose.ui.text.TextStyle displaySmall, optional androidx.compose.ui.text.TextStyle headlineLarge, optional androidx.compose.ui.text.TextStyle headlineMedium, optional androidx.compose.ui.text.TextStyle headlineSmall, optional androidx.compose.ui.text.TextStyle titleLarge, optional androidx.compose.ui.text.TextStyle titleMedium, optional androidx.compose.ui.text.TextStyle titleSmall, optional androidx.compose.ui.text.TextStyle bodyLarge, optional androidx.compose.ui.text.TextStyle bodyMedium, optional androidx.compose.ui.text.TextStyle bodySmall, optional androidx.compose.ui.text.TextStyle labelLarge, optional androidx.compose.ui.text.TextStyle labelMedium, optional androidx.compose.ui.text.TextStyle labelSmall);
diff --git a/tv/tv-material/build.gradle b/tv/tv-material/build.gradle
index 11a2550..2978d14 100644
--- a/tv/tv-material/build.gradle
+++ b/tv/tv-material/build.gradle
@@ -30,12 +30,13 @@
     def composeVersion = '1.3.0-rc01'
 
     api("androidx.annotation:annotation:1.5.0")
-    api("androidx.compose.animation:animation:$composeVersion")
     api("androidx.compose.runtime:runtime:$composeVersion")
 
     api(project(":compose:ui:ui"))
+    api(project(":compose:animation:animation"))
     api("androidx.compose.foundation:foundation:$composeVersion")
     api("androidx.compose.foundation:foundation-layout:$composeVersion")
+    api("androidx.compose.material:material-icons-core:$composeVersion")
     api("androidx.compose.ui:ui-graphics:$composeVersion")
     api("androidx.compose.ui:ui-text:$composeVersion")
     api("androidx.compose.ui:ui-util:$composeVersion")
@@ -49,6 +50,7 @@
     androidTestImplementation(project(":compose:ui:ui-test"))
     androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
+    androidTestImplementation(project(":test:screenshot:screenshot"))
     androidTestImplementation(libs.testRunner)
 
     samples(project(":tv:tv-samples"))
@@ -59,6 +61,8 @@
     defaultConfig {
         minSdkVersion 21
     }
+    sourceSets.androidTest.assets.srcDirs +=
+             project.rootDir.absolutePath + "/../../golden/tv/compose/material3"
 }
 
 androidx {
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/GoldenCommon.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/GoldenCommon.kt
new file mode 100644
index 0000000..49b8130
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/GoldenCommon.kt
@@ -0,0 +1,19 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+internal const val TV_GOLDEN_MATERIAL3 = "tv/compose/material3"
\ No newline at end of file
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt
new file mode 100644
index 0000000..68299e8
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/IconTest.kt
@@ -0,0 +1,319 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@OptIn(ExperimentalTvMaterial3Api::class)
+@RunWith(AndroidJUnit4::class)
+class IconTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun vector_materialIconSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val vector = Icons.Filled.Menu
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                Icon(vector, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun vector_customIconSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+        val vector = ImageVector.Builder(
+            defaultWidth = width, defaultHeight = height,
+            viewportWidth = width.value, viewportHeight = height.value
+        ).build()
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                Icon(vector, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                Icon(image, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                Icon(image, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val painter = ColorPainter(Color.Red)
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                Icon(painter, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                val bitmapPainter = BitmapPainter(image)
+                Icon(bitmapPainter, null)
+            }
+        }
+
+        rule
+            .onNodeWithTag(testTag)
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconScalesToFitSize() {
+        // Image with intrinsic size of 24dp
+        val width = 24.dp
+        val height = 24.dp
+        val testTag = "testTag"
+        var expectedIntSize: IntSize? = null
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(
+                    image,
+                    null,
+                    // Force Icon to be 50dp
+                    modifier = Modifier.requiredSize(50.dp),
+                    tint = Color.Unspecified
+                )
+                with(LocalDensity.current) {
+                    val dimension = 50.dp.roundToPx()
+                    expectedIntSize = IntSize(dimension, dimension)
+                }
+            }
+        }
+
+        rule.onNodeWithTag(testTag)
+            .captureToImage()
+            // The icon should be 50x50 and fill the whole size with red pixels
+            .assertPixels(expectedSize = expectedIntSize!!) {
+                Color.Red
+            }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconUnspecifiedTintColorIgnored() {
+        val width = 35.dp
+        val height = 83.dp
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(image, null, tint = Color.Unspecified)
+            }
+        }
+
+        // With no color provided for a tint, the icon should render the original pixels
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Red }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconSpecifiedTintColorApplied() {
+        val width = 35.dp
+        val height = 83.dp
+        val testTag = "testTag"
+
+        rule.setContent {
+            Box(Modifier.testTag(testTag)) {
+                val image: ImageBitmap
+                with(LocalDensity.current) {
+                    image = createBitmapWithColor(
+                        this,
+                        width.roundToPx(),
+                        height.roundToPx(),
+                        Color.Red
+                    )
+                }
+                Icon(image, null, tint = Color.Blue)
+            }
+        }
+
+        // With a tint color provided, all pixels should be blue
+        rule.onNodeWithTag(testTag).captureToImage().assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun defaultSemanticsWhenContentDescriptionProvided() {
+        val testTag = "TestTag"
+        rule.setContent {
+            Icon(
+                bitmap = ImageBitmap(100, 100),
+                contentDescription = "qwerty",
+                modifier = Modifier.testTag(testTag)
+            )
+        }
+
+        rule.onNodeWithTag(testTag)
+            .assertContentDescriptionEquals("qwerty")
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
+    }
+
+    private fun createBitmapWithColor(
+        density: Density,
+        width: Int,
+        height: Int,
+        color: Color
+    ): ImageBitmap {
+        val size = Size(width.toFloat(), height.toFloat())
+        val image = ImageBitmap(width, height)
+        CanvasDrawScope().draw(
+            density,
+            LayoutDirection.Ltr,
+            Canvas(image),
+            size
+        ) {
+            drawRect(color)
+        }
+        return image
+    }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt
new file mode 100644
index 0000000..d0f0fda
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/ModalNavigationDrawerTest.kt
@@ -0,0 +1,315 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toSize
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+class ModalNavigationDrawerTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun modalNavigationDrawer_initialStateClosed_closedStateComposableDisplayed() {
+        rule.setContent {
+            ModalNavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Closed) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }
+            ) { Box(Modifier.size(200.dp)) }
+        }
+
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun modalNavigationDrawer_initialStateOpen_openStateComposableDisplayed() {
+        rule.setContent {
+            ModalNavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Open) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }) { BasicText("other content") }
+        }
+
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun modalNavigationDrawer_focusInsideDrawer_openedStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val drawerFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            ModalNavigationDrawer(
+                modifier = Modifier.focusRequester(drawerFocusRequester),
+                drawerState = navigationDrawerValue,
+                drawerContent = {
+                    BasicText(
+                        text =
+                        if (it == DrawerValue.Open) "Opened" else "Closed"
+                    )
+                }) { BasicText("other content") }
+        }
+
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+
+        rule.runOnIdle {
+            drawerFocusRequester.requestFocus()
+        }
+
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
+    @Test
+    fun modalNavigationDrawer_focusMovesOutOfDrawer_closedStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val drawerFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            Row {
+                ModalNavigationDrawer(
+                    modifier = Modifier.focusRequester(drawerFocusRequester).focusable(false),
+                    drawerState = navigationDrawerValue,
+                    drawerContent = {
+                        BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                    }) {
+                    Box(modifier = Modifier.focusable()) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            drawerFocusRequester.requestFocus()
+        }
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+        rule.onRoot().performKeyInput { pressKey(Key.DirectionRight) }
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class, ExperimentalTestApi::class)
+    @Test
+    fun modalNavigationDrawer_focusMovesIntoDrawer_openStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val buttonFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            Row {
+                ModalNavigationDrawer(
+                    drawerState = navigationDrawerValue,
+                    drawerContent = {
+                        BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                    }) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(buttonFocusRequester)
+                            .focusable()
+                    ) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            buttonFocusRequester.requestFocus()
+        }
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+        rule.onRoot().performKeyInput { pressKey(Key.DirectionLeft) }
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun modalNavigationDrawer_closedState_widthOfDrawerIsWidthOfContent() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val totalWidth = 100.dp
+        val closedDrawerContentWidth = 30.dp
+        val expectedContentWidth = totalWidth - closedDrawerContentWidth
+        rule.setContent {
+            Box(modifier = Modifier.width(totalWidth)) {
+                NavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.width(closedDrawerContentWidth)) {
+                            // extra long content wrapped in a drawer-width restricting box
+                            Box(Modifier.width(closedDrawerContentWidth * 10))
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        rule.onNodeWithTag(contentWidthBoxTag).assertWidthIsEqualTo(expectedContentWidth)
+    }
+
+    @Test
+    fun modalNavigationDrawer_openState_widthOfDrawerIsWidthOfContent() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val totalWidth = 100.dp
+        val openDrawerContentWidth = 70.dp
+        val expectedContentWidth = totalWidth - openDrawerContentWidth
+        rule.setContent {
+            Box(modifier = Modifier.width(totalWidth)) {
+                NavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.width(openDrawerContentWidth)) {
+                            Box(Modifier.width(openDrawerContentWidth * 10))
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        rule.onNodeWithTag(contentWidthBoxTag).assertWidthIsEqualTo(expectedContentWidth)
+    }
+
+    @Test
+    fun modalNavigationDrawer_rtl_drawerIsDrawnAtTheStart() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val drawerContentBoxTag = "drawerContentBox"
+        rule.setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                ModalNavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.testTag(drawerContentBoxTag).border(2.dp, Color.Red)) {
+                            BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        val rightEdgeOfRoot = rule.onRoot().getUnclippedBoundsInRoot().right
+        rule.onNodeWithTag(drawerContentBoxTag).assertRightPositionInRootIsEqualTo(rightEdgeOfRoot)
+    }
+
+    @Test
+    fun modalNavigationDrawer_rtl_drawerExpandsTowardsEnd() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val drawerContentBoxTag = "drawerContentBox"
+        var drawerState: DrawerState? = null
+        rule.setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                drawerState = remember { DrawerState(DrawerValue.Closed) }
+                ModalNavigationDrawer(
+                    drawerState = drawerState!!,
+                    drawerContent = {
+                        Box(Modifier.testTag(drawerContentBoxTag).border(2.dp, Color.Red)) {
+                            BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        val endPositionInClosedState =
+            rule.onNodeWithTag(drawerContentBoxTag).getUnclippedBoundsInRoot().left
+
+        rule.runOnIdle { drawerState?.setValue(DrawerValue.Open) }
+        val endPositionInOpenState =
+            rule.onNodeWithTag(drawerContentBoxTag).getUnclippedBoundsInRoot().left
+
+        assert(endPositionInClosedState.value > endPositionInOpenState.value)
+    }
+
+    private fun SemanticsNodeInteractionCollection.assertAnyAreDisplayed() {
+        val result = (0 until fetchSemanticsNodes().size).map { get(it) }.any {
+            try {
+                it.assertIsDisplayed()
+                true
+            } catch (e: AssertionError) {
+                false
+            }
+        }
+
+        if (!result) throw AssertionError("Assert failed: None of the components are displayed!")
+    }
+
+    private fun SemanticsNodeInteraction.assertRightPositionInRootIsEqualTo(
+        expectedRight: Dp
+    ): SemanticsNodeInteraction {
+        return withUnclippedBoundsInRoot {
+            it.right.assertIsEqualTo(expectedRight, "right")
+        }
+    }
+
+    private fun SemanticsNodeInteraction.withUnclippedBoundsInRoot(
+        assertion: (DpRect) -> Unit
+    ): SemanticsNodeInteraction {
+        val node = fetchSemanticsNode("Failed to retrieve bounds of the node.")
+        val bounds = with(node.layoutInfo.density) {
+            node.unclippedBoundsInRoot.let {
+                DpRect(it.left.toDp(), it.top.toDp(), it.right.toDp(), it.bottom.toDp())
+            }
+        }
+        assertion.invoke(bounds)
+        return this
+    }
+
+    private val SemanticsNode.unclippedBoundsInRoot: Rect
+        get() {
+            return if (layoutInfo.isPlaced) {
+                Rect(positionInRoot, size.toSize())
+            } else {
+                Dp.Unspecified.value.let { Rect(it, it, it, it) }
+            }
+        }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerTest.kt
new file mode 100644
index 0000000..f414f64
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/NavigationDrawerTest.kt
@@ -0,0 +1,346 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.input.key.Key
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsNode
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsNodeInteraction
+import androidx.compose.ui.test.SemanticsNodeInteractionCollection
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performKeyInput
+import androidx.compose.ui.test.pressKey
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpRect
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.toSize
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+
+@OptIn(ExperimentalTvMaterial3Api::class)
+class NavigationDrawerTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun navigationDrawer_initialStateClosed_closedStateComposableDisplayed() {
+        rule.setContent {
+            NavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Closed) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }
+            ) { Box(Modifier.size(200.dp)) }
+        }
+
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun navigationDrawer_initialStateOpen_openStateComposableDisplayed() {
+        rule.setContent {
+            NavigationDrawer(
+                drawerState = remember { DrawerState(DrawerValue.Open) },
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }) { BasicText("other content") }
+        }
+
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun navigationDrawer_focusInsideDrawer_openedStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val drawerFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            NavigationDrawer(
+                modifier = Modifier.focusRequester(drawerFocusRequester),
+                drawerState = navigationDrawerValue,
+                drawerContent = {
+                    BasicText(
+                        text =
+                        if (it == DrawerValue.Open) "Opened" else "Closed"
+                    )
+                }) { BasicText("other content") }
+        }
+
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+
+        rule.runOnIdle {
+            drawerFocusRequester.requestFocus()
+        }
+
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
+    @Test
+    fun navigationDrawer_focusMovesOutOfDrawer_closedStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val drawerFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            Row {
+                NavigationDrawer(
+                    modifier = Modifier.focusRequester(drawerFocusRequester),
+                    drawerState = navigationDrawerValue,
+                    drawerContent = {
+                        BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                    }) {
+                    Box(modifier = Modifier.focusable()) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            drawerFocusRequester.requestFocus()
+        }
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+        rule.onRoot().performKeyInput { pressKey(Key.DirectionRight) }
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class, ExperimentalTestApi::class)
+    @Test
+    fun navigationDrawer_focusMovesIntoDrawer_openStateComposableDisplayed() {
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val buttonFocusRequester = FocusRequester()
+        rule.setContent {
+            val navigationDrawerValue = remember { DrawerState(DrawerValue.Closed) }
+            Row {
+                NavigationDrawer(
+                    drawerState = navigationDrawerValue,
+                    drawerContent = {
+                        BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                    }) {
+                    Box(
+                        modifier = Modifier
+                            .focusRequester(buttonFocusRequester)
+                            .focusable()
+                    ) {
+                        BasicText("Button")
+                    }
+                }
+            }
+        }
+        rule.runOnIdle {
+            buttonFocusRequester.requestFocus()
+        }
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+        rule.onRoot().performKeyInput { pressKey(Key.DirectionLeft) }
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    @Test
+    fun navigationDrawer_closedState_widthOfDrawerIsWidthOfContent() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val totalWidth = 100.dp
+        val closedDrawerContentWidth = 30.dp
+        val expectedContentWidth = totalWidth - closedDrawerContentWidth
+        rule.setContent {
+            Box(modifier = Modifier.width(totalWidth)) {
+                NavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.width(closedDrawerContentWidth)) {
+                            // extra long content wrapped in a drawer-width restricting box
+                            Box(Modifier.width(closedDrawerContentWidth * 10))
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        rule.onNodeWithTag(contentWidthBoxTag).assertWidthIsEqualTo(expectedContentWidth)
+    }
+
+    @Test
+    fun navigationDrawer_openState_widthOfDrawerIsWidthOfContent() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val totalWidth = 100.dp
+        val openDrawerContentWidth = 70.dp
+        val expectedContentWidth = totalWidth - openDrawerContentWidth
+        rule.setContent {
+            Box(modifier = Modifier.width(totalWidth)) {
+                NavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.width(openDrawerContentWidth)) {
+                            Box(Modifier.width(openDrawerContentWidth * 10))
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        rule.onNodeWithTag(contentWidthBoxTag).assertWidthIsEqualTo(expectedContentWidth)
+    }
+
+    @Test
+    fun navigationDrawer_rtl_drawerIsDrawnAtTheStart() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val drawerContentBoxTag = "drawerContentBox"
+        rule.setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                NavigationDrawer(
+                    drawerState = remember { DrawerState(DrawerValue.Closed) },
+                    drawerContent = {
+                        Box(Modifier.testTag(drawerContentBoxTag).border(2.dp, Color.Red)) {
+                            BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        val rightEdgeOfRoot = rule.onRoot().getUnclippedBoundsInRoot().right
+        rule.onNodeWithTag(drawerContentBoxTag).assertRightPositionInRootIsEqualTo(rightEdgeOfRoot)
+    }
+
+    @Test
+    fun navigationDrawer_rtl_drawerExpandsTowardsEnd() {
+        val contentWidthBoxTag = "contentWidthBox"
+        val drawerContentBoxTag = "drawerContentBox"
+        var drawerState: DrawerState? = null
+        rule.setContent {
+            CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) {
+                drawerState = remember { DrawerState(DrawerValue.Closed) }
+                NavigationDrawer(
+                    drawerState = drawerState!!,
+                    drawerContent = {
+                        Box(Modifier.testTag(drawerContentBoxTag).border(2.dp, Color.Red)) {
+                            BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                        }
+                    }
+                ) { Box(Modifier.fillMaxWidth().testTag(contentWidthBoxTag)) }
+            }
+        }
+
+        val endPositionInClosedState =
+            rule.onNodeWithTag(drawerContentBoxTag).getUnclippedBoundsInRoot().left
+
+        rule.runOnIdle { drawerState?.setValue(DrawerValue.Open) }
+        val endPositionInOpenState =
+            rule.onNodeWithTag(drawerContentBoxTag).getUnclippedBoundsInRoot().left
+
+        assert(endPositionInClosedState.value > endPositionInOpenState.value)
+    }
+
+    @Test
+    fun navigationDrawerState_restoreState_remembersRecordedState() {
+        // Arrange
+        InstrumentationRegistry.getInstrumentation().setInTouchMode(false)
+        val stateRestorationTester = StateRestorationTester(rule)
+        val drawerFocusRequester = FocusRequester()
+        stateRestorationTester.setContent {
+            val navigationDrawerValue = rememberDrawerState(initialValue = DrawerValue.Closed)
+            NavigationDrawer(
+                modifier = Modifier.focusRequester(drawerFocusRequester),
+                drawerState = navigationDrawerValue,
+                drawerContent = {
+                    BasicText(text = if (it == DrawerValue.Open) "Opened" else "Closed")
+                }) { BasicText("other content") }
+        }
+
+        rule.onAllNodesWithText("Closed").assertAnyAreDisplayed()
+
+        // Act
+        rule.runOnIdle {
+            drawerFocusRequester.requestFocus()
+        }
+
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+
+        stateRestorationTester.emulateSavedInstanceStateRestore()
+        // Assert
+        rule.onAllNodesWithText("Opened").assertAnyAreDisplayed()
+    }
+
+    private fun SemanticsNodeInteractionCollection.assertAnyAreDisplayed() {
+        val result = (0 until fetchSemanticsNodes().size).map { get(it) }.any {
+            try {
+                it.assertIsDisplayed()
+                true
+            } catch (e: AssertionError) {
+                false
+            }
+        }
+
+        if (!result) throw AssertionError("Assert failed: None of the components are displayed!")
+    }
+
+    private fun SemanticsNodeInteraction.assertRightPositionInRootIsEqualTo(
+        expectedRight: Dp
+    ): SemanticsNodeInteraction {
+        return withUnclippedBoundsInRoot {
+            it.right.assertIsEqualTo(expectedRight, "right")
+        }
+    }
+
+    private fun SemanticsNodeInteraction.withUnclippedBoundsInRoot(
+        assertion: (DpRect) -> Unit
+    ): SemanticsNodeInteraction {
+        val node = fetchSemanticsNode("Failed to retrieve bounds of the node.")
+        val bounds = with(node.layoutInfo.density) {
+            node.unclippedBoundsInRoot.let {
+                DpRect(it.left.toDp(), it.top.toDp(), it.right.toDp(), it.bottom.toDp())
+            }
+        }
+        assertion.invoke(bounds)
+        return this
+    }
+
+    private val SemanticsNode.unclippedBoundsInRoot: Rect
+        get() {
+            return if (layoutInfo.isPlaced) {
+                Rect(positionInRoot, size.toSize())
+            } else {
+                Dp.Unspecified.value.let { Rect(it, it, it, it) }
+            }
+        }
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
index f9a2879..392192f 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/SurfaceTest.kt
@@ -40,15 +40,22 @@
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.composed
+import androidx.compose.ui.focus.FocusManager
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.pointer.PointerEventPass
 import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalFocusManager
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
 import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.role
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertHasClickAction
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsFocused
@@ -85,6 +92,7 @@
 )
 @MediumTest
 @RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
 class SurfaceTest {
 
     @get:Rule
@@ -214,6 +222,7 @@
         rule.setContent {
             Surface(
                 modifier = Modifier
+                    .semantics { role = Role.Tab }
                     .testTag("surface"),
                 onClick = { count.value += 1 },
             ) {
@@ -224,6 +233,7 @@
         rule.onNodeWithTag("surface")
             .performSemanticsAction(SemanticsActions.RequestFocus)
             .assertHasClickAction()
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
             .assertIsEnabled()
             // since we merge descendants we should have text on the same node
             .assertTextEquals("0")
@@ -370,7 +380,6 @@
         Truth.assertThat(hitTested.value).isTrue()
     }
 
-    @OptIn(ExperimentalTestApi::class, ExperimentalComposeUiApi::class)
     @Test
     fun clickableSurface_reactsToStateChange() {
         val interactionSource = MutableInteractionSource()
@@ -491,4 +500,370 @@
 
         surface.captureToImage().assertContainsColor(Color.Magenta)
     }
+
+    @Test
+    fun toggleable_semantics() {
+        var isChecked by mutableStateOf(false)
+        rule.setContent {
+            Surface(
+                checked = isChecked,
+                modifier = Modifier
+                    .testTag("surface"),
+                onCheckedChange = { isChecked = it }
+            ) {
+                Text("$isChecked")
+                Spacer(Modifier.size(30.toDp()))
+            }
+        }
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsProperties.Role))
+            .assertIsEnabled()
+            // since we merge descendants we should have text on the same node
+            .assertTextEquals("false")
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            .assertTextEquals("true")
+    }
+
+    @Test
+    fun toggleable_customSemantics() {
+        var isChecked by mutableStateOf(false)
+        rule.setContent {
+            Surface(
+                checked = isChecked,
+                modifier = Modifier
+                    .semantics { role = Role.Tab }
+                    .testTag("surface"),
+                onCheckedChange = { isChecked = it }
+            ) {
+                Text("$isChecked")
+                Spacer(Modifier.size(30.toDp()))
+            }
+        }
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .assertHasClickAction()
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Tab))
+            .assertIsEnabled()
+            // since we merge descendants we should have text on the same node
+            .assertTextEquals("false")
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+            .assertTextEquals("true")
+    }
+
+    @Test
+    fun toggleable_toggleAction() {
+        var isChecked by mutableStateOf(false)
+
+        rule.setContent {
+            Surface(
+                checked = isChecked,
+                modifier = Modifier
+                    .testTag("surface"),
+                onCheckedChange = { isChecked = it }
+            ) {
+                Spacer(modifier = Modifier.size(30.toDp()))
+            }
+        }
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        Truth.assertThat(isChecked).isTrue()
+
+        rule.onNodeWithTag("surface").performKeyInput { pressKey(Key.DirectionCenter) }
+        Truth.assertThat(isChecked).isFalse()
+    }
+
+    @Test
+    fun toggleable_enabled_disabled() {
+        var isChecked by mutableStateOf(false)
+        var enabled by mutableStateOf(true)
+
+        rule.setContent {
+            Surface(
+                checked = isChecked,
+                modifier = Modifier
+                    .testTag("surface"),
+                onCheckedChange = { isChecked = it },
+                enabled = enabled
+            ) {
+                Spacer(Modifier.size(30.toDp()))
+            }
+        }
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .assertIsEnabled()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        Truth.assertThat(isChecked).isTrue()
+        rule.runOnIdle {
+            enabled = false
+        }
+
+        rule.onNodeWithTag("surface")
+            .assertIsNotEnabled()
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        Truth.assertThat(isChecked).isTrue()
+    }
+
+    @Test
+    fun toggleable_interactionSource() {
+        val interactionSource = MutableInteractionSource()
+
+        lateinit var scope: CoroutineScope
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            Surface(
+                checked = false,
+                modifier = Modifier
+                    .testTag("surface"),
+                onCheckedChange = {},
+                interactionSource = interactionSource
+            ) {
+                Spacer(Modifier.size(30.toDp()))
+            }
+        }
+
+        val interactions = mutableListOf<Interaction>()
+
+        scope.launch {
+            interactionSource.interactions.collect { interactions.add(it) }
+        }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).isEmpty()
+        }
+
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { keyDown(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(2)
+            Truth.assertThat(interactions[1]).isInstanceOf(PressInteraction.Press::class.java)
+        }
+
+        rule.onNodeWithTag("surface").performKeyInput { keyUp(Key.DirectionCenter) }
+
+        rule.runOnIdle {
+            Truth.assertThat(interactions).hasSize(3)
+            Truth.assertThat(interactions.first()).isInstanceOf(FocusInteraction.Focus::class.java)
+            Truth.assertThat(interactions[1]).isInstanceOf(PressInteraction.Press::class.java)
+            Truth.assertThat(interactions[2]).isInstanceOf(PressInteraction.Release::class.java)
+            Truth.assertThat((interactions[2] as PressInteraction.Release).press)
+                .isEqualTo(interactions[1])
+        }
+    }
+
+    @Test
+    fun toggleableSurface_allowsFinalPassChildren() {
+        val hitTested = mutableStateOf(false)
+
+        rule.setContent {
+            Box(Modifier.fillMaxSize()) {
+                Surface(
+                    checked = false,
+                    modifier = Modifier
+                        .fillMaxSize()
+                        .testTag("surface"),
+                    onCheckedChange = {}
+                ) {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag("pressable")
+                            .pointerInput(Unit) {
+                                awaitEachGesture {
+                                    hitTested.value = true
+                                    val event = awaitPointerEvent(PointerEventPass.Final)
+                                    Truth
+                                        .assertThat(event.changes[0].isConsumed)
+                                        .isFalse()
+                                }
+                            }
+                    )
+                }
+            }
+        }
+        rule.onNodeWithTag("surface").performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.onNodeWithTag("pressable", true)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+        Truth.assertThat(hitTested.value).isTrue()
+    }
+
+    @Test
+    fun toggleableSurface_reactsToStateChange() {
+        val interactionSource = MutableInteractionSource()
+        var isPressed by mutableStateOf(false)
+
+        rule.setContent {
+            isPressed = interactionSource.collectIsPressedAsState().value
+            Surface(
+                checked = false,
+                modifier = Modifier
+                    .testTag("surface")
+                    .size(100.toDp()),
+                onCheckedChange = {},
+                interactionSource = interactionSource
+            ) {}
+        }
+
+        with(rule.onNodeWithTag("surface")) {
+            performSemanticsAction(SemanticsActions.RequestFocus)
+            assertIsFocused()
+            performKeyInput { keyDown(Key.DirectionCenter) }
+        }
+
+        rule.waitUntil(condition = { isPressed })
+
+        Truth.assertThat(isPressed).isTrue()
+    }
+
+    @FlakyTest(bugId = 269229262)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun toggleableSurface_onCheckedChange_changesGlowColor() {
+        var isChecked by mutableStateOf(false)
+        var focusManager: FocusManager? = null
+        rule.setContent {
+            focusManager = LocalFocusManager.current
+            Surface(
+                checked = isChecked,
+                modifier = Modifier
+                    .testTag("surface")
+                    .size(100.toDp()),
+                onCheckedChange = { isChecked = it },
+                color = ToggleableSurfaceDefaults.color(
+                    color = Color.Transparent,
+                    selectedColor = Color.Transparent
+                ),
+                glow = ToggleableSurfaceDefaults.glow(
+                    glow = Glow(
+                        elevationColor = Color.Magenta,
+                        elevation = Elevation.Level5
+                    ),
+                    selectedGlow = Glow(
+                        elevationColor = Color.Green,
+                        elevation = Elevation.Level5
+                    )
+                )
+            ) {}
+        }
+        rule.onNodeWithTag("surface")
+            .captureToImage()
+            .assertContainsColor(Color.Magenta)
+
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        // Remove focused state to reveal selected state
+        focusManager?.clearFocus()
+
+        rule.onNodeWithTag("surface")
+            .captureToImage()
+            .assertContainsColor(Color.Green)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun toggleableSurface_onCheckedChange_changesScaleFactor() {
+        var isChecked by mutableStateOf(false)
+        var focusManager: FocusManager? = null
+        rule.setContent {
+            focusManager = LocalFocusManager.current
+            Box(
+                modifier = Modifier
+                    .background(Color.Blue)
+                    .size(50.toDp())
+            )
+            Surface(
+                checked = isChecked,
+                onCheckedChange = { isChecked = it },
+                modifier = Modifier
+                    .size(50.toDp())
+                    .testTag("surface"),
+                scale = ToggleableSurfaceDefaults.scale(
+                    selectedScale = 1.5f
+                )
+            ) {}
+        }
+        rule.onRoot().captureToImage().assertContainsColor(Color.Blue)
+
+        rule.onNodeWithTag("surface")
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        // Remove focused state to reveal selected state
+        focusManager?.clearFocus()
+
+        rule.onRoot().captureToImage().assertDoesNotContainColor(Color.Blue)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun toggleableSurface_onCheckedChange_showsOutline() {
+        var isChecked by mutableStateOf(false)
+        var focusManager: FocusManager? = null
+        rule.setContent {
+            focusManager = LocalFocusManager.current
+            Surface(
+                checked = isChecked,
+                onCheckedChange = { isChecked = it },
+                modifier = Modifier
+                    .size(100.toDp())
+                    .testTag("surface"),
+                border = ToggleableSurfaceDefaults.border(
+                    selectedBorder = Border(
+                        border = BorderStroke(width = 5.toDp(), color = Color.Magenta)
+                    )
+                ),
+                color = ToggleableSurfaceDefaults.color(
+                    color = Color.Transparent,
+                    selectedColor = Color.Transparent
+                )
+            ) {}
+        }
+
+        val surface = rule.onNodeWithTag("surface")
+
+        surface.captureToImage().assertDoesNotContainColor(Color.Magenta)
+
+        surface
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+            .performKeyInput { pressKey(Key.DirectionCenter) }
+
+        // Remove focused state to reveal selected state
+        focusManager?.clearFocus()
+
+        surface.captureToImage().assertContainsColor(Color.Magenta)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun surfaceChangesStyleOnChangingEnabledState() {
+        var surfaceEnabled by mutableStateOf(true)
+
+        rule.setContent {
+            Surface(
+                modifier = Modifier
+                    .size(100.toDp())
+                    .testTag("surface"),
+                onClick = {},
+                enabled = surfaceEnabled,
+                color = ClickableSurfaceDefaults.color(
+                    color = Color.Green,
+                    disabledColor = Color.Red
+                )
+            ) {}
+        }
+
+        // Assert surface is enabled
+        rule.onNodeWithTag("surface").captureToImage().assertContainsColor(Color.Green)
+        surfaceEnabled = false
+        // Assert surface is disabled
+        rule.onNodeWithTag("surface").captureToImage().assertContainsColor(Color.Red)
+    }
 }
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
index fd811f2..198e493 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/TabRowTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.tv.material3
 
+import android.os.Build
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
@@ -34,6 +35,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
@@ -42,20 +44,52 @@
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.unit.DpRect
 import androidx.compose.ui.unit.dp
+import androidx.test.filters.SdkSuppress
 import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.screenshot.AndroidXScreenshotTestRule
 import org.junit.Rule
 import org.junit.Test
 
 class TabRowTest {
-
     @get:Rule
     val rule = createComposeRule()
 
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(TV_GOLDEN_MATERIAL3)
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun tabRow_pillIndicatorScreenshot() {
+        val tabs = constructTabs(count = 3)
+        val testTag = "TabRowTestTag"
+
+        setContent(
+            tabs = tabs,
+            contentBuilder = {
+                Box {
+                    var selectedTabIndex by remember { mutableStateOf(0) }
+                    TabRowSample(
+                        tabs = tabs,
+                        modifier = Modifier.testTag(testTag),
+                        selectedTabIndex = selectedTabIndex,
+                        onFocus = { selectedTabIndex = it }
+                    )
+                }
+            }
+        )
+
+        rule
+            .onNodeWithTag(testTag)
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "tab_row_pill_indicator_default")
+    }
+
     @Test
     fun tabRow_shouldNotCrashWithOnly1Tab() {
         val tabs = constructTabs(count = 1)
@@ -233,6 +267,7 @@
 private fun TabRowSample(
     tabs: List<String>,
     selectedTabIndex: Int,
+    modifier: Modifier = Modifier,
     onFocus: (index: Int) -> Unit = {},
     onClick: (index: Int) -> Unit = onFocus,
     buildTab: @Composable ((index: Int, tab: String) -> Unit) = @Composable { index, tab ->
@@ -272,6 +307,7 @@
         if (indicator != null) {
             TabRow(
                 selectedTabIndex = selectedTabIndex,
+                modifier = modifier,
                 indicator = indicator,
                 separator = { Spacer(modifier = Modifier.width(12.dp)) },
             ) {
@@ -280,6 +316,7 @@
         } else {
             TabRow(
                 selectedTabIndex = selectedTabIndex,
+                modifier = modifier,
                 separator = { Spacer(modifier = Modifier.width(12.dp)) },
             ) {
                 tabs.forEachIndexed { index, tab -> buildTab(index, tab) }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt
new file mode 100644
index 0000000..2e8faf1
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Icon.kt
@@ -0,0 +1,174 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.paint
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.toolingGraphicsLayer
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+/**
+ * A Material Design icon component that draws [imageVector] using [tint], with a default value
+ * of [LocalContentColor]. If [imageVector] has no intrinsic size, this component will use the
+ * recommended default size. Icon is an opinionated component designed to be used with single-color
+ * icons so that they can be tinted correctly for the component they are placed in. For multicolored
+ * icons and icons that should not be tinted, use [Color.Unspecified] for [tint]. For generic images
+ * that should not be tinted, and do not follow the recommended icon size, use the generic
+ * [androidx.compose.foundation.Image] instead.
+ *
+ * To learn more about icons, see [Material Design icons](https://m3.material.io/styles/icons/overview)
+ *
+ * @param imageVector [ImageVector] to draw inside this icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be gprovided unless this icon is used for decorative purposes, and
+ * does not represent a meaningful action that a user can take. This text should be localized, such
+ * as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier the [Modifier] to be applied to this icon
+ * @param tint tint to be applied to [imageVector]. If [Color.Unspecified] is provided, then no tint
+ * is applied.
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun Icon(
+    imageVector: ImageVector,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current
+) {
+    Icon(
+        painter = rememberVectorPainter(imageVector),
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint
+    )
+}
+
+/**
+ * A Material Design icon component that draws [bitmap] using [tint], with a default value
+ * of [LocalContentColor]. If [bitmap] has no intrinsic size, this component will use the
+ * recommended default size. Icon is an opinionated component designed to be used with single-color
+ * icons so that they can be tinted correctly for the component they are placed in. For multicolored
+ * icons and icons that should not be tinted, use [Color.Unspecified] for [tint]. For generic images
+ * that should not be tinted, and do not follow the recommended icon size, use the generic
+ * [androidx.compose.foundation.Image] instead.
+ *
+ * To learn more about icons, see [Material Design icons](https://m3.material.io/styles/icons/overview)
+ *
+ * @param bitmap [ImageBitmap] to draw inside this icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes, and
+ * does not represent a meaningful action that a user can take. This text should be localized, such
+ * as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier the [Modifier] to be applied to this icon
+ * @param tint tint to be applied to [bitmap]. If [Color.Unspecified] is provided, then no tint is
+ * applied.
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun Icon(
+    bitmap: ImageBitmap,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current
+) {
+    val painter = remember(bitmap) { BitmapPainter(bitmap) }
+    Icon(
+        painter = painter,
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint
+    )
+}
+
+/**
+ * A Material Design icon component that draws [painter] using [tint], with a default value
+ * of [LocalContentColor]. If [painter] has no intrinsic size, this component will use the
+ * recommended default size. Icon is an opinionated component designed to be used with single-color
+ * icons so that they can be tinted correctly for the component they are placed in. For multicolored
+ * icons and icons that should not be tinted, use [Color.Unspecified] for [tint]. For generic images
+ * that should not be tinted, and do not follow the recommended icon size, use the generic
+ * [androidx.compose.foundation.Image] instead.
+ *
+ * To learn more about icons, see [Material Design icons](https://m3.material.io/styles/icons/overview)
+ *
+ * @param painter [Painter] to draw inside this icon
+ * @param contentDescription text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes, and
+ * does not represent a meaningful action that a user can take. This text should be localized, such
+ * as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier the [Modifier] to be applied to this icon
+ * @param tint tint to be applied to [painter]. If [Color.Unspecified] is provided, then no tint is
+ * applied.
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun Icon(
+    painter: Painter,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = LocalContentColor.current
+) {
+    val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
+    val semantics =
+        if (contentDescription != null) {
+            Modifier.semantics {
+                this.contentDescription = contentDescription
+                this.role = Role.Image
+            }
+        } else {
+            Modifier
+        }
+    Box(
+        modifier
+            .toolingGraphicsLayer()
+            .defaultSizeFor(painter)
+            .paint(painter, colorFilter = colorFilter, contentScale = ContentScale.Fit)
+            .then(semantics)
+    )
+}
+
+private fun Modifier.defaultSizeFor(painter: Painter) =
+    this.then(
+        if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
+            DefaultIconSizeModifier
+        } else {
+            Modifier
+        }
+    )
+
+private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
+
+// Default icon size, for icons with no intrinsic size information
+// TODO(rvighnesh): change this to IconButtonTokens.IconSize when we introduce IconButton
+private val DefaultIconSizeModifier = Modifier.size(24.dp)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
index 7a5631b..9f934d8 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/ImmersiveList.kt
@@ -16,12 +16,11 @@
 
 package androidx.tv.material3
 
-import androidx.compose.animation.AnimatedContentScope
+import androidx.compose.animation.AnimatedContentTransitionScope
 import androidx.compose.animation.AnimatedVisibilityScope
 import androidx.compose.animation.ContentTransform
 import androidx.compose.animation.EnterTransition
 import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
 import androidx.compose.animation.fadeOut
@@ -156,15 +155,13 @@
      * @link androidx.compose.animation.AnimatedContent
      * @see androidx.compose.animation.AnimatedContent
      * @see ContentTransform
-     * @see AnimatedContentScope
+     * @see AnimatedContentTransitionScope
      */
-    @Suppress("IllegalExperimentalApiUsage")
-    @ExperimentalAnimationApi
     @Composable
     fun AnimatedContent(
         targetState: Int,
         modifier: Modifier = Modifier,
-        transitionSpec: AnimatedContentScope<Int>.() -> ContentTransform = {
+        transitionSpec: AnimatedContentTransitionScope<Int>.() -> ContentTransform = {
             ImmersiveListDefaults.EnterTransition.with(ImmersiveListDefaults.ExitTransition)
         },
         contentAlignment: Alignment = Alignment.TopStart,
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
new file mode 100644
index 0000000..8cba520
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/NavigationDrawer.kt
@@ -0,0 +1,316 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.tv.material3
+
+import androidx.compose.animation.animateContentSize
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.padding
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection
+import androidx.compose.ui.focus.FocusManager
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.focus.onFocusChanged
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection.Ltr
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.zIndex
+
+/**
+ * Navigation drawers provide ergonomic access to destinations in an app.
+ * Modal navigation drawers are good for infrequent, but more focused, switching to different
+ * destinations.
+ *
+ * It displays content associated with the closed state when the drawer is not in focus and displays
+ * content associated with the open state when the drawer or its contents are focused on.
+ * Modal navigation drawers are elevated above most of the app’s UI and don’t affect the screen’s
+ * layout grid.
+ *
+ * Example:
+ * @sample androidx.tv.samples.SampleModalNavigationDrawer
+ *
+ * @param drawerContent Content that needs to be displayed on the drawer based on whether the drawer
+ * is [DrawerValue.Open] or [DrawerValue.Closed].
+ * Drawer-entries can be animated when the drawer moves from Closed to Open state and vice-versa.
+ * For, e.g., the entry could show only an icon in the Closed state and slide in text to form
+ * (icon + text) when in the Open state.
+ * @sample androidx.tv.samples.NavigationRow
+ *
+ * To limit the width of the drawer in the open or closed state, wrap the content in a box with the
+ * required width.
+ *
+ * @param modifier the [Modifier] to be applied to this drawer
+ * @param drawerState state of the drawer
+ * @param scrimColor color of the scrim that obscures content when the drawer is open
+ * @param content content of the rest of the UI
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun ModalNavigationDrawer(
+    drawerContent: @Composable (DrawerValue) -> Unit,
+    modifier: Modifier = Modifier,
+    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
+    scrimColor: Color = LocalColorScheme.current.scrim.copy(alpha = 0.5f),
+    content: @Composable () -> Unit
+) {
+    val layoutDirection = LocalLayoutDirection.current
+    val exitDirection =
+        if (layoutDirection == Ltr) FocusDirection.Right else FocusDirection.Left
+    val drawerFocusRequester = remember { FocusRequester() }
+    val closedDrawerWidth: MutableState<Dp?> = remember { mutableStateOf(null) }
+    val internalDrawerModifier =
+        Modifier
+            .modalDrawerNavigation(
+                drawerFocusRequester = drawerFocusRequester,
+                exitDirection = exitDirection,
+                drawerState = drawerState,
+                focusManager = LocalFocusManager.current
+            )
+            .zIndex(Float.MAX_VALUE)
+            .onSizeChanged {
+                if (closedDrawerWidth.value == null &&
+                    drawerState.currentValue == DrawerValue.Closed
+                ) {
+                    closedDrawerWidth.value = it.width.dp
+                }
+            }
+
+    Box(modifier = modifier) {
+        DrawerSheet(
+            modifier = internalDrawerModifier.align(Alignment.CenterStart),
+            drawerState = drawerState,
+            sizeAnimationFinishedListener = { _, targetSize ->
+                if (drawerState.currentValue == DrawerValue.Closed) {
+                    closedDrawerWidth.value = targetSize.width.dp
+                }
+            },
+            content = drawerContent
+        )
+
+        Box(Modifier.padding(start = closedDrawerWidth.value ?: ClosedDrawerWidth.dp)) {
+            content()
+            if (drawerState.currentValue == DrawerValue.Open) {
+                // Scrim
+                Canvas(Modifier.fillMaxSize()) {
+                    drawRect(scrimColor)
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Navigation drawers provide ergonomic access to destinations in an app. They’re often next to
+ * app content and affect the screen’s layout grid.
+ * Standard navigation drawers are good for frequent switching to different destinations.
+ *
+ * It displays content associated with the closed state when the drawer is not in focus and displays
+ * content associated with the open state when the drawer or its contents are focused on.
+ * The drawer is at the same level as the app's UI an reduces the screen size available to the
+ * remaining content.
+ *
+ * Example:
+ * @sample androidx.tv.samples.SampleNavigationDrawer
+ *
+ * @param drawerContent Content that needs to be displayed on the drawer based on whether the drawer
+ * is [DrawerValue.Open] or [DrawerValue.Closed].
+ * Drawer-entries can be animated when the drawer moves from Closed to Open state and vice-versa.
+ * For, e.g., the entry could show only an icon in the Closed state and slide in text to form
+ * (icon + text) when in the Open state.
+ * @sample androidx.tv.samples.NavigationRow
+ *
+ * To limit the width of the drawer in the open or closed state, wrap the content in a box with the
+ * required width.
+ *
+ * @param modifier the [Modifier] to be applied to this drawer
+ * @param drawerState state of the drawer
+ * @param content content of the rest of the UI
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun NavigationDrawer(
+    drawerContent: @Composable (DrawerValue) -> Unit,
+    modifier: Modifier = Modifier,
+    drawerState: DrawerState = rememberDrawerState(DrawerValue.Closed),
+    content: @Composable () -> Unit
+) {
+    Row(modifier = modifier) {
+        DrawerSheet(
+            drawerState = drawerState,
+            content = drawerContent
+        )
+        content()
+    }
+}
+
+/**
+ * States that the drawer can exist in.
+ */
+@ExperimentalTvMaterial3Api
+enum class DrawerValue {
+    /**
+     * The state of the drawer when it is closed.
+     */
+    Closed,
+
+    /**
+     * The state of the drawer when it is open.
+     */
+    Open
+}
+
+/**
+ * State of the [NavigationDrawer] or [ModalNavigationDrawer] composable.
+ *
+ * @param initialValue the initial value ([DrawerValue.Closed] or [DrawerValue.Open]) of the drawer.
+ */
+@ExperimentalTvMaterial3Api
+class DrawerState(initialValue: DrawerValue = DrawerValue.Closed) {
+    var currentValue by mutableStateOf(initialValue)
+        private set
+
+    /**
+     * Updates the state of the drawer.
+     *
+     * @param drawerValue the value the state of the drawer should be set to.
+     */
+    fun setValue(drawerValue: DrawerValue) {
+        currentValue = drawerValue
+    }
+
+    companion object {
+        /**
+         * The [Saver] used by [rememberDrawerState] to record and restore [DrawerState] across
+         * activity or process recreation.
+         */
+        val Saver =
+            Saver<DrawerState, DrawerValue>(
+                save = { it.currentValue },
+                restore = { DrawerState(it) }
+            )
+    }
+}
+
+/**
+ * Create and remember a [DrawerState].
+ *
+ * @param initialValue The initial value of the state.
+ */
+@Composable
+@ExperimentalTvMaterial3Api
+fun rememberDrawerState(initialValue: DrawerValue): DrawerState {
+    return rememberSaveable(saver = DrawerState.Saver) {
+        DrawerState(initialValue)
+    }
+}
+
+@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalTvMaterial3Api::class)
+private fun Modifier.modalDrawerNavigation(
+    drawerFocusRequester: FocusRequester,
+    exitDirection: FocusDirection,
+    drawerState: DrawerState,
+    focusManager: FocusManager
+): Modifier {
+    return this
+        .focusRequester(drawerFocusRequester)
+        .focusProperties {
+            exit = {
+                if (it == exitDirection || it == FocusDirection.Exit) {
+                    drawerFocusRequester.requestFocus()
+                    drawerState.setValue(DrawerValue.Closed)
+                    focusManager.moveFocus(it)
+                    FocusRequester.Cancel
+                } else {
+                    FocusRequester.Default
+                }
+            }
+        }
+}
+
+@Suppress("IllegalExperimentalApiUsage") // TODO (b/233188423): Address before moving to beta
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalTvMaterial3Api::class)
+@Composable
+private fun DrawerSheet(
+    modifier: Modifier = Modifier,
+    drawerState: DrawerState = remember { DrawerState() },
+    sizeAnimationFinishedListener: ((initialValue: IntSize, targetValue: IntSize) -> Unit)? = null,
+    content: @Composable (DrawerValue) -> Unit
+) {
+    // indicates that the drawer has been set to its initial state and has grabbed focus if
+    // necessary. Controls whether focus is used to decide the state of the drawer going forward.
+    var initializationComplete: Boolean = remember { false }
+
+    val focusRequester = remember { FocusRequester() }
+    LaunchedEffect(key1 = drawerState.currentValue) {
+        if (drawerState.currentValue == DrawerValue.Open) {
+            // used to grab focus if the drawer state is set to Open on start.
+            focusRequester.requestFocus()
+        }
+        initializationComplete = true
+    }
+
+    val focusManager = LocalFocusManager.current
+    val internalModifier =
+        Modifier
+            .focusRequester(focusRequester)
+            .animateContentSize(finishedListener = sizeAnimationFinishedListener)
+            .fillMaxHeight()
+            // adding passed-in modifier here to ensure animateContentSize is called before other
+            // size based modifiers.
+            .then(modifier)
+            .onFocusChanged {
+                when {
+                    it.isFocused && drawerState.currentValue == DrawerValue.Closed -> {
+                        drawerState.setValue(DrawerValue.Open)
+                        focusManager.moveFocus(FocusDirection.Enter)
+                    }
+
+                    !it.hasFocus && drawerState.currentValue == DrawerValue.Open &&
+                        initializationComplete -> {
+                        drawerState.setValue(DrawerValue.Closed)
+                    }
+                }
+            }
+            .focusable()
+
+    Box(modifier = internalModifier) { content.invoke(drawerState.currentValue) }
+}
+
+private const val ClosedDrawerWidth = 80
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
index c446450..1ae2b6c 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Surface.kt
@@ -28,7 +28,6 @@
 import androidx.compose.foundation.layout.BoxScope
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
-import androidx.compose.runtime.NonRestartableComposable
 import androidx.compose.runtime.compositionLocalOf
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -53,6 +52,7 @@
 import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
+import androidx.tv.material3.tokens.Elevation
 import kotlinx.coroutines.launch
 
 /**
@@ -82,7 +82,6 @@
  * @param content defines the [Composable] content inside the surface
  */
 @ExperimentalTvMaterial3Api
-@NonRestartableComposable
 @Composable
 fun Surface(
     onClick: () -> Unit,
@@ -105,10 +104,8 @@
             enabled = enabled,
             onClick = onClick,
             interactionSource = interactionSource,
-            value = false,
-            onValueChanged = null
         ),
-        selected = false,
+        checked = false,
         enabled = enabled,
         tonalElevation = tonalElevation,
         shape = ClickableSurfaceDefaults.shape(
@@ -152,11 +149,121 @@
     )
 }
 
+/**
+ * The Surface is a building block component that will be used for any focusable
+ * element on TV such as buttons, cards, navigation, etc.
+ *
+ * This version of Surface is responsible for a toggling its checked state as well as everything
+ * else that a regular Surface does:
+ *
+ * This version of surface will react to the check toggles, calling
+ * [onCheckedChange] lambda, updating the [interactionSource] when [PressInteraction] occurs, and
+ * showing ripple indication in response to press events. If you don't need check
+ * handling, consider using a Surface function that doesn't require [onCheckedChange] param.
+ *
+ * To manually retrieve the content color inside a surface, use [LocalContentColor].
+ *
+ * @param checked whether or not this Surface is toggled on or off
+ * @param onCheckedChange callback to be invoked when the toggleable Surface is clicked
+ * @param modifier Modifier to be applied to the layout corresponding to the surface
+ * @param enabled Controls the enabled state of the surface. When `false`, this Surface will not be
+ * clickable or focusable.
+ * @param tonalElevation When [color] is [ColorScheme.surface], a higher the elevation will result
+ * in a darker color in light theme and lighter color in dark theme.
+ * @param shape Defines the surface's shape.
+ * @param color Color to be used on background of the Surface
+ * @param contentColor The preferred content color provided by this Surface to its children.
+ * @param scale Defines size of the Surface relative to its original size.
+ * @param border Defines a border around the Surface.
+ * @param glow Diffused shadow to be shown behind the Surface.
+ * @param interactionSource the [MutableInteractionSource] representing the stream of [Interaction]s
+ * for this Surface. You can create and pass in your own remembered [MutableInteractionSource] if
+ * you want to observe [Interaction]s and customize the appearance / behavior of this Surface in
+ * different [Interaction]s.
+ * @param content defines the [Composable] content inside the surface
+ */
+@ExperimentalTvMaterial3Api
+@Composable
+fun Surface(
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    modifier: Modifier = Modifier,
+    enabled: Boolean = true,
+    tonalElevation: Dp = Elevation.Level0,
+    shape: ToggleableSurfaceShape = ToggleableSurfaceDefaults.shape(),
+    color: ToggleableSurfaceColor = ToggleableSurfaceDefaults.color(),
+    contentColor: ToggleableSurfaceColor = ToggleableSurfaceDefaults.contentColor(),
+    scale: ToggleableSurfaceScale = ToggleableSurfaceDefaults.scale(),
+    border: ToggleableSurfaceBorder = ToggleableSurfaceDefaults.border(),
+    glow: ToggleableSurfaceGlow = ToggleableSurfaceDefaults.glow(),
+    interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
+    content: @Composable (BoxScope.() -> Unit)
+) {
+    val focused by interactionSource.collectIsFocusedAsState()
+    val pressed by interactionSource.collectIsPressedAsState()
+
+    SurfaceImpl(
+        modifier = modifier.tvToggleable(
+            enabled = enabled,
+            checked = checked,
+            onCheckedChange = onCheckedChange,
+            interactionSource = interactionSource,
+        ),
+        checked = checked,
+        enabled = enabled,
+        tonalElevation = tonalElevation,
+        shape = ToggleableSurfaceDefaults.shape(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            shape = shape
+        ),
+        color = ToggleableSurfaceDefaults.color(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            color = color
+        ),
+        contentColor = ToggleableSurfaceDefaults.color(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            color = contentColor
+        ),
+        scale = ToggleableSurfaceDefaults.scale(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            scale = scale
+        ),
+        border = ToggleableSurfaceDefaults.border(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            border = border
+        ),
+        glow = ToggleableSurfaceDefaults.glow(
+            enabled = enabled,
+            focused = focused,
+            pressed = pressed,
+            selected = checked,
+            glow = glow
+        ),
+        interactionSource = interactionSource,
+        content = content
+    )
+}
+
 @ExperimentalTvMaterial3Api
 @Composable
 private fun SurfaceImpl(
     modifier: Modifier,
-    selected: Boolean,
+    checked: Boolean,
     enabled: Boolean,
     shape: Shape,
     color: Color,
@@ -175,7 +282,7 @@
         enabled = enabled,
         focused = focused,
         pressed = pressed,
-        selected = selected
+        selected = checked
     )
 
     val absoluteElevation = LocalAbsoluteTonalElevation.current + tonalElevation
@@ -258,25 +365,19 @@
 
 /**
  * This modifier handles click, press, and focus events for a TV composable.
- * @param enabled decides whether [onClick] or [onValueChanged] is executed
+ * @param enabled decides whether [onClick] is executed
  * @param onClick executes the provided lambda
- * @param value differentiates whether the current item is selected or unselected
- * @param onValueChanged executes the provided lambda while returning the inverse state of [value]
  * @param interactionSource used to emit [PressInteraction] events
  */
 private fun Modifier.tvClickable(
     enabled: Boolean,
     onClick: (() -> Unit)?,
-    value: Boolean,
-    onValueChanged: ((Boolean) -> Unit)?,
     interactionSource: MutableInteractionSource
 ) = this
     .handleDPadEnter(
         enabled = enabled,
         interactionSource = interactionSource,
-        onClick = onClick,
-        value = value,
-        onValueChanged = onValueChanged
+        onClick = onClick
     )
     .focusable(interactionSource = interactionSource)
     .semantics(mergeDescendants = true) {
@@ -284,9 +385,6 @@
             onClick?.let { nnOnClick ->
                 nnOnClick()
                 return@onClick true
-            } ?: onValueChanged?.let { nnOnValueChanged ->
-                nnOnValueChanged(!value)
-                return@onClick true
             }
             false
         }
@@ -295,20 +393,59 @@
         }
     }
 
+/**
+ * This modifier handles click, press, and focus events for a TV composable.
+ * @param enabled decides whether [onCheckedChange] is executed
+ * @param checked differentiates whether the current item is checked or unchecked
+ * @param onCheckedChange executes the provided lambda while returning the inverse state of
+ * [checked]
+ */
+private fun Modifier.tvToggleable(
+    enabled: Boolean,
+    checked: Boolean,
+    onCheckedChange: (Boolean) -> Unit,
+    interactionSource: MutableInteractionSource,
+) = handleDPadEnter(
+        enabled = enabled,
+        interactionSource = interactionSource,
+        checked = checked,
+        onCheckedChanged = onCheckedChange
+    )
+    .focusable(enabled = enabled, interactionSource = interactionSource)
+    .semantics(mergeDescendants = true) {
+        onClick {
+            onCheckedChange(!checked)
+            true
+        }
+        if (!enabled) {
+            disabled()
+        }
+    }
+
+/**
+ * This modifier is used to perform some actions when the user clicks the D-PAD enter button
+ *
+ * @param enabled if this is false, the D-PAD enter event is ignored
+ * @param interactionSource used to emit [PressInteraction] events
+ * @param onClick this lambda will be triggered on D-PAD enter event
+ * @param checked differentiates whether the current item is checked or unchecked
+ * @param onCheckedChanged executes the provided lambda while returning the inverse state of
+ * [checked]
+ */
 private fun Modifier.handleDPadEnter(
     enabled: Boolean,
     interactionSource: MutableInteractionSource,
-    onClick: (() -> Unit)?,
-    value: Boolean,
-    onValueChanged: ((Boolean) -> Unit)?
+    onClick: (() -> Unit)? = null,
+    checked: Boolean = false,
+    onCheckedChanged: ((Boolean) -> Unit)? = null
 ) = composed(
     inspectorInfo = debugInspectorInfo {
         name = "handleDPadEnter"
         properties["enabled"] = enabled
         properties["interactionSource"] = interactionSource
         properties["onClick"] = onClick
-        properties["onValueChanged"] = onValueChanged
-        properties["value"] = value
+        properties["checked"] = checked
+        properties["onCheckedChanged"] = onCheckedChanged
     }
 ) {
     val coroutineScope = rememberCoroutineScope()
@@ -334,7 +471,7 @@
                                 interactionSource.emit(PressInteraction.Release(pressInteraction))
                             }
                             onClick?.invoke()
-                            onValueChanged?.invoke(!value)
+                            onCheckedChanged?.invoke(!checked)
                         }
                     }
                 }
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
index 35afd2f..2462a4f 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceDefaults.kt
@@ -261,4 +261,336 @@
     )
 }
 
+/**
+ * Contains the default values used by Toggleable Surface.
+ */
+@ExperimentalTvMaterial3Api
+object ToggleableSurfaceDefaults {
+    /**
+     * Creates a [ToggleableSurfaceShape] that represents the default container shapes used in a
+     * toggleable Surface.
+     *
+     * @param shape the shape used when the Surface is enabled, and has no other
+     * [Interaction]s.
+     * @param focusedShape the shape used when the Surface is enabled and focused.
+     * @param pressedShape the shape used when the Surface is enabled and pressed.
+     * @param selectedShape the shape used when the Surface is enabled and selected.
+     * @param disabledShape the shape used when the Surface is not enabled.
+     * @param focusedSelectedShape the shape used when the Surface is enabled, focused and selected.
+     * @param focusedDisabledShape the shape used when the Surface is not enabled and focused.
+     * @param pressedSelectedShape the shape used when the Surface is enabled, pressed and selected.
+     * @param selectedDisabledShape the shape used when the Surface is not enabled and selected.
+     * @param focusedSelectedDisabledShape the shape used when the Surface is not enabled, focused
+     * and selected.
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun shape(
+        shape: Shape = MaterialTheme.shapes.medium,
+        focusedShape: Shape = shape,
+        pressedShape: Shape = shape,
+        selectedShape: Shape = shape,
+        disabledShape: Shape = shape,
+        focusedSelectedShape: Shape = shape,
+        focusedDisabledShape: Shape = disabledShape,
+        pressedSelectedShape: Shape = shape,
+        selectedDisabledShape: Shape = disabledShape,
+        focusedSelectedDisabledShape: Shape = disabledShape
+    ) = ToggleableSurfaceShape(
+        shape = shape,
+        focusedShape = focusedShape,
+        pressedShape = pressedShape,
+        selectedShape = selectedShape,
+        disabledShape = disabledShape,
+        focusedSelectedShape = focusedSelectedShape,
+        focusedDisabledShape = focusedDisabledShape,
+        pressedSelectedShape = pressedSelectedShape,
+        selectedDisabledShape = selectedDisabledShape,
+        focusedSelectedDisabledShape = focusedSelectedDisabledShape
+    )
+
+    /**
+     * Creates a [ToggleableSurfaceColor] that represents the default container colors used in a
+     * toggleable Surface.
+     *
+     * @param color the color used when the Surface is enabled, and has no other [Interaction]s.
+     * @param focusedColor the color used when the Surface is enabled and focused.
+     * @param pressedColor the color used when the Surface is enabled and pressed.
+     * @param selectedColor the color used when the Surface is enabled and selected.
+     * @param disabledColor the color used when the Surface is not enabled.
+     * @param focusedSelectedColor the color used when the Surface is enabled, focused and selected.
+     * @param pressedSelectedColor the color used when the Surface is enabled, pressed and selected.
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun color(
+        color: Color = MaterialTheme.colorScheme.surface,
+        focusedColor: Color = MaterialTheme.colorScheme.inverseSurface,
+        pressedColor: Color = MaterialTheme.colorScheme.inverseSurface,
+        selectedColor: Color = MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.5f),
+        disabledColor: Color = MaterialTheme.colorScheme.surfaceVariant.copy(
+            alpha = DisabledBackgroundAlpha
+        ),
+        focusedSelectedColor: Color = MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.5f),
+        pressedSelectedColor: Color = MaterialTheme.colorScheme.inverseSurface.copy(alpha = 0.5f)
+    ) = ToggleableSurfaceColor(
+        color = color,
+        focusedColor = focusedColor,
+        pressedColor = pressedColor,
+        selectedColor = selectedColor,
+        disabledColor = disabledColor,
+        focusedSelectedColor = focusedSelectedColor,
+        pressedSelectedColor = pressedSelectedColor
+    )
+
+    /**
+     * Creates a [ToggleableSurfaceColor] that represents the default content colors used in a
+     * toggleable Surface.
+     *
+     * @param color the color used when the Surface is enabled, and has no other [Interaction]s.
+     * @param focusedColor the color used when the Surface is enabled and focused.
+     * @param pressedColor the color used when the Surface is enabled and pressed.
+     * @param selectedColor the color used when the Surface is enabled and selected.
+     * @param disabledColor the color used when the Surface is not enabled.
+     * @param focusedSelectedColor the color used when the Surface is enabled, focused and selected.
+     * @param pressedSelectedColor the color used when the Surface is enabled, pressed and selected.
+     */
+    @ReadOnlyComposable
+    @Composable
+    fun contentColor(
+        color: Color = MaterialTheme.colorScheme.onSurface,
+        focusedColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        selectedColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        disabledColor: Color = MaterialTheme.colorScheme.onSurface,
+        focusedSelectedColor: Color = MaterialTheme.colorScheme.inverseOnSurface,
+        pressedSelectedColor: Color = MaterialTheme.colorScheme.inverseOnSurface
+    ) = ToggleableSurfaceColor(
+        color = color,
+        focusedColor = focusedColor,
+        pressedColor = pressedColor,
+        selectedColor = selectedColor,
+        disabledColor = disabledColor,
+        focusedSelectedColor = focusedSelectedColor,
+        pressedSelectedColor = pressedSelectedColor
+    )
+
+    /**
+     * Creates a [ToggleableSurfaceScale] that represents the default scales used in a
+     * toggleable Surface. scales are used to modify the size of a composable in different
+     * [Interaction] states e.g. 1f (original) in default state, 1.2f (scaled up) in focused state,
+     * 0.8f (scaled down) in pressed state, etc.
+     *
+     * @param scale the scale used when the Surface is enabled, and has no other
+     * [Interaction]s.
+     * @param focusedScale the scale used when the Surface is enabled and focused.
+     * @param pressedScale the scale used when the Surface is enabled and pressed.
+     * @param selectedScale the scale used when the Surface is enabled and selected.
+     * @param disabledScale the scale used when the Surface is not enabled.
+     * @param focusedSelectedScale the scale used when the Surface is enabled, focused and
+     * selected.
+     * @param focusedDisabledScale the scale used when the Surface is not enabled and
+     * focused.
+     * @param pressedSelectedScale the scale used when the Surface is enabled, pressed and
+     * selected.
+     * @param selectedDisabledScale the scale used when the Surface is not enabled and
+     * selected.
+     * @param focusedSelectedDisabledScale the scale used when the Surface is not enabled,
+     * focused and selected.
+     */
+    fun scale(
+        scale: Float = 1f,
+        focusedScale: Float = 1.1f,
+        pressedScale: Float = scale,
+        selectedScale: Float = scale,
+        disabledScale: Float = scale,
+        focusedSelectedScale: Float = focusedScale,
+        focusedDisabledScale: Float = disabledScale,
+        pressedSelectedScale: Float = scale,
+        selectedDisabledScale: Float = disabledScale,
+        focusedSelectedDisabledScale: Float = disabledScale
+    ) = ToggleableSurfaceScale(
+        scale = scale,
+        focusedScale = focusedScale,
+        pressedScale = pressedScale,
+        selectedScale = selectedScale,
+        disabledScale = disabledScale,
+        focusedSelectedScale = focusedSelectedScale,
+        focusedDisabledScale = focusedDisabledScale,
+        pressedSelectedScale = pressedSelectedScale,
+        selectedDisabledScale = selectedDisabledScale,
+        focusedSelectedDisabledScale = focusedSelectedDisabledScale
+    )
+
+    /**
+     * Creates a [ToggleableSurfaceBorder] that represents the default [Border]s applied on a
+     * toggleable Surface in different [Interaction] states.
+     *
+     * @param border the [Border] used when the Surface is enabled, and has no other
+     * [Interaction]s.
+     * @param focusedBorder the [Border] used when the Surface is enabled and focused.
+     * @param pressedBorder the [Border] used when the Surface is enabled and pressed.
+     * @param selectedBorder the [Border] used when the Surface is enabled and selected.
+     * @param disabledBorder the [Border] used when the Surface is not enabled.
+     * @param focusedSelectedBorder the [Border] used when the Surface is enabled, focused and
+     * selected.
+     * @param focusedDisabledBorder the [Border] used when the Surface is not enabled and focused.
+     * @param pressedSelectedBorder the [Border] used when the Surface is enabled, pressed and
+     * selected.
+     * @param selectedDisabledBorder the [Border] used when the Surface is not enabled and
+     * selected.
+     * @param focusedSelectedDisabledBorder the [Border] used when the Surface is not enabled,
+     * focused and selected.
+     */
+    fun border(
+        border: Border = Border.None,
+        focusedBorder: Border = border,
+        pressedBorder: Border = focusedBorder,
+        selectedBorder: Border = border,
+        disabledBorder: Border = border,
+        focusedSelectedBorder: Border = focusedBorder,
+        focusedDisabledBorder: Border = disabledBorder,
+        pressedSelectedBorder: Border = border,
+        selectedDisabledBorder: Border = disabledBorder,
+        focusedSelectedDisabledBorder: Border = disabledBorder
+    ) = ToggleableSurfaceBorder(
+        border = border,
+        focusedBorder = focusedBorder,
+        pressedBorder = pressedBorder,
+        selectedBorder = selectedBorder,
+        disabledBorder = disabledBorder,
+        focusedSelectedBorder = focusedSelectedBorder,
+        focusedDisabledBorder = focusedDisabledBorder,
+        pressedSelectedBorder = pressedSelectedBorder,
+        selectedDisabledBorder = selectedDisabledBorder,
+        focusedSelectedDisabledBorder = focusedSelectedDisabledBorder
+    )
+
+    /**
+     * Creates a [ToggleableSurfaceGlow] that represents the default [Glow]s used in a
+     * toggleable Surface.
+     *
+     * @param glow the [Glow] used when the Surface is enabled, and has no other [Interaction]s.
+     * @param focusedGlow the [Glow] used when the Surface is enabled and focused.
+     * @param pressedGlow the [Glow] used when the Surface is enabled and pressed.
+     * @param selectedGlow the [Glow] used when the Surface is enabled and selected.
+     * @param focusedSelectedGlow the [Glow] used when the Surface is enabled, focused and selected.
+     * @param pressedSelectedGlow the [Glow] used when the Surface is enabled, pressed and selected.
+     */
+    fun glow(
+        glow: Glow = Glow.None,
+        focusedGlow: Glow = glow,
+        pressedGlow: Glow = glow,
+        selectedGlow: Glow = glow,
+        focusedSelectedGlow: Glow = focusedGlow,
+        pressedSelectedGlow: Glow = glow
+    ) = ToggleableSurfaceGlow(
+        glow = glow,
+        focusedGlow = focusedGlow,
+        pressedGlow = pressedGlow,
+        selectedGlow = selectedGlow,
+        focusedSelectedGlow = focusedSelectedGlow,
+        pressedSelectedGlow = pressedSelectedGlow
+    )
+
+    internal fun shape(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        selected: Boolean,
+        shape: ToggleableSurfaceShape
+    ): Shape {
+        return when {
+            enabled && selected && pressed -> shape.pressedSelectedShape
+            enabled && selected && focused -> shape.focusedSelectedShape
+            enabled && selected -> shape.selectedShape
+            enabled && pressed -> shape.pressedShape
+            enabled && focused -> shape.focusedShape
+            enabled -> shape.shape
+            !enabled && selected && focused -> shape.focusedSelectedDisabledShape
+            !enabled && selected -> shape.selectedDisabledShape
+            !enabled && focused -> shape.focusedDisabledShape
+            else -> shape.disabledShape
+        }
+    }
+
+    internal fun color(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        selected: Boolean,
+        color: ToggleableSurfaceColor
+    ): Color {
+        return when {
+            enabled && selected && pressed -> color.pressedSelectedColor
+            enabled && selected && focused -> color.focusedSelectedColor
+            enabled && selected -> color.selectedColor
+            enabled && pressed -> color.pressedColor
+            enabled && focused -> color.focusedColor
+            enabled -> color.color
+            else -> color.disabledColor
+        }
+    }
+
+    internal fun scale(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        selected: Boolean,
+        scale: ToggleableSurfaceScale
+    ): Float {
+        return when {
+            enabled && selected && pressed -> scale.pressedSelectedScale
+            enabled && selected && focused -> scale.focusedSelectedScale
+            enabled && selected -> scale.selectedScale
+            enabled && pressed -> scale.pressedScale
+            enabled && focused -> scale.focusedScale
+            enabled -> scale.scale
+            !enabled && selected && focused -> scale.focusedSelectedDisabledScale
+            !enabled && selected -> scale.selectedDisabledScale
+            !enabled && focused -> scale.focusedDisabledScale
+            else -> scale.disabledScale
+        }
+    }
+
+    internal fun border(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        selected: Boolean,
+        border: ToggleableSurfaceBorder
+    ): Border {
+        return when {
+            enabled && selected && pressed -> border.pressedSelectedBorder
+            enabled && selected && focused -> border.focusedSelectedBorder
+            enabled && selected -> border.selectedBorder
+            enabled && pressed -> border.pressedBorder
+            enabled && focused -> border.focusedBorder
+            enabled -> border.border
+            !enabled && selected && focused -> border.focusedSelectedDisabledBorder
+            !enabled && selected -> border.selectedDisabledBorder
+            !enabled && focused -> border.focusedDisabledBorder
+            else -> border.disabledBorder
+        }
+    }
+
+    internal fun glow(
+        enabled: Boolean,
+        focused: Boolean,
+        pressed: Boolean,
+        selected: Boolean,
+        glow: ToggleableSurfaceGlow
+    ): Glow {
+        return when {
+            enabled && selected && pressed -> glow.pressedSelectedGlow
+            enabled && selected && focused -> glow.focusedSelectedGlow
+            enabled && selected -> glow.selectedGlow
+            enabled && pressed -> glow.pressedGlow
+            enabled && focused -> glow.focusedGlow
+            enabled -> glow.glow
+            else -> Glow.None
+        }
+    }
+}
+
 private const val DisabledBackgroundAlpha = 0.4f
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
index 3ea0875..c8d929a 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/SurfaceStyles.kt
@@ -67,6 +67,69 @@
 }
 
 /**
+ * Defines [Shape] for all TV [Interaction] states of a toggleable Surface.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ToggleableSurfaceShape internal constructor(
+    internal val shape: Shape,
+    internal val focusedShape: Shape,
+    internal val pressedShape: Shape,
+    internal val selectedShape: Shape,
+    internal val disabledShape: Shape,
+    internal val focusedSelectedShape: Shape,
+    internal val focusedDisabledShape: Shape,
+    internal val pressedSelectedShape: Shape,
+    internal val selectedDisabledShape: Shape,
+    internal val focusedSelectedDisabledShape: Shape
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ToggleableSurfaceShape
+
+        if (shape != other.shape) return false
+        if (focusedShape != other.focusedShape) return false
+        if (pressedShape != other.pressedShape) return false
+        if (selectedShape != other.selectedShape) return false
+        if (disabledShape != other.disabledShape) return false
+        if (focusedSelectedShape != other.focusedSelectedShape) return false
+        if (focusedDisabledShape != other.focusedDisabledShape) return false
+        if (pressedSelectedShape != other.pressedSelectedShape) return false
+        if (selectedDisabledShape != other.selectedDisabledShape) return false
+        if (focusedSelectedDisabledShape != other.focusedSelectedDisabledShape) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = shape.hashCode()
+        result = 31 * result + focusedShape.hashCode()
+        result = 31 * result + pressedShape.hashCode()
+        result = 31 * result + selectedShape.hashCode()
+        result = 31 * result + disabledShape.hashCode()
+        result = 31 * result + focusedSelectedShape.hashCode()
+        result = 31 * result + focusedDisabledShape.hashCode()
+        result = 31 * result + pressedSelectedShape.hashCode()
+        result = 31 * result + selectedDisabledShape.hashCode()
+        result = 31 * result + focusedSelectedDisabledShape.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ToggleableSurfaceShape(shape=$shape, focusedShape=$focusedShape," +
+            "pressedShape=$pressedShape, selectedShape=$selectedShape," +
+            "disabledShape=$disabledShape, focusedSelectedShape=$focusedSelectedShape, " +
+            "focusedDisabledShape=$focusedDisabledShape," +
+            "pressedSelectedShape=$pressedSelectedShape, " +
+            "selectedDisabledShape=$selectedDisabledShape, " +
+            "focusedSelectedDisabledShape=$focusedSelectedDisabledShape)"
+    }
+}
+
+/**
  * Defines [Color] for all TV [Interaction] states of a Clickable Surface.
  */
 @ExperimentalTvMaterial3Api
@@ -107,6 +170,57 @@
 }
 
 /**
+ * Defines [Color] for all TV [Interaction] states of a toggleable Surface.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ToggleableSurfaceColor internal constructor(
+    internal val color: Color,
+    internal val focusedColor: Color,
+    internal val pressedColor: Color,
+    internal val selectedColor: Color,
+    internal val disabledColor: Color,
+    internal val focusedSelectedColor: Color,
+    internal val pressedSelectedColor: Color
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ToggleableSurfaceColor
+
+        if (color != other.color) return false
+        if (focusedColor != other.focusedColor) return false
+        if (pressedColor != other.pressedColor) return false
+        if (selectedColor != other.selectedColor) return false
+        if (disabledColor != other.disabledColor) return false
+        if (focusedSelectedColor != other.focusedSelectedColor) return false
+        if (pressedSelectedColor != other.pressedSelectedColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = color.hashCode()
+        result = 31 * result + focusedColor.hashCode()
+        result = 31 * result + pressedColor.hashCode()
+        result = 31 * result + selectedColor.hashCode()
+        result = 31 * result + disabledColor.hashCode()
+        result = 31 * result + focusedSelectedColor.hashCode()
+        result = 31 * result + pressedSelectedColor.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ToggleableSurfaceColor(color=$color, focusedColor=$focusedColor," +
+            "pressedColor=$pressedColor, selectedColor=$selectedColor," +
+            "disabledColor=$disabledColor, focusedSelectedColor=$focusedSelectedColor, " +
+            "pressedSelectedColor=$pressedSelectedColor)"
+    }
+}
+
+/**
  * Defines the scale for all TV indication states of Surface. Note: This scale must be
  * a non-negative float.
  */
@@ -166,6 +280,70 @@
 }
 
 /**
+ * Defines the scale for all TV [Interaction] states of toggleable Surface. Note: This
+ * scale must be a non-negative float.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ToggleableSurfaceScale internal constructor(
+    @FloatRange(from = 0.0) internal val scale: Float,
+    @FloatRange(from = 0.0) internal val focusedScale: Float,
+    @FloatRange(from = 0.0) internal val pressedScale: Float,
+    @FloatRange(from = 0.0) internal val selectedScale: Float,
+    @FloatRange(from = 0.0) internal val disabledScale: Float,
+    @FloatRange(from = 0.0) internal val focusedSelectedScale: Float,
+    @FloatRange(from = 0.0) internal val focusedDisabledScale: Float,
+    @FloatRange(from = 0.0) internal val pressedSelectedScale: Float,
+    @FloatRange(from = 0.0) internal val selectedDisabledScale: Float,
+    @FloatRange(from = 0.0) internal val focusedSelectedDisabledScale: Float
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ToggleableSurfaceScale
+
+        if (scale != other.scale) return false
+        if (focusedScale != other.focusedScale) return false
+        if (pressedScale != other.pressedScale) return false
+        if (selectedScale != other.selectedScale) return false
+        if (disabledScale != other.disabledScale) return false
+        if (focusedSelectedScale != other.focusedSelectedScale) return false
+        if (focusedDisabledScale != other.focusedDisabledScale) return false
+        if (pressedSelectedScale != other.pressedSelectedScale) return false
+        if (selectedDisabledScale != other.selectedDisabledScale) return false
+        if (focusedSelectedDisabledScale != other.focusedSelectedDisabledScale) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = scale.hashCode()
+        result = 31 * result + focusedScale.hashCode()
+        result = 31 * result + pressedScale.hashCode()
+        result = 31 * result + selectedScale.hashCode()
+        result = 31 * result + disabledScale.hashCode()
+        result = 31 * result + focusedSelectedScale.hashCode()
+        result = 31 * result + focusedDisabledScale.hashCode()
+        result = 31 * result + pressedSelectedScale.hashCode()
+        result = 31 * result + selectedDisabledScale.hashCode()
+        result = 31 * result + focusedSelectedDisabledScale.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ToggleableSurfaceScale(scale=$scale, focusedScale=$focusedScale," +
+            "pressedScale=$pressedScale, selectedScale=$selectedScale," +
+            "disabledScale=$disabledScale, focusedSelectedScale=$focusedSelectedScale, " +
+            "focusedDisabledScale=$focusedDisabledScale," +
+            "pressedSelectedScale=$pressedSelectedScale, " +
+            "selectedDisabledScale=$selectedDisabledScale, " +
+            "focusedSelectedDisabledScale=$focusedSelectedDisabledScale)"
+    }
+}
+
+/**
  * Defines [Border] for all TV states of [Surface].
  */
 @ExperimentalTvMaterial3Api
@@ -210,7 +388,70 @@
 }
 
 /**
- * Defines [Glow] for all TV states of [Surface].
+ * Defines [Border] for all TV states of a toggleable Surface.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ToggleableSurfaceBorder internal constructor(
+    internal val border: Border,
+    internal val focusedBorder: Border,
+    internal val pressedBorder: Border,
+    internal val selectedBorder: Border,
+    internal val disabledBorder: Border,
+    internal val focusedSelectedBorder: Border,
+    internal val focusedDisabledBorder: Border,
+    internal val pressedSelectedBorder: Border,
+    internal val selectedDisabledBorder: Border,
+    internal val focusedSelectedDisabledBorder: Border
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ToggleableSurfaceBorder
+
+        if (border != other.border) return false
+        if (focusedBorder != other.focusedBorder) return false
+        if (pressedBorder != other.pressedBorder) return false
+        if (selectedBorder != other.selectedBorder) return false
+        if (disabledBorder != other.disabledBorder) return false
+        if (focusedSelectedBorder != other.focusedSelectedBorder) return false
+        if (focusedDisabledBorder != other.focusedDisabledBorder) return false
+        if (pressedSelectedBorder != other.pressedSelectedBorder) return false
+        if (selectedDisabledBorder != other.selectedDisabledBorder) return false
+        if (focusedSelectedDisabledBorder != other.focusedSelectedDisabledBorder) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = border.hashCode()
+        result = 31 * result + focusedBorder.hashCode()
+        result = 31 * result + pressedBorder.hashCode()
+        result = 31 * result + selectedBorder.hashCode()
+        result = 31 * result + disabledBorder.hashCode()
+        result = 31 * result + focusedSelectedBorder.hashCode()
+        result = 31 * result + focusedDisabledBorder.hashCode()
+        result = 31 * result + pressedSelectedBorder.hashCode()
+        result = 31 * result + selectedDisabledBorder.hashCode()
+        result = 31 * result + focusedSelectedDisabledBorder.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ToggleableSurfaceBorder(border=$border, focusedBorder=$focusedBorder," +
+            "pressedBorder=$pressedBorder, selectedBorder=$selectedBorder," +
+            "disabledBorder=$disabledBorder, focusedSelectedBorder=$focusedSelectedBorder, " +
+            "focusedDisabledBorder=$focusedDisabledBorder," +
+            "pressedSelectedBorder=$pressedSelectedBorder, " +
+            "selectedDisabledBorder=$selectedDisabledBorder, " +
+            "focusedSelectedDisabledBorder=$focusedSelectedDisabledBorder)"
+    }
+}
+
+/**
+ * Defines [Glow] for all TV [Interaction] states of [Surface].
  */
 @ExperimentalTvMaterial3Api
 @Immutable
@@ -245,3 +486,50 @@
             "pressedGlow=$pressedGlow)"
     }
 }
+
+/**
+ * Defines [Glow] for all TV [Interaction] states of a toggleable Surface.
+ */
+@ExperimentalTvMaterial3Api
+@Immutable
+class ToggleableSurfaceGlow internal constructor(
+    internal val glow: Glow,
+    internal val focusedGlow: Glow,
+    internal val pressedGlow: Glow,
+    internal val selectedGlow: Glow,
+    internal val focusedSelectedGlow: Glow,
+    internal val pressedSelectedGlow: Glow
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other == null || this::class != other::class) return false
+
+        other as ToggleableSurfaceGlow
+
+        if (glow != other.glow) return false
+        if (focusedGlow != other.focusedGlow) return false
+        if (pressedGlow != other.pressedGlow) return false
+        if (selectedGlow != other.selectedGlow) return false
+        if (focusedSelectedGlow != other.focusedSelectedGlow) return false
+        if (pressedSelectedGlow != other.pressedSelectedGlow) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = glow.hashCode()
+        result = 31 * result + focusedGlow.hashCode()
+        result = 31 * result + pressedGlow.hashCode()
+        result = 31 * result + selectedGlow.hashCode()
+        result = 31 * result + focusedSelectedGlow.hashCode()
+        result = 31 * result + pressedSelectedGlow.hashCode()
+
+        return result
+    }
+
+    override fun toString(): String {
+        return "ToggleableSurfaceGlow(glow=$glow, focusedGlow=$focusedGlow," +
+            "pressedGlow=$pressedGlow, selectedGlow=$selectedGlow," +
+            "focusedSelectedGlow=$focusedSelectedGlow, pressedSelectedGlow=$pressedSelectedGlow)"
+    }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
index cd7aaec..ac7c7f7 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Text.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.compose.ui.text.style.TextOverflow
 import androidx.compose.ui.unit.TextUnit
+import androidx.tv.material3.tokens.DefaultTextStyle
 
 /**
  * High level element that displays text and provides semantics / accessibility information.
@@ -244,7 +245,7 @@
  *
  * @see ProvideTextStyle
  */
-val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { TextStyle.Default }
+val LocalTextStyle = compositionLocalOf(structuralEqualityPolicy()) { DefaultTextStyle }
 
 // TODO(b/156598010): remove this and replace with fold definition on the backing CompositionLocal
 /**
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt
index c7b5519..6e5b366 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/tokens/TypographyTokens.kt
@@ -17,11 +17,12 @@
 
 package androidx.tv.material3.tokens
 
+import androidx.compose.ui.text.PlatformTextStyle
 import androidx.compose.ui.text.TextStyle
 
 internal object TypographyTokens {
     val BodyLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodyLargeFont,
             fontWeight = TypeScaleTokens.BodyLargeWeight,
             fontSize = TypeScaleTokens.BodyLargeSize,
@@ -29,7 +30,7 @@
             letterSpacing = TypeScaleTokens.BodyLargeTracking
         )
     val BodyMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodyMediumFont,
             fontWeight = TypeScaleTokens.BodyMediumWeight,
             fontSize = TypeScaleTokens.BodyMediumSize,
@@ -37,7 +38,7 @@
             letterSpacing = TypeScaleTokens.BodyMediumTracking
         )
     val BodySmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.BodySmallFont,
             fontWeight = TypeScaleTokens.BodySmallWeight,
             fontSize = TypeScaleTokens.BodySmallSize,
@@ -45,7 +46,7 @@
             letterSpacing = TypeScaleTokens.BodySmallTracking
         )
     val DisplayLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplayLargeFont,
             fontWeight = TypeScaleTokens.DisplayLargeWeight,
             fontSize = TypeScaleTokens.DisplayLargeSize,
@@ -53,7 +54,7 @@
             letterSpacing = TypeScaleTokens.DisplayLargeTracking
         )
     val DisplayMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplayMediumFont,
             fontWeight = TypeScaleTokens.DisplayMediumWeight,
             fontSize = TypeScaleTokens.DisplayMediumSize,
@@ -61,7 +62,7 @@
             letterSpacing = TypeScaleTokens.DisplayMediumTracking
         )
     val DisplaySmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.DisplaySmallFont,
             fontWeight = TypeScaleTokens.DisplaySmallWeight,
             fontSize = TypeScaleTokens.DisplaySmallSize,
@@ -69,7 +70,7 @@
             letterSpacing = TypeScaleTokens.DisplaySmallTracking
         )
     val HeadlineLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineLargeFont,
             fontWeight = TypeScaleTokens.HeadlineLargeWeight,
             fontSize = TypeScaleTokens.HeadlineLargeSize,
@@ -77,7 +78,7 @@
             letterSpacing = TypeScaleTokens.HeadlineLargeTracking
         )
     val HeadlineMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineMediumFont,
             fontWeight = TypeScaleTokens.HeadlineMediumWeight,
             fontSize = TypeScaleTokens.HeadlineMediumSize,
@@ -85,7 +86,7 @@
             letterSpacing = TypeScaleTokens.HeadlineMediumTracking
         )
     val HeadlineSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.HeadlineSmallFont,
             fontWeight = TypeScaleTokens.HeadlineSmallWeight,
             fontSize = TypeScaleTokens.HeadlineSmallSize,
@@ -93,7 +94,7 @@
             letterSpacing = TypeScaleTokens.HeadlineSmallTracking
         )
     val LabelLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelLargeFont,
             fontWeight = TypeScaleTokens.LabelLargeWeight,
             fontSize = TypeScaleTokens.LabelLargeSize,
@@ -101,7 +102,7 @@
             letterSpacing = TypeScaleTokens.LabelLargeTracking
         )
     val LabelMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelMediumFont,
             fontWeight = TypeScaleTokens.LabelMediumWeight,
             fontSize = TypeScaleTokens.LabelMediumSize,
@@ -109,7 +110,7 @@
             letterSpacing = TypeScaleTokens.LabelMediumTracking
         )
     val LabelSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.LabelSmallFont,
             fontWeight = TypeScaleTokens.LabelSmallWeight,
             fontSize = TypeScaleTokens.LabelSmallSize,
@@ -117,7 +118,7 @@
             letterSpacing = TypeScaleTokens.LabelSmallTracking
         )
     val TitleLarge =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleLargeFont,
             fontWeight = TypeScaleTokens.TitleLargeWeight,
             fontSize = TypeScaleTokens.TitleLargeSize,
@@ -125,7 +126,7 @@
             letterSpacing = TypeScaleTokens.TitleLargeTracking
         )
     val TitleMedium =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleMediumFont,
             fontWeight = TypeScaleTokens.TitleMediumWeight,
             fontSize = TypeScaleTokens.TitleMediumSize,
@@ -133,7 +134,7 @@
             letterSpacing = TypeScaleTokens.TitleMediumTracking
         )
     val TitleSmall =
-        TextStyle(
+        DefaultTextStyle.copy(
             fontFamily = TypeScaleTokens.TitleSmallFont,
             fontWeight = TypeScaleTokens.TitleSmallWeight,
             fontSize = TypeScaleTokens.TitleSmallSize,
@@ -141,3 +142,10 @@
             letterSpacing = TypeScaleTokens.TitleSmallTracking
         )
 }
+
+private const val DefaultIncludeFontPadding = true
+
+@Suppress("DEPRECATION")
+internal val DefaultTextStyle = TextStyle.Default.copy(
+    platformStyle = PlatformTextStyle(includeFontPadding = DefaultIncludeFontPadding)
+)
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
index 643d854..227a1f0 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/AdapterDataSetChangeWhileSmoothScrollTest.kt
@@ -36,6 +36,7 @@
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.Matchers.greaterThan
 import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -105,6 +106,7 @@
         test.setAdapterSync(config.adapterProvider.provider(dataSet))
     }
 
+    @Ignore // b/271634631
     @Test
     fun test() {
         tryNTimes(3, resetBlock = { test.resetViewPagerTo(initialPage) }) {
diff --git a/wear/compose/compose-foundation/api/current.txt b/wear/compose/compose-foundation/api/current.txt
index 384b168..117b84c 100644
--- a/wear/compose/compose-foundation/api/current.txt
+++ b/wear/compose/compose-foundation/api/current.txt
@@ -171,6 +171,30 @@
     property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
   }
 
+  public final class ExpandableItemsDefaults {
+    method @androidx.compose.runtime.Composable public void Chevron(float progress, long color, optional androidx.compose.ui.Modifier modifier, optional float strokeWidth);
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
+    field public static final androidx.wear.compose.foundation.ExpandableItemsDefaults INSTANCE;
+  }
+
+  public final class ExpandableItemsState {
+    method public float getExpandProgress();
+    method public boolean isExpanded();
+    method public void setExpanded(boolean);
+    method public void toggle();
+    property public final float expandProgress;
+    property public final boolean expanded;
+  }
+
+  public final class ExpandableKt {
+    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
+    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
+  }
+
 }
 
 package androidx.wear.compose.foundation.lazy {
diff --git a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
index eda89f7..abaaf5d 100644
--- a/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-foundation/api/public_plus_experimental_current.txt
@@ -171,6 +171,30 @@
     property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
   }
 
+  public final class ExpandableItemsDefaults {
+    method @androidx.compose.runtime.Composable public void Chevron(float progress, long color, optional androidx.compose.ui.Modifier modifier, optional float strokeWidth);
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
+    field public static final androidx.wear.compose.foundation.ExpandableItemsDefaults INSTANCE;
+  }
+
+  public final class ExpandableItemsState {
+    method public float getExpandProgress();
+    method public boolean isExpanded();
+    method public void setExpanded(boolean);
+    method public void toggle();
+    property public final float expandProgress;
+    property public final boolean expanded;
+  }
+
+  public final class ExpandableKt {
+    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
+    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
+  }
+
   @kotlin.RequiresOptIn(message="This Wear Foundation API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearFoundationApi {
   }
 
diff --git a/wear/compose/compose-foundation/api/restricted_current.txt b/wear/compose/compose-foundation/api/restricted_current.txt
index 384b168..117b84c 100644
--- a/wear/compose/compose-foundation/api/restricted_current.txt
+++ b/wear/compose/compose-foundation/api/restricted_current.txt
@@ -171,6 +171,30 @@
     property public final androidx.compose.ui.text.font.FontWeight? fontWeight;
   }
 
+  public final class ExpandableItemsDefaults {
+    method @androidx.compose.runtime.Composable public void Chevron(float progress, long color, optional androidx.compose.ui.Modifier modifier, optional float strokeWidth);
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
+    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
+    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
+    field public static final androidx.wear.compose.foundation.ExpandableItemsDefaults INSTANCE;
+  }
+
+  public final class ExpandableItemsState {
+    method public float getExpandProgress();
+    method public boolean isExpanded();
+    method public void setExpanded(boolean);
+    method public void toggle();
+    property public final float expandProgress;
+    property public final boolean expanded;
+  }
+
+  public final class ExpandableKt {
+    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
+    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.foundation.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
+    method @androidx.compose.runtime.Composable public static androidx.wear.compose.foundation.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
+  }
+
 }
 
 package androidx.wear.compose.foundation.lazy {
diff --git a/wear/compose/compose-foundation/build.gradle b/wear/compose/compose-foundation/build.gradle
index 32f95ce..5a85f33 100644
--- a/wear/compose/compose-foundation/build.gradle
+++ b/wear/compose/compose-foundation/build.gradle
@@ -27,22 +27,22 @@
 AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project, /* isMultiplatformEnabled= */ false)
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.4.0-beta02")
-    api("androidx.compose.ui:ui:1.4.0-beta02")
-    api("androidx.compose.ui:ui-text:1.4.0-beta02")
-    api("androidx.compose.runtime:runtime:1.4.0-beta02")
+    api(project(":compose:foundation:foundation"))
+    api(project(":compose:ui:ui"))
+    api(project(":compose:ui:ui-text"))
+    api(project(":compose:runtime:runtime"))
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:ui:ui-util"))
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
 
     testImplementation(libs.testRules)
     testImplementation(libs.testRunner)
     testImplementation(libs.junit)
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(libs.kotlinTest)
diff --git a/wear/compose/compose-foundation/samples/build.gradle b/wear/compose/compose-foundation/samples/build.gradle
index 63728a1..be551b8 100644
--- a/wear/compose/compose-foundation/samples/build.gradle
+++ b/wear/compose/compose-foundation/samples/build.gradle
@@ -28,11 +28,11 @@
     implementation(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation("androidx.compose.foundation:foundation:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    implementation("androidx.compose.runtime:runtime:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-text:1.4.0-beta02")
+    implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-text"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation(project(":wear:compose:compose-material"))
 }
diff --git a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ExpandableSample.kt b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ExpandableSample.kt
similarity index 92%
rename from wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ExpandableSample.kt
rename to wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ExpandableSample.kt
index afa936f..3769f31 100644
--- a/wear/compose/compose-material/samples/src/main/java/androidx/wear/compose/material/samples/ExpandableSample.kt
+++ b/wear/compose/compose-foundation/samples/src/main/java/androidx/wear/compose/foundation/samples/ExpandableSample.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.wear.compose.material.samples
+package androidx.wear.compose.foundation.samples
 
 import androidx.annotation.Sampled
 import androidx.compose.foundation.layout.Spacer
@@ -25,15 +25,15 @@
 import androidx.compose.ui.Alignment
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ExpandableItemsDefaults
+import androidx.wear.compose.foundation.expandableItem
+import androidx.wear.compose.foundation.expandableItems
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
+import androidx.wear.compose.foundation.rememberExpandableItemsState
 import androidx.wear.compose.material.Chip
-import androidx.wear.compose.material.ExpandableItemsDefaults
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.OutlinedChip
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.expandableItem
-import androidx.wear.compose.material.expandableItems
-import androidx.wear.compose.material.rememberExpandableItemsState
 
 @Sampled
 @Composable
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Expandable.kt b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/Expandable.kt
similarity index 95%
rename from wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Expandable.kt
rename to wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/Expandable.kt
index 47cffe6..1fafbf8 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Expandable.kt
+++ b/wear/compose/compose-foundation/src/commonMain/kotlin/androidx/wear/compose/foundation/Expandable.kt
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.wear.compose.material
+package androidx.wear.compose.foundation
 
 import androidx.compose.animation.core.Animatable
 import androidx.compose.animation.core.AnimationSpec
@@ -45,10 +45,10 @@
  * Create and [remember] an [ExpandableItemsState]
  *
  * Example of an expandable list:
- * @sample androidx.wear.compose.material.samples.ExpandableWithItemsSample
+ * @sample androidx.wear.compose.foundation.samples.ExpandableWithItemsSample
  *
  * Example of an expandable text:
- * @sample androidx.wear.compose.material.samples.ExpandableTextSample
+ * @sample androidx.wear.compose.foundation.samples.ExpandableTextSample
  *
  * @param initiallyExpanded The initial value of the state.
  * @param expandAnimationSpec The [AnimationSpec] to use when showing the extra information.
@@ -70,7 +70,7 @@
  * Adds a series of items, that will be expanded/collapsed according to the [ExpandableItemsState]
  *
  * Example of an expandable list:
- * @sample androidx.wear.compose.material.samples.ExpandableWithItemsSample
+ * @sample androidx.wear.compose.foundation.samples.ExpandableWithItemsSample
  *
  * @param state The [ExpandableItemsState] connected to these items to.
  * @param count The number of items
@@ -115,7 +115,7 @@
  * Adds a single item, that will be expanded/collapsed according to the [ExpandableItemsState].
  *
  * Example of an expandable text:
- * @sample androidx.wear.compose.material.samples.ExpandableTextSample
+ * @sample androidx.wear.compose.foundation.samples.ExpandableTextSample
  *
  * The item should support two levels of information display (for example, a text showing a few
  * lines in the collapsed state, and more in the expanded state)
@@ -229,16 +229,16 @@
      *
      * @param progress The point in the animation we are displaying this chevron in. 0f means pointing
      * downward, 1f means pointing upward.
+     * @param color The color to draw this chevron on.
      * @param modifier Modifier to be applied to the AnimatableChevron. This can be used to provide a
      * content description for accessibility.
-     * @param color The color to draw this chevron on.
      * @param strokeWidth The stroke width used to draw this chevron.
      */
     @Composable
     public fun Chevron(
         progress: Float,
+        color: Color,
         modifier: Modifier = Modifier,
-        color: Color = MaterialTheme.colors.onBackground,
         strokeWidth: Dp = 3.dp
     ) {
         Box(
diff --git a/wear/compose/compose-material-core/build.gradle b/wear/compose/compose-material-core/build.gradle
index ed4a9f1..84995a0 100644
--- a/wear/compose/compose-material-core/build.gradle
+++ b/wear/compose/compose-material-core/build.gradle
@@ -28,21 +28,21 @@
 AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project, /* isMultiplatformEnabled= */ false)
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.4.0-beta02")
-    api("androidx.compose.ui:ui:1.4.0-beta02")
-    api("androidx.compose.ui:ui-text:1.4.0-beta02")
-    api("androidx.compose.runtime:runtime:1.4.0-beta02")
+    api(project(":compose:foundation:foundation"))
+    api(project(":compose:ui:ui"))
+    api(project(":compose:ui:ui-text"))
+    api(project(":compose:runtime:runtime"))
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.animation:animation:1.4.0-beta02")
-    implementation("androidx.compose.material:material-icons-core:1.4.0-beta02")
-    implementation("androidx.compose.material:material-ripple:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
+    implementation(project(":compose:animation:animation"))
+    implementation(project(":compose:material:material-icons-core"))
+    implementation(project(":compose:material:material-ripple"))
+    implementation(project(":compose:ui:ui-util"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
 
     androidTestImplementation(project(":test:screenshot:screenshot"))
diff --git a/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/IconTest.kt b/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/IconTest.kt
new file mode 100644
index 0000000..ce31b5d
--- /dev/null
+++ b/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/IconTest.kt
@@ -0,0 +1,323 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.materialcore
+
+import android.os.Build
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Menu
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.testutils.assertPixels
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Canvas
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ImageBitmap
+import androidx.compose.ui.graphics.drawscope.CanvasDrawScope
+import androidx.compose.ui.graphics.painter.ColorPainter
+import androidx.compose.ui.graphics.painter.BitmapPainter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.vector.ImageVector
+import androidx.compose.ui.graphics.vector.rememberVectorPainter
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertHeightIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class IconTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun vector_materialIconSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val vector = Icons.Filled.Menu
+        rule
+            .setContentForSizeAssertions {
+                IconWithDefaults(
+                    painter = rememberVectorPainter(vector),
+                    contentDescription = null,
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun vector_customIconSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+        val vector = ImageVector.Builder(
+            defaultWidth = width, defaultHeight = height,
+            viewportWidth = width.value, viewportHeight = height.value
+        ).build()
+        rule
+            .setContentForSizeAssertions {
+                IconWithDefaults(
+                    painter = rememberVectorPainter(vector),
+                    contentDescription = null,
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        rule
+            .setContentForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+                val painter = remember(image) { BitmapPainter(image) }
+                IconWithDefaults(
+                    painter = painter,
+                    contentDescription = null
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun image_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+                val painter = remember(image) { BitmapPainter(image) }
+                IconWithDefaults(
+                    painter = painter,
+                    contentDescription = null
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_noIntrinsicSize_dimensions() {
+        val width = 24.dp
+        val height = 24.dp
+        val painter = ColorPainter(Color.Red)
+        rule
+            .setContentForSizeAssertions {
+                IconWithDefaults(
+                    painter = painter,
+                    contentDescription = null
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @Test
+    fun painter_withIntrinsicSize_dimensions() {
+        val width = 35.dp
+        val height = 83.dp
+
+        rule
+            .setContentForSizeAssertions {
+                val image = with(LocalDensity.current) {
+                    ImageBitmap(width.roundToPx(), height.roundToPx())
+                }
+
+                val bitmapPainter = BitmapPainter(image)
+                IconWithDefaults(
+                    painter = bitmapPainter,
+                    contentDescription = null
+                )
+            }
+            .assertWidthIsEqualTo(width)
+            .assertHeightIsEqualTo(height)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconScalesToFitSize() {
+        // Image with intrinsic size of 24dp
+        val width = 24.dp
+        val height = 24.dp
+        var expectedIntSize: IntSize? = null
+        rule.setContent {
+            val image: ImageBitmap
+            with(LocalDensity.current) {
+                image = createBitmapWithColor(
+                    this,
+                    width.roundToPx(),
+                    height.roundToPx(),
+                    Color.Red
+                )
+            }
+            val painter = remember(image) { BitmapPainter(image) }
+            IconWithDefaults(
+                painter = painter,
+                contentDescription = null,
+                modifier = Modifier
+                    .requiredSize(50.dp)
+                    .testTag(TEST_TAG),
+                tint = Color.Unspecified
+            )
+
+            with(LocalDensity.current) {
+                val dimension = 50.dp.roundToPx()
+                expectedIntSize = IntSize(dimension, dimension)
+            }
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .captureToImage()
+            // The icon should be 50x50 and fill the whole size with red pixels
+            .assertPixels(expectedSize = expectedIntSize!!) {
+                Color.Red
+            }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconUnspecifiedTintColorIgnored() {
+        val width = 35.dp
+        val height = 83.dp
+        rule.setContent {
+            val image: ImageBitmap
+            with(LocalDensity.current) {
+                image = createBitmapWithColor(
+                    this,
+                    width.roundToPx(),
+                    height.roundToPx(),
+                    Color.Red
+                )
+            }
+            val painter = remember(image) { BitmapPainter(image) }
+            IconWithDefaults(
+                painter = painter,
+                contentDescription = null,
+                modifier = Modifier.testTag(TEST_TAG),
+                tint = Color.Unspecified
+            )
+        }
+
+        // With no color provided for a tint, the icon should render the original pixels
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertPixels { Color.Red }
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun iconSpecifiedTintColorApplied() {
+        val width = 35.dp
+        val height = 83.dp
+        rule.setContent {
+            val image: ImageBitmap
+            with(LocalDensity.current) {
+                image = createBitmapWithColor(
+                    this,
+                    width.roundToPx(),
+                    height.roundToPx(),
+                    Color.Red
+                )
+            }
+            val painter = remember(image) { BitmapPainter(image) }
+            IconWithDefaults(
+                painter = painter,
+                contentDescription = null,
+                modifier = Modifier.testTag(TEST_TAG),
+                tint = Color.Blue
+            )
+        }
+
+        // With a tint color provided, all pixels should be blue
+        rule.onNodeWithTag(TEST_TAG).captureToImage().assertPixels { Color.Blue }
+    }
+
+    @Test
+    fun defaultSemanticsWhenContentDescriptionProvided() {
+        rule.setContent {
+            val painter = remember(ImageBitmap(100, 100)) { BitmapPainter(ImageBitmap(100, 100)) }
+            IconWithDefaults(
+                painter = painter,
+                contentDescription = "qwerty",
+                modifier = Modifier.testTag(TEST_TAG)
+            )
+        }
+
+        rule.onNodeWithTag(TEST_TAG)
+            .assertContentDescriptionEquals("qwerty")
+            .assert(SemanticsMatcher.expectValue(SemanticsProperties.Role, Role.Image))
+    }
+
+    private fun createBitmapWithColor(
+        density: Density,
+        width: Int,
+        height: Int,
+        color: Color
+    ): ImageBitmap {
+        val size = Size(width.toFloat(), height.toFloat())
+        val image = ImageBitmap(width, height)
+        CanvasDrawScope().draw(
+            density,
+            LayoutDirection.Ltr,
+            Canvas(image),
+            size
+        ) {
+            drawRect(color)
+        }
+        return image
+    }
+}
+
+@Composable
+internal fun IconWithDefaults(
+    painter: Painter,
+    contentDescription: String?,
+    modifier: Modifier = Modifier,
+    tint: Color = Color.Red
+) {
+    Icon(
+        painter = painter,
+        contentDescription = contentDescription,
+        tint = tint,
+        modifier = modifier
+    )
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/MaterialCoreTest.kt b/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/MaterialCoreTest.kt
index 4ff48c8..cee442c 100644
--- a/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/MaterialCoreTest.kt
+++ b/wear/compose/compose-material-core/src/androidAndroidTest/kotlin/androidx/wear/compose/materialcore/MaterialCoreTest.kt
@@ -20,6 +20,7 @@
 import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.ProvidableCompositionLocal
@@ -31,6 +32,8 @@
 import androidx.compose.ui.graphics.Shape
 import androidx.compose.ui.graphics.toPixelMap
 import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.SemanticsNodeInteraction
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.onNodeWithTag
@@ -45,6 +48,9 @@
 
 internal const val TEST_TAG = "test-item"
 
+val BigTestMaxWidth = 5000.dp
+val BigTestMaxHeight = 5000.dp
+
 internal enum class Status {
     Enabled,
     Disabled;
@@ -113,3 +119,27 @@
     }
     return histogram
 }
+
+internal fun ComposeContentTestRule.setContentForSizeAssertions(
+    parentMaxWidth: Dp = BigTestMaxWidth,
+    parentMaxHeight: Dp = BigTestMaxHeight,
+    useUnmergedTree: Boolean = false,
+    content: @Composable () -> Unit
+): SemanticsNodeInteraction {
+    setContent {
+        Box {
+            Box(
+                Modifier
+                    .sizeIn(
+                        maxWidth = parentMaxWidth,
+                        maxHeight = parentMaxHeight
+                    )
+                    .testTag("containerForSizeAssertion")
+            ) {
+                content()
+            }
+        }
+    }
+
+    return onNodeWithTag("containerForSizeAssertion", useUnmergedTree = useUnmergedTree)
+}
diff --git a/wear/compose/compose-material-core/src/commonMain/kotlin/androidx/wear/compose/materialcore/Icon.kt b/wear/compose/compose-material-core/src/commonMain/kotlin/androidx/wear/compose/materialcore/Icon.kt
new file mode 100644
index 0000000..33dcc31
--- /dev/null
+++ b/wear/compose/compose-material-core/src/commonMain/kotlin/androidx/wear/compose/materialcore/Icon.kt
@@ -0,0 +1,91 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.compose.materialcore
+
+import androidx.annotation.RestrictTo
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.draw.paint
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ColorFilter
+import androidx.compose.ui.graphics.painter.Painter
+import androidx.compose.ui.graphics.toolingGraphicsLayer
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.dp
+
+/**
+ * Icon component that draws a [painter] using [tint].
+ * TODO:// Add link to Chip for a clickable icon
+ *
+ * @param painter [Painter] to draw inside this Icon
+ * @param contentDescription Text used by accessibility services to describe what this icon
+ * represents. This should always be provided unless this icon is used for decorative purposes,
+ * and does not represent a meaningful action that a user can take. This text should be
+ * localized, such as by using [androidx.compose.ui.res.stringResource] or similar
+ * @param modifier Optional [Modifier] for this Icon
+ * @param tint Tint to be applied to [painter]. If [Color.Unspecified] is provided, then no
+ *  tint is applied
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@Composable
+fun Icon(
+    painter: Painter,
+    contentDescription: String?,
+    modifier: Modifier,
+    tint: Color
+) {
+    // TODO: b/149735981 semantics for content description
+    val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
+    val semantics = if (contentDescription != null) {
+        Modifier.semantics {
+            this.contentDescription = contentDescription
+            this.role = Role.Image
+        }
+    } else {
+        Modifier
+    }
+    Box(
+        modifier.toolingGraphicsLayer().defaultSizeFor(painter)
+            .paint(
+                painter,
+                colorFilter = colorFilter,
+                contentScale = ContentScale.Fit
+            )
+            .then(semantics)
+    )
+}
+
+internal fun Modifier.defaultSizeFor(painter: Painter) =
+    this.then(
+        if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
+            DefaultIconSizeModifier
+        } else {
+            Modifier
+        }
+    )
+
+internal fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
+
+// Default icon size, for icons with no intrinsic size information
+internal val DefaultIconSizeModifier = Modifier.size(24.dp)
diff --git a/wear/compose/compose-material/api/current.txt b/wear/compose/compose-material/api/current.txt
index 1be733d..761dc1e8 100644
--- a/wear/compose/compose-material/api/current.txt
+++ b/wear/compose/compose-material/api/current.txt
@@ -184,30 +184,6 @@
     method @Deprecated public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional long background, optional long color, optional long fontSize, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
   }
 
-  public final class ExpandableItemsDefaults {
-    method @androidx.compose.runtime.Composable public void Chevron(float progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional float strokeWidth);
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
-    field public static final androidx.wear.compose.material.ExpandableItemsDefaults INSTANCE;
-  }
-
-  public final class ExpandableItemsState {
-    method public float getExpandProgress();
-    method public boolean isExpanded();
-    method public void setExpanded(boolean);
-    method public void toggle();
-    property public final float expandProgress;
-    property public final boolean expanded;
-  }
-
-  public final class ExpandableKt {
-    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
-    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
-  }
-
   public final class HorizontalPageIndicatorKt {
     method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(androidx.wear.compose.material.PageIndicatorState pageIndicatorState, optional androidx.compose.ui.Modifier modifier, optional int indicatorStyle, optional long selectedColor, optional long unselectedColor, optional float indicatorSize, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape);
   }
diff --git a/wear/compose/compose-material/api/public_plus_experimental_current.txt b/wear/compose/compose-material/api/public_plus_experimental_current.txt
index c1255f3..3dd2192 100644
--- a/wear/compose/compose-material/api/public_plus_experimental_current.txt
+++ b/wear/compose/compose-material/api/public_plus_experimental_current.txt
@@ -185,30 +185,6 @@
     method @Deprecated public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional long background, optional long color, optional long fontSize, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
   }
 
-  public final class ExpandableItemsDefaults {
-    method @androidx.compose.runtime.Composable public void Chevron(float progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional float strokeWidth);
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
-    field public static final androidx.wear.compose.material.ExpandableItemsDefaults INSTANCE;
-  }
-
-  public final class ExpandableItemsState {
-    method public float getExpandProgress();
-    method public boolean isExpanded();
-    method public void setExpanded(boolean);
-    method public void toggle();
-    property public final float expandProgress;
-    property public final boolean expanded;
-  }
-
-  public final class ExpandableKt {
-    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
-    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
-  }
-
   @kotlin.RequiresOptIn(message="This Wear Material API is experimental and is likely to change or to be removed in" + " the future.") @kotlin.annotation.Retention(kotlin.annotation.AnnotationRetention.BINARY) public @interface ExperimentalWearMaterialApi {
   }
 
diff --git a/wear/compose/compose-material/api/restricted_current.txt b/wear/compose/compose-material/api/restricted_current.txt
index 1be733d..761dc1e8 100644
--- a/wear/compose/compose-material/api/restricted_current.txt
+++ b/wear/compose/compose-material/api/restricted_current.txt
@@ -184,30 +184,6 @@
     method @Deprecated public static void curvedText(androidx.wear.compose.foundation.CurvedScope, String text, optional androidx.wear.compose.foundation.CurvedModifier modifier, optional long background, optional long color, optional long fontSize, optional androidx.wear.compose.foundation.CurvedTextStyle? style, optional androidx.wear.compose.foundation.CurvedDirection.Angular? angularDirection, optional int overflow);
   }
 
-  public final class ExpandableItemsDefaults {
-    method @androidx.compose.runtime.Composable public void Chevron(float progress, optional androidx.compose.ui.Modifier modifier, optional long color, optional float strokeWidth);
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getCollapseAnimationSpec();
-    method public androidx.compose.animation.core.AnimationSpec<java.lang.Float> getExpandAnimationSpec();
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec;
-    property public final androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec;
-    field public static final androidx.wear.compose.material.ExpandableItemsDefaults INSTANCE;
-  }
-
-  public final class ExpandableItemsState {
-    method public float getExpandProgress();
-    method public boolean isExpanded();
-    method public void setExpanded(boolean);
-    method public void toggle();
-    property public final float expandProgress;
-    property public final boolean expanded;
-  }
-
-  public final class ExpandableKt {
-    method public static void expandableItem(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, optional Object? key, kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> content);
-    method public static void expandableItems(androidx.wear.compose.foundation.lazy.ScalingLazyListScope, androidx.wear.compose.material.ExpandableItemsState state, int count, optional kotlin.jvm.functions.Function1<? super java.lang.Integer,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.layout.BoxScope,? super java.lang.Integer,kotlin.Unit> itemContent);
-    method @androidx.compose.runtime.Composable public static androidx.wear.compose.material.ExpandableItemsState rememberExpandableItemsState(optional boolean initiallyExpanded, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> expandAnimationSpec, optional androidx.compose.animation.core.AnimationSpec<java.lang.Float> collapseAnimationSpec);
-  }
-
   public final class HorizontalPageIndicatorKt {
     method @androidx.compose.runtime.Composable public static void HorizontalPageIndicator(androidx.wear.compose.material.PageIndicatorState pageIndicatorState, optional androidx.compose.ui.Modifier modifier, optional int indicatorStyle, optional long selectedColor, optional long unselectedColor, optional float indicatorSize, optional float spacing, optional androidx.compose.ui.graphics.Shape indicatorShape);
   }
diff --git a/wear/compose/compose-material/build.gradle b/wear/compose/compose-material/build.gradle
index e4a4865..e1354a3 100644
--- a/wear/compose/compose-material/build.gradle
+++ b/wear/compose/compose-material/build.gradle
@@ -28,23 +28,23 @@
 AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project, /* isMultiplatformEnabled= */ false)
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.4.0-beta02")
-    api("androidx.compose.ui:ui:1.4.0-beta02")
-    api("androidx.compose.ui:ui-text:1.4.0-beta02")
-    api("androidx.compose.runtime:runtime:1.4.0-beta02")
+    api(project(":compose:foundation:foundation"))
+    api(project(":compose:ui:ui"))
+    api(project(":compose:ui:ui-text"))
+    api(project(":compose:runtime:runtime"))
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.animation:animation:1.4.0-beta02")
-    implementation("androidx.compose.material:material-icons-core:1.4.0-beta02")
-    implementation("androidx.compose.material:material-ripple:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
+    implementation(project(":compose:animation:animation"))
+    implementation(project(":compose:material:material-icons-core"))
+    implementation(project(":compose:material:material-ripple"))
+    implementation(project(":compose:ui:ui-util"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation(project(":wear:compose:compose-material-core"))
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
     implementation("androidx.lifecycle:lifecycle-common:2.5.1")
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
 
     androidTestImplementation(project(":test:screenshot:screenshot"))
diff --git a/wear/compose/compose-material/samples/build.gradle b/wear/compose/compose-material/samples/build.gradle
index a2423ec..55ec414 100644
--- a/wear/compose/compose-material/samples/build.gradle
+++ b/wear/compose/compose-material/samples/build.gradle
@@ -28,21 +28,21 @@
     implementation(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation("androidx.compose.animation:animation-graphics:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    implementation("androidx.compose.runtime:runtime:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-text:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-tooling-preview:1.4.0-beta02")
-    implementation("androidx.compose.material:material-icons-core:1.4.0-beta02")
+    implementation(project(":compose:animation:animation-graphics"))
+    implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-util"))
+    implementation(project(":compose:ui:ui-text"))
+    implementation(project(":compose:ui:ui-tooling-preview"))
+    implementation(project(":compose:material:material-icons-core"))
     implementation(project(":wear:compose:compose-material"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation(project(":wear:compose:compose-ui-tooling"))
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
 
     androidTestImplementation(libs.testRunner)
diff --git a/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt b/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
index adcec0e..0965d74 100644
--- a/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
+++ b/wear/compose/compose-material/src/androidMain/kotlin/androidx/wear/compose/material/dialog/Dialog.android.kt
@@ -21,6 +21,7 @@
 import androidx.compose.animation.core.Transition
 import androidx.compose.animation.core.TweenSpec
 import androidx.compose.animation.core.animateFloat
+import androidx.compose.animation.core.keyframes
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.core.updateTransition
 import androidx.compose.animation.fadeIn
@@ -29,7 +30,6 @@
 import androidx.compose.foundation.layout.Box
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.LaunchedEffect
-import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
@@ -161,34 +161,29 @@
     positionIndicator: @Composable () -> Unit,
     content: @Composable () -> Unit,
 ) {
-    // Transitions for background and 'dialog content' alpha.
-    var alphaTransitionState by remember {
-        mutableStateOf(MutableTransitionState(AlphaStage.IntroFadeOut))
+    // Transitions for dialog animation.
+    var transitionState by remember {
+        mutableStateOf(MutableTransitionState(DialogVisibility.Hide))
     }
-    val alphaTransition = updateTransition(alphaTransitionState)
+    val transition = updateTransition(transitionState)
 
-    // Transitions for dialog content scaling.
-    var scaleTransitionState by remember {
-        mutableStateOf(MutableTransitionState(ScaleStage.Intro))
+    var pendingOnDismissCall by remember {
+        mutableStateOf(false)
     }
-    val scaleTransition = updateTransition(scaleTransitionState)
 
-    if (showDialog ||
-        alphaTransitionState.targetState != AlphaStage.IntroFadeOut ||
-        scaleTransitionState.targetState != ScaleStage.Intro
-    ) {
+    if (showDialog || transition.currentState == DialogVisibility.Display) {
         Dialog(
             onDismissRequest = onDismissRequest,
             properties = properties,
         ) {
-            val backgroundAlpha by animateBackgroundAlpha(alphaTransition, alphaTransitionState)
-            val alpha by animateDialogAlpha(alphaTransition, alphaTransitionState)
-            val scale by animateDialogScale(scaleTransition, scaleTransitionState)
 
+            val backgroundScrimAlpha by animateBackgroundScrimAlpha(transition)
+            val contentAlpha by animateContentAlpha(transition)
+            val scale by animateDialogScale(transition)
             Scaffold(
                 vignette = {
                     AnimatedVisibility(
-                        visible = scaleTransitionState.targetState == ScaleStage.Display,
+                        visible = transition.targetState == DialogVisibility.Display,
                         enter = fadeIn(
                             animationSpec =
                             TweenSpec(durationMillis = CASUAL, easing = STANDARD_IN)
@@ -207,58 +202,45 @@
                 SwipeToDismissBox(
                     state = rememberSwipeToDismissBoxState(),
                     modifier = Modifier.graphicsLayer(
-                        alpha = alpha,
+                        alpha = backgroundScrimAlpha,
                         scaleX = scale,
                         scaleY = scale,
                     ),
                     onDismissed = {
                         onDismissRequest()
                         // Reset state for the next time this dialog is shown.
-                        alphaTransitionState = MutableTransitionState(AlphaStage.IntroFadeOut)
-                        scaleTransitionState = MutableTransitionState(ScaleStage.Intro)
+                        transitionState = MutableTransitionState(DialogVisibility.Hide)
                     }
                 ) { isBackground ->
                     Box(
                         modifier = Modifier
                             .matchParentSize()
-                            .background(
-                                MaterialTheme.colors.background.copy(alpha = backgroundAlpha)
-                            )
-                    )
-                    if (!isBackground) content()
+                            .graphicsLayer(alpha = contentAlpha)
+                            .background(MaterialTheme.colors.background)
+                    ) {
+                        if (!isBackground) content()
+                    }
                 }
             }
-
-            SideEffect {
-                // Trigger initial Intro animation
-                if (alphaTransitionState.currentState == AlphaStage.IntroFadeOut) {
-                    // a) Fade out previous screen contents b) Scale down dialog contents.
-                    alphaTransitionState.targetState = AlphaStage.IntroFadeIn
-                    scaleTransitionState.targetState = ScaleStage.Display
-                } else if (alphaTransitionState.currentState == AlphaStage.IntroFadeIn) {
-                    // Now conclude the Intro animation by fading in the dialog contents.
-                    alphaTransitionState.targetState = AlphaStage.Display
-                }
-            }
-
-            // Trigger Outro animation when the caller updates showDialog to false.
             LaunchedEffect(showDialog) {
-                if (!showDialog) {
+                if (showDialog) {
+                    // a) Fade out previous screen contents b) Scale down dialog contents from 125%
+                    transitionState.targetState = DialogVisibility.Display
+                    pendingOnDismissCall = true
+                } else {
                     // a) Fade out dialog contents b) Scale up dialog contents.
-                    alphaTransitionState.targetState = AlphaStage.OutroFadeOut
-                    scaleTransitionState.targetState = ScaleStage.Outro
+                    transitionState.targetState = DialogVisibility.Hide
                 }
             }
 
-            LaunchedEffect(alphaTransitionState.currentState) {
-                if (alphaTransitionState.currentState == AlphaStage.OutroFadeOut) {
-                    // Conclude the Outro animation by fading in the background contents.
-                    alphaTransitionState.targetState = AlphaStage.OutroFadeIn
-                } else if (alphaTransitionState.currentState == AlphaStage.OutroFadeIn) {
+            LaunchedEffect(transitionState.currentState) {
+                if (pendingOnDismissCall &&
+                    transitionState.currentState == DialogVisibility.Hide &&
+                    transitionState.isIdle
+                ) {
                     // After the outro animation, leave the dialog & reset alpha/scale transitions.
                     onDismissRequest()
-                    alphaTransitionState = MutableTransitionState(AlphaStage.IntroFadeOut)
-                    scaleTransitionState = MutableTransitionState(ScaleStage.Intro)
+                    pendingOnDismissCall = false
                 }
             }
         }
@@ -266,79 +248,79 @@
 }
 
 @Composable
-private fun animateBackgroundAlpha(
-    alphaTransition: Transition<AlphaStage>,
-    alphaTransitionState: MutableTransitionState<AlphaStage>
-) = alphaTransition.animateFloat(
+private fun animateBackgroundScrimAlpha(
+    transition: Transition<DialogVisibility>
+) = transition.animateFloat(
     transitionSpec = {
-        if (alphaTransitionState.currentState == AlphaStage.IntroFadeOut)
-            tween(durationMillis = RAPID, easing = STANDARD_OUT)
-        else if (alphaTransitionState.targetState == AlphaStage.OutroFadeIn)
-            tween(durationMillis = QUICK, easing = STANDARD_IN)
-        else
-            tween(durationMillis = 0)
+        when (transition.targetState) {
+            DialogVisibility.Display -> tween(
+                durationMillis = (RAPID / 0.9f).toInt(),
+                easing = STANDARD_OUT
+            )
+
+            DialogVisibility.Hide -> keyframes {
+                // Outro
+                durationMillis = QUICK + RAPID
+                1f at 0
+                0.9f at RAPID with STANDARD_IN
+                0.0f at RAPID + QUICK
+            }
+        }
     },
-    label = "background-alpha"
+    label = "background-scrim-alpha"
 ) { stage ->
     when (stage) {
-        AlphaStage.IntroFadeOut -> 0.0f
-        AlphaStage.IntroFadeIn -> 0.9f
-        AlphaStage.Display -> 1.0f
-        AlphaStage.OutroFadeOut -> 0.9f
-        AlphaStage.OutroFadeIn -> 0.0f
+        DialogVisibility.Hide -> 0f
+        DialogVisibility.Display -> 1f
     }
 }
 
 @Composable
-private fun animateDialogAlpha(
-    alphaTransition: Transition<AlphaStage>,
-    alphaTransitionState: MutableTransitionState<AlphaStage>
-) = alphaTransition.animateFloat(
+private fun animateContentAlpha(
+    transition: Transition<DialogVisibility>
+) = transition.animateFloat(
     transitionSpec = {
-        if (alphaTransitionState.currentState == AlphaStage.IntroFadeIn)
-            tween(durationMillis = QUICK, easing = STANDARD_IN)
-        else if (alphaTransitionState.targetState == AlphaStage.OutroFadeOut)
-            tween(durationMillis = RAPID, easing = STANDARD_OUT)
-        else
-            tween(durationMillis = 0)
+        when (transition.targetState) {
+            DialogVisibility.Display -> keyframes {
+                // Intro
+                durationMillis = QUICK + RAPID
+                0.0f at 0
+                0.1f at RAPID with STANDARD_IN
+                1f at RAPID + QUICK
+            }
+
+            DialogVisibility.Hide -> tween(
+                durationMillis = (RAPID / 0.9f).toInt(),
+                easing = STANDARD_OUT
+            )
+        }
     },
-    label = "alpha"
+    label = "content-alpha"
 ) { stage ->
     when (stage) {
-        AlphaStage.IntroFadeOut -> 0.0f
-        AlphaStage.IntroFadeIn -> 0.1f
-        AlphaStage.Display -> 1.0f
-        AlphaStage.OutroFadeOut -> 0.1f
-        AlphaStage.OutroFadeIn -> 0.0f
+        DialogVisibility.Hide -> 0f
+        DialogVisibility.Display -> 1f
     }
 }
 
 @Composable
 private fun animateDialogScale(
-    scaleTransition: Transition<ScaleStage>,
-    scaleTransitionState: MutableTransitionState<ScaleStage>
-) = scaleTransition.animateFloat(
+    transition: Transition<DialogVisibility>
+) = transition.animateFloat(
     transitionSpec = {
-        if (scaleTransitionState.currentState == ScaleStage.Intro)
-            tween(durationMillis = CASUAL, easing = STANDARD_IN)
-        else
-            tween(durationMillis = CASUAL, easing = STANDARD_OUT)
+        when (transition.targetState) {
+            DialogVisibility.Display -> tween(durationMillis = CASUAL, easing = STANDARD_IN)
+            DialogVisibility.Hide -> tween(durationMillis = CASUAL, easing = STANDARD_OUT)
+        }
     },
     label = "scale"
 ) { stage ->
     when (stage) {
-        ScaleStage.Intro -> 1.25f
-        ScaleStage.Display -> 1.0f
-        ScaleStage.Outro -> 1.25f
+        DialogVisibility.Hide -> 1.25f
+        DialogVisibility.Display -> 1.0f
     }
 }
 
-// Alpha transition stages - Intro and Outro are split into FadeIn/FadeOut stages.
-private enum class AlphaStage {
-    IntroFadeOut, IntroFadeIn, Display, OutroFadeOut, OutroFadeIn;
-}
-
-// Scale transition stages - scaling is applied as single Intro/Outro animations.
-private enum class ScaleStage {
-    Intro, Display, Outro;
-}
+private enum class DialogVisibility {
+    Hide, Display;
+}
\ No newline at end of file
diff --git a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Icon.kt b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Icon.kt
index f92d72d..181a2cd1 100644
--- a/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Icon.kt
+++ b/wear/compose/compose-material/src/commonMain/kotlin/androidx/wear/compose/material/Icon.kt
@@ -16,27 +16,15 @@
 
 package androidx.wear.compose.material
 
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.draw.paint
-import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.graphics.ColorFilter
 import androidx.compose.ui.graphics.ImageBitmap
 import androidx.compose.ui.graphics.painter.BitmapPainter
 import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.graphics.toolingGraphicsLayer
 import androidx.compose.ui.graphics.vector.ImageVector
 import androidx.compose.ui.graphics.vector.rememberVectorPainter
-import androidx.compose.ui.layout.ContentScale
-import androidx.compose.ui.semantics.Role
-import androidx.compose.ui.semantics.contentDescription
-import androidx.compose.ui.semantics.role
-import androidx.compose.ui.semantics.semantics
-import androidx.compose.ui.unit.dp
 
 /**
  * Icon component that draws [imageVector] using [tint], defaulting to [LocalContentColor]. For a
@@ -116,36 +104,10 @@
     tint: Color = LocalContentColor.current.copy(alpha = LocalContentAlpha.current)
 ) {
     // TODO: b/149735981 semantics for content description
-    val colorFilter = if (tint == Color.Unspecified) null else ColorFilter.tint(tint)
-    val semantics = if (contentDescription != null) {
-        Modifier.semantics {
-            this.contentDescription = contentDescription
-            this.role = Role.Image
-        }
-    } else {
-        Modifier
-    }
-    Box(
-        modifier.toolingGraphicsLayer().defaultSizeFor(painter)
-            .paint(
-                painter,
-                colorFilter = colorFilter,
-                contentScale = ContentScale.Fit
-            )
-            .then(semantics)
+    androidx.wear.compose.materialcore.Icon(
+        painter = painter,
+        contentDescription = contentDescription,
+        modifier = modifier,
+        tint = tint
     )
 }
-
-private fun Modifier.defaultSizeFor(painter: Painter) =
-    this.then(
-        if (painter.intrinsicSize == Size.Unspecified || painter.intrinsicSize.isInfinite()) {
-            DefaultIconSizeModifier
-        } else {
-            Modifier
-        }
-    )
-
-private fun Size.isInfinite() = width.isInfinite() && height.isInfinite()
-
-// Default icon size, for icons with no intrinsic size information
-private val DefaultIconSizeModifier = Modifier.size(24.dp)
diff --git a/wear/compose/compose-material3/build.gradle b/wear/compose/compose-material3/build.gradle
index c51408c..fd5a497 100644
--- a/wear/compose/compose-material3/build.gradle
+++ b/wear/compose/compose-material3/build.gradle
@@ -28,22 +28,22 @@
 AndroidXComposePlugin.applyAndConfigureKotlinPlugin(project, /* isMultiplatformEnabled= */ false)
 
 dependencies {
-    api("androidx.compose.foundation:foundation:1.4.0-beta02")
-    api("androidx.compose.ui:ui:1.4.0-beta02")
-    api("androidx.compose.ui:ui-text:1.4.0-beta02")
-    api("androidx.compose.runtime:runtime:1.4.0-beta02")
+    api(project(":compose:foundation:foundation"))
+    api(project(":compose:ui:ui"))
+    api(project(":compose:ui:ui-text"))
+    api(project(":compose:runtime:runtime"))
 
     implementation(libs.kotlinStdlib)
-    implementation("androidx.compose.animation:animation:1.4.0-beta02")
-    implementation("androidx.compose.material:material-icons-core:1.4.0-beta02")
-    implementation("androidx.compose.material:material-ripple:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-util:1.4.0-beta02")
+    implementation(project(":compose:animation:animation"))
+    implementation(project(":compose:material:material-icons-core"))
+    implementation(project(":compose:material:material-ripple"))
+    implementation(project(":compose:ui:ui-util"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation(project(":wear:compose:compose-material-core"))
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
 
     androidTestImplementation(project(":test:screenshot:screenshot"))
diff --git a/wear/compose/compose-material3/samples/build.gradle b/wear/compose/compose-material3/samples/build.gradle
index eefc0f8..d09ea46 100644
--- a/wear/compose/compose-material3/samples/build.gradle
+++ b/wear/compose/compose-material3/samples/build.gradle
@@ -28,18 +28,18 @@
     implementation(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation("androidx.compose.animation:animation-graphics:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    implementation("androidx.compose.runtime:runtime:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-text:1.4.0-beta02")
-    implementation("androidx.compose.material:material-icons-core:1.4.0-beta02")
+    implementation(project(":compose:animation:animation-graphics"))
+    implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-text"))
+    implementation(project(":compose:material:material-icons-core"))
     implementation(project(":wear:compose:compose-material3"))
     implementation(project(":wear:compose:compose-foundation"))
 
-    androidTestImplementation("androidx.compose.ui:ui-test:1.4.0-beta02")
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test"))
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":compose:test-utils"))
 
     androidTestImplementation(libs.testRunner)
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index a7e6eed..cee0747 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -25,8 +25,8 @@
 
 dependencies {
 
-    api("androidx.compose.ui:ui:1.4.0-beta02")
-    api("androidx.compose.runtime:runtime:1.4.0-beta02")
+    api(project(":compose:ui:ui"))
+    api(project(":compose:runtime:runtime"))
     api("androidx.navigation:navigation-runtime:2.5.3")
     api(project(":wear:compose:compose-material"))
     api(project(":activity:activity-compose"))
@@ -38,13 +38,13 @@
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
 
     androidTestImplementation(project(":compose:test-utils"))
-    androidTestImplementation("androidx.compose.ui:ui-test-junit4:1.4.0-beta02")
+    androidTestImplementation(project(":compose:ui:ui-test-junit4"))
     androidTestImplementation(project(":navigation:navigation-common"))
     androidTestImplementation(libs.testRunner)
     androidTestImplementation(project(":wear:compose:compose-material"))
     androidTestImplementation(project(":wear:compose:compose-navigation-samples"))
     androidTestImplementation(libs.truth)
-    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.5.1")
+    androidTestImplementation("androidx.lifecycle:lifecycle-runtime-testing:2.6.0")
     androidTestImplementation("androidx.navigation:navigation-testing:2.5.3")
 
     samples(project(":wear:compose:compose-navigation-samples"))
diff --git a/wear/compose/compose-navigation/samples/build.gradle b/wear/compose/compose-navigation/samples/build.gradle
index 5b891ca..25a4b70 100644
--- a/wear/compose/compose-navigation/samples/build.gradle
+++ b/wear/compose/compose-navigation/samples/build.gradle
@@ -28,12 +28,12 @@
     implementation(libs.kotlinStdlib)
 
     compileOnly(project(":annotation:annotation-sampled"))
-    implementation("androidx.compose.animation:animation-graphics:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation:1.4.0-beta02")
-    implementation("androidx.compose.foundation:foundation-layout:1.4.0-beta02")
-    implementation("androidx.compose.runtime:runtime:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui:1.4.0-beta02")
-    implementation("androidx.compose.ui:ui-text:1.4.0-beta02")
+    implementation(project(":compose:animation:animation-graphics"))
+    implementation(project(":compose:foundation:foundation"))
+    implementation(project(":compose:foundation:foundation-layout"))
+    implementation(project(":compose:runtime:runtime"))
+    implementation(project(":compose:ui:ui"))
+    implementation(project(":compose:ui:ui-text"))
     implementation(project(":wear:compose:compose-material"))
     implementation(project(":wear:compose:compose-foundation"))
     implementation(project(":wear:compose:compose-navigation"))
diff --git a/wear/compose/compose-ui-tooling/build.gradle b/wear/compose/compose-ui-tooling/build.gradle
index 3da8aaf..5c5e81d 100644
--- a/wear/compose/compose-ui-tooling/build.gradle
+++ b/wear/compose/compose-ui-tooling/build.gradle
@@ -30,7 +30,7 @@
     api("androidx.annotation:annotation:1.5.0")
 
     implementation(libs.kotlinStdlibCommon)
-    implementation("androidx.compose.ui:ui-tooling-preview:1.4.0-beta02")
+    implementation(project(":compose:ui:ui-tooling-preview"))
 
     samples(project(":wear:compose:compose-material-samples"))
 }
diff --git a/wear/compose/integration-tests/demos/build.gradle b/wear/compose/integration-tests/demos/build.gradle
index 4f95b89..f1d47a0 100644
--- a/wear/compose/integration-tests/demos/build.gradle
+++ b/wear/compose/integration-tests/demos/build.gradle
@@ -26,8 +26,8 @@
         applicationId "androidx.wear.compose.integration.demos"
         minSdk 25
         targetSdk 30
-        versionCode 12
-        versionName "1.12"
+        versionCode 13
+        versionName "1.13"
         // Change the APK name to match the *testapp regex we use to pick up APKs for testing as
         // part of CI.
         archivesBaseName = "wear-compose-demos-testapp"
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
index fd110fe..8d61b3f 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/DialogDemo.kt
@@ -40,6 +40,7 @@
 import androidx.wear.compose.material.ButtonDefaults
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
+import androidx.wear.compose.material.Colors
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.Text
 import androidx.wear.compose.material.dialog.Alert
@@ -139,49 +140,53 @@
 
 @Composable
 fun DialogBackground(background: Color) {
-    // By setting the background of the launcher and the dialog,
+    // By setting a bespoke background theme color for the launcher and dialog,
     // it is easier to see animation details such as the vignette.
     var showDialog by remember { mutableStateOf(false) }
     val scrollState = rememberScalingLazyListState()
-    LaunchScreen(onClick = { showDialog = true }, background = background)
-    Dialog(
-        showDialog = showDialog,
-        onDismissRequest = { showDialog = false },
-        scrollState = scrollState,
-    ) {
-        Alert(
-            backgroundColor = background,
+    MaterialTheme(colors = Colors(background = background)) {
+        LaunchScreen(onClick = { showDialog = true })
+        Dialog(
+            showDialog = showDialog,
+            onDismissRequest = { showDialog = false },
             scrollState = scrollState,
-            icon = {
-                DemoIcon(
-                    resourceId = R.drawable.ic_baseline_location_on_24,
-                    contentDescription = "Location"
-                )
-            },
-            title = {
-                Text(
-                    text = "Allow Bikemap to access this device's location?",
-                    textAlign = TextAlign.Center,
-                    color = MaterialTheme.colors.onBackground
-                )
-            },
-            negativeButton = {
-                Button(
-                    onClick = { showDialog = false },
-                    colors = ButtonDefaults.secondaryButtonColors()
-                ) {
-                    DemoIcon(resourceId = R.drawable.ic_clear_24px, contentDescription = "Cross")
-                }
-            },
-            positiveButton = {
-                Button(
-                    onClick = { showDialog = false },
-                    colors = ButtonDefaults.primaryButtonColors()
-                ) {
-                    DemoIcon(resourceId = R.drawable.ic_check_24px, contentDescription = "Tick")
-                }
-            },
-        )
+        ) {
+            Alert(
+                scrollState = scrollState,
+                icon = {
+                    DemoIcon(
+                        resourceId = R.drawable.ic_baseline_location_on_24,
+                        contentDescription = "Location"
+                    )
+                },
+                title = {
+                    Text(
+                        text = "Allow Bikemap to access this device's location?",
+                        textAlign = TextAlign.Center,
+                        color = MaterialTheme.colors.onBackground
+                    )
+                },
+                negativeButton = {
+                    Button(
+                        onClick = { showDialog = false },
+                        colors = ButtonDefaults.secondaryButtonColors()
+                    ) {
+                        DemoIcon(
+                            resourceId = R.drawable.ic_clear_24px,
+                            contentDescription = "Cross"
+                        )
+                    }
+                },
+                positiveButton = {
+                    Button(
+                        onClick = { showDialog = false },
+                        colors = ButtonDefaults.primaryButtonColors()
+                    ) {
+                        DemoIcon(resourceId = R.drawable.ic_check_24px, contentDescription = "Tick")
+                    }
+                },
+            )
+        }
     }
 }
 
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
index 39f1e3c..e40b68a 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/ExpandableDemo.kt
@@ -29,18 +29,18 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
+import androidx.wear.compose.foundation.ExpandableItemsDefaults
+import androidx.wear.compose.foundation.ExpandableItemsState
+import androidx.wear.compose.foundation.expandableItem
+import androidx.wear.compose.foundation.expandableItems
 import androidx.wear.compose.foundation.lazy.ScalingLazyColumn
 import androidx.wear.compose.foundation.lazy.ScalingLazyListScope
+import androidx.wear.compose.foundation.rememberExpandableItemsState
 import androidx.wear.compose.material.Chip
 import androidx.wear.compose.material.ChipDefaults
-import androidx.wear.compose.material.ExpandableItemsDefaults
 import androidx.wear.compose.material.MaterialTheme
 import androidx.wear.compose.material.OutlinedChip
-import androidx.wear.compose.material.ExpandableItemsState
 import androidx.wear.compose.material.Text
-import androidx.wear.compose.material.rememberExpandableItemsState
-import androidx.wear.compose.material.expandableItem
-import androidx.wear.compose.material.expandableItems
 
 @Composable
 fun ExpandableListItems() {
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
index 49fb373..c0f9761 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/FoundationDemos.kt
@@ -24,6 +24,8 @@
 import androidx.wear.compose.foundation.samples.CurvedFonts
 import androidx.wear.compose.foundation.samples.CurvedRowAndColumn
 import androidx.wear.compose.foundation.samples.CurvedWeight
+import androidx.wear.compose.foundation.samples.ExpandableTextSample
+import androidx.wear.compose.foundation.samples.ExpandableWithItemsSample
 import androidx.wear.compose.foundation.samples.HierarchicalFocusCoordinatorSample
 import androidx.wear.compose.foundation.samples.OversizeComposable
 import androidx.wear.compose.foundation.samples.ScalingLazyColumnEdgeAnchoredAndAnimatedScrollTo
@@ -35,6 +37,15 @@
 val WearFoundationDemos = DemoCategory(
     "Foundation",
     listOf(
+        DemoCategory(
+            "Expandables",
+            listOf(
+                ComposableDemo("Items in SLC") { ExpandableListItems() },
+                ComposableDemo("Expandable Text") { ExpandableText() },
+                ComposableDemo("Items Sample") { ExpandableWithItemsSample() },
+                ComposableDemo("Text Sample") { ExpandableTextSample() },
+            )
+        ),
         DemoCategory("CurvedLayout", listOf(
             ComposableDemo("Curved Row") { CurvedWorldDemo() },
             ComposableDemo("Curved Row and Column") { CurvedRowAndColumn() },
diff --git a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
index baf4417..45ecbf2 100644
--- a/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
+++ b/wear/compose/integration-tests/demos/src/main/java/androidx/wear/compose/integration/demos/MaterialDemos.kt
@@ -50,8 +50,6 @@
 import androidx.wear.compose.material.samples.CurvedTextDemo
 import androidx.wear.compose.material.samples.CurvedTextProviderDemo
 import androidx.wear.compose.material.samples.EdgeSwipeForSwipeToDismiss
-import androidx.wear.compose.material.samples.ExpandableTextSample
-import androidx.wear.compose.material.samples.ExpandableWithItemsSample
 import androidx.wear.compose.material.samples.FixedFontSize
 import androidx.wear.compose.material.samples.HorizontalPageIndicatorSample
 import androidx.wear.compose.material.samples.IndeterminateCircularProgressIndicator
@@ -130,15 +128,6 @@
     "Material",
     listOf(
         DemoCategory(
-            "Expandables",
-            listOf(
-                ComposableDemo("Items in SLC") { ExpandableListItems() },
-                ComposableDemo("Expandable Text") { ExpandableText() },
-                ComposableDemo("Items Sample") { ExpandableWithItemsSample() },
-                ComposableDemo("Text Sample") { ExpandableTextSample() },
-            )
-        ),
-        DemoCategory(
             "ScrollAway",
             listOf(
                 ComposableDemo("Column") { ScrollAwayColumnDemo() },
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 450087c..cfe4d48 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
@@ -43,7 +43,7 @@
 import androidx.wear.protolayout.expression.pipeline.FloatNodes.DynamicAnimatedFloatNode;
 import androidx.wear.protolayout.expression.pipeline.FloatNodes.FixedFloatNode;
 import androidx.wear.protolayout.expression.pipeline.FloatNodes.Int32ToFloatNode;
-import androidx.wear.protolayout.expression.pipeline.FloatNodes.StateFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.StateFloatSourceNode;
 import androidx.wear.protolayout.expression.pipeline.InstantNodes.FixedInstantNode;
 import androidx.wear.protolayout.expression.pipeline.InstantNodes.PlatformTimeSourceNode;
 import androidx.wear.protolayout.expression.pipeline.Int32Nodes.AnimatableFixedInt32Node;
@@ -54,7 +54,6 @@
 import androidx.wear.protolayout.expression.pipeline.Int32Nodes.GetDurationPartOpNode;
 import androidx.wear.protolayout.expression.pipeline.Int32Nodes.PlatformInt32SourceNode;
 import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode;
-import androidx.wear.protolayout.expression.pipeline.PlatformDataSources.SensorGatewayPlatformDataSource;
 import androidx.wear.protolayout.expression.pipeline.StringNodes.FixedStringNode;
 import androidx.wear.protolayout.expression.pipeline.StringNodes.FloatFormatNode;
 import androidx.wear.protolayout.expression.pipeline.StringNodes.Int32FormatNode;
@@ -939,9 +938,8 @@
                 node = new FixedFloatNode(floatSource.getFixed(), consumer);
                 break;
             case STATE_SOURCE:
-                node =
-                        new StateFloatNode(
-                                mStateStore, floatSource.getStateSource().getSourceKey(), consumer);
+                node = new StateFloatSourceNode(
+                                mStateStore, floatSource.getStateSource(), consumer);
                 break;
             case ARITHMETIC_OPERATION:
                 {
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 ebfe128..ca8133c 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
@@ -24,6 +24,7 @@
 import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedFloat;
 import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticFloatOp;
+import androidx.wear.protolayout.expression.proto.DynamicProto.StateFloatSource;
 import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
 
 /** Dynamic data nodes which yield floats. */
@@ -59,12 +60,16 @@
     }
 
     /** Dynamic float node that gets value from the state. */
-    static class StateFloatNode extends StateSourceNode<Float> {
-        StateFloatNode(
+    static class StateFloatSourceNode extends StateSourceNode<Float> {
+        StateFloatSourceNode(
                 ObservableStateStore observableStateStore,
-                String bindKey,
+                StateFloatSource protoNode,
                 DynamicTypeValueReceiver<Float> downstream) {
-            super(observableStateStore, bindKey, se -> se.getFloatVal().getValue(), downstream);
+            super(
+                    observableStateStore,
+                    protoNode.getSourceKey(),
+                    se -> se.getFloatVal().getValue(),
+                    downstream);
         }
     }
 
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 40bf780..1aa940a 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
@@ -23,8 +23,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.UiThread;
-import androidx.wear.protolayout.expression.pipeline.PlatformDataSources.PlatformDataSource;
-import androidx.wear.protolayout.expression.pipeline.PlatformDataSources.SensorGatewayPlatformDataSource;
 import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
 import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedInt32;
 import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticInt32Op;
@@ -40,7 +38,9 @@
 
 /** Dynamic data nodes which yield integers. */
 class Int32Nodes {
-    private Int32Nodes() {}
+
+    private Int32Nodes() {
+    }
 
     /** Dynamic integer node that has a fixed value. */
     static class FixedInt32Node implements DynamicDataSourceNode<Integer> {
@@ -74,22 +74,30 @@
         private static final String TAG = "PlatformInt32SourceNode";
 
         @Nullable private final SensorGatewayPlatformDataSource mSensorGatewaySource;
-        private final PlatformInt32Source mProtoNode;
+        private final PlatformInt32SourceType mPlatformSourceType;
         private final DynamicTypeValueReceiver<Integer> mDownstream;
 
         PlatformInt32SourceNode(
                 PlatformInt32Source protoNode,
                 @Nullable SensorGatewayPlatformDataSource sensorGatewaySource,
                 DynamicTypeValueReceiver<Integer> downstream) {
-            this.mProtoNode = protoNode;
-            this.mSensorGatewaySource = sensorGatewaySource;
+            this.mPlatformSourceType = protoNode.getSourceType();
+            if (mPlatformSourceType
+                    == PlatformInt32SourceType.PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE
+                    || mPlatformSourceType
+                    == PlatformInt32SourceType.PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT) {
+                this.mSensorGatewaySource = sensorGatewaySource;
+            } else {
+                this.mSensorGatewaySource = null;
+                Log.w(TAG, "Unknown PlatformInt32SourceType: " + mPlatformSourceType);
+            }
             this.mDownstream = downstream;
         }
 
         @Override
         @UiThread
         public void preInit() {
-            if (platformInt32SourceTypeToPlatformDataSource(mProtoNode.getSourceType()) != null) {
+            if (mSensorGatewaySource != null) {
                 mDownstream.onPreUpdate();
             }
         }
@@ -97,10 +105,14 @@
         @Override
         @UiThread
         public void init() {
-            PlatformDataSource dataSource =
-                    platformInt32SourceTypeToPlatformDataSource(mProtoNode.getSourceType());
-            if (dataSource != null) {
-                dataSource.registerForData(mProtoNode.getSourceType(), mDownstream);
+            if (mSensorGatewaySource != null) {
+                try {
+                    mSensorGatewaySource.registerForData(mPlatformSourceType, mDownstream);
+                } catch (SecurityException e) {
+                    // Package does not have the permission to request the health data.
+                    Log.w(TAG, e.getMessage(), e);
+                    mDownstream.onInvalidated();
+                }
             } else {
                 mDownstream.onInvalidated();
             }
@@ -109,28 +121,10 @@
         @Override
         @UiThread
         public void destroy() {
-            PlatformDataSource dataSource =
-                    platformInt32SourceTypeToPlatformDataSource(mProtoNode.getSourceType());
-            if (dataSource != null) {
-                dataSource.unregisterForData(mProtoNode.getSourceType(), mDownstream);
+            if (mSensorGatewaySource != null) {
+                mSensorGatewaySource.unregisterForData(mPlatformSourceType, mDownstream);
             }
         }
-
-        @Nullable
-        private PlatformDataSource platformInt32SourceTypeToPlatformDataSource(
-                PlatformInt32SourceType sourceType) {
-            switch (sourceType) {
-                case UNRECOGNIZED:
-                case PLATFORM_INT32_SOURCE_TYPE_UNDEFINED:
-                    Log.w(TAG, "Unknown PlatformInt32SourceType");
-                    return null;
-                case PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE:
-                case PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT:
-                    return mSensorGatewaySource;
-            }
-            Log.w(TAG, "Unknown PlatformInt32SourceType");
-            return null;
-        }
     }
 
     /** Dynamic integer node that supports arithmetic operations. */
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataSources.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataSources.java
deleted file mode 100644
index f070941..0000000
--- a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/PlatformDataSources.java
+++ /dev/null
@@ -1,114 +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.wear.protolayout.expression.pipeline;
-
-import android.os.Build.VERSION;
-import android.os.Build.VERSION_CODES;
-
-import androidx.annotation.DoNotInline;
-import androidx.annotation.RequiresApi;
-import androidx.collection.ArrayMap;
-import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
-import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.SensorDataType;
-import androidx.wear.protolayout.expression.proto.DynamicProto.PlatformInt32SourceType;
-
-import java.util.Map;
-import java.util.concurrent.Executor;
-
-/** Utility for various platform data sources. */
-class PlatformDataSources {
-
-    private PlatformDataSources() {}
-
-    interface PlatformDataSource {
-        void registerForData(
-                PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> consumer);
-
-        void unregisterForData(
-                PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> consumer);
-    }
-
-    /** Utility for sensor data source. */
-    static class SensorGatewayPlatformDataSource implements PlatformDataSource {
-        private static final String TAG = "SensorGtwPltDataSource";
-        final Executor mUiExecutor;
-        private final SensorGateway mSensorGateway;
-        private final Map<DynamicTypeValueReceiver<Integer>, SensorGateway.Consumer>
-                mCallbackToRegisteredSensorConsumer = new ArrayMap<>();
-
-        SensorGatewayPlatformDataSource(Executor uiExecutor, SensorGateway sensorGateway) {
-            this.mUiExecutor = uiExecutor;
-            this.mSensorGateway = sensorGateway;
-        }
-
-        @SensorDataType
-        private int mapSensorPlatformSource(PlatformInt32SourceType platformSource) {
-            switch (platformSource) {
-                case PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE:
-                    return SensorGateway.SENSOR_DATA_TYPE_HEART_RATE;
-                case PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT:
-                    if (VERSION.SDK_INT >= VERSION_CODES.Q) {
-                        return Api29Impl.getSensorDataTypeDailyStepCount();
-                    } else {
-                        return SensorGateway.SENSOR_DATA_TYPE_INVALID;
-                    }
-                default:
-                    throw new IllegalArgumentException("Unknown PlatformSourceType");
-            }
-        }
-
-        @RequiresApi(VERSION_CODES.Q)
-        private static class Api29Impl {
-            @DoNotInline
-            static int getSensorDataTypeDailyStepCount() {
-                return SensorGateway.SENSOR_DATA_TYPE_DAILY_STEP_COUNT;
-            }
-        }
-
-        @Override
-        @SuppressWarnings("ExecutorTaskName")
-        public void registerForData(
-                PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> callback) {
-            @SensorDataType int sensorDataType = mapSensorPlatformSource(sourceType);
-            SensorGateway.Consumer sensorConsumer =
-                    new SensorGateway.Consumer() {
-                        @Override
-                        public void onData(double value) {
-                            mUiExecutor.execute(() -> callback.onData((int) value));
-                        }
-
-                        @Override
-                        @SensorDataType
-                        public int getRequestedDataType() {
-                            return sensorDataType;
-                        }
-                    };
-            mCallbackToRegisteredSensorConsumer.put(callback, sensorConsumer);
-            mSensorGateway.registerSensorGatewayConsumer(sensorConsumer);
-        }
-
-        @Override
-        public void unregisterForData(
-                PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> consumer) {
-            SensorGateway.Consumer sensorConsumer =
-                    mCallbackToRegisteredSensorConsumer.get(consumer);
-            if (sensorConsumer != null) {
-                mSensorGateway.unregisterSensorGatewayConsumer(sensorConsumer);
-            }
-        }
-    }
-}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewayPlatformDataSource.java b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewayPlatformDataSource.java
new file mode 100644
index 0000000..44e85c8
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/main/java/androidx/wear/protolayout/expression/pipeline/SensorGatewayPlatformDataSource.java
@@ -0,0 +1,95 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.expression.pipeline;
+
+import android.os.Build.VERSION;
+import android.os.Build.VERSION_CODES;
+import androidx.annotation.DoNotInline;
+import androidx.annotation.RequiresApi;
+import androidx.collection.ArrayMap;
+import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway;
+import androidx.wear.protolayout.expression.pipeline.sensor.SensorGateway.SensorDataType;
+import androidx.wear.protolayout.expression.proto.DynamicProto.PlatformInt32SourceType;
+import java.util.Map;
+import java.util.concurrent.Executor;
+
+/** Utility for sensor data source. */
+class SensorGatewayPlatformDataSource {
+    private static final String TAG = "SensorGtwPltDataSource";
+    final Executor mUiExecutor;
+    private final SensorGateway mSensorGateway;
+    private final Map<DynamicTypeValueReceiver<Integer>, SensorGateway.Consumer>
+            mCallbackToRegisteredSensorConsumer = new ArrayMap<>();
+
+    SensorGatewayPlatformDataSource(Executor uiExecutor, SensorGateway sensorGateway) {
+        this.mUiExecutor = uiExecutor;
+        this.mSensorGateway = sensorGateway;
+    }
+
+    @SensorDataType
+    private static int mapSensorPlatformSource(PlatformInt32SourceType platformSource) {
+        switch (platformSource) {
+            case PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE:
+                return SensorGateway.SENSOR_DATA_TYPE_HEART_RATE;
+            case PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT:
+                if (VERSION.SDK_INT >= VERSION_CODES.Q) {
+                    return Api29Impl.getSensorDataTypeDailyStepCount();
+                } else {
+                    return SensorGateway.SENSOR_DATA_TYPE_INVALID;
+                }
+            default:
+                throw new IllegalArgumentException("Unknown PlatformSourceType");
+        }
+    }
+
+    @SuppressWarnings("ExecutorTaskName")
+    public void registerForData(
+            PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> callback) {
+        @SensorDataType int sensorDataType = mapSensorPlatformSource(sourceType);
+        SensorGateway.Consumer sensorConsumer =
+                new SensorGateway.Consumer() {
+                    @Override
+                    public void onData(double value) {
+                        mUiExecutor.execute(() -> callback.onData((int) value));
+                    }
+
+                    @Override
+                    @SensorDataType
+                    public int getRequestedDataType() {
+                        return sensorDataType;
+                    }
+                };
+        mCallbackToRegisteredSensorConsumer.put(callback, sensorConsumer);
+        mSensorGateway.registerSensorGatewayConsumer(sensorConsumer);
+    }
+
+    public void unregisterForData(
+            PlatformInt32SourceType sourceType, DynamicTypeValueReceiver<Integer> consumer) {
+        SensorGateway.Consumer sensorConsumer = mCallbackToRegisteredSensorConsumer.get(consumer);
+        if (sensorConsumer != null) {
+            mSensorGateway.unregisterSensorGatewayConsumer(sensorConsumer);
+        }
+    }
+
+    @RequiresApi(VERSION_CODES.Q)
+    private static class Api29Impl {
+        @DoNotInline
+        static int getSensorDataTypeDailyStepCount() {
+            return SensorGateway.SENSOR_DATA_TYPE_DAILY_STEP_COUNT;
+        }
+    }
+}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ColorNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ColorNodesTest.java
index 007ea68..551afbb 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ColorNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/ColorNodesTest.java
@@ -57,6 +57,7 @@
         FixedColor protoNode = FixedColor.newBuilder().setArgb(FROM_COLOR).build();
         FixedColorNode node = new FixedColorNode(protoNode, new AddToListCallback<>(results));
 
+        node.preInit();
         node.init();
 
         assertThat(results).containsExactly(FROM_COLOR);
@@ -152,6 +153,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(true);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -174,6 +176,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(false);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -195,6 +198,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(true);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
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
new file mode 100644
index 0000000..3eb57343d
--- /dev/null
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/FloatNodeTest.java
@@ -0,0 +1,363 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.expression.pipeline;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static java.lang.Integer.MAX_VALUE;
+
+import static org.robolectric.Shadows.shadowOf;
+
+import android.os.Looper;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.AnimatableFixedFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.ArithmeticFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.DynamicAnimatedFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.FixedFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.Int32ToFloatNode;
+import androidx.wear.protolayout.expression.pipeline.FloatNodes.StateFloatSourceNode;
+import androidx.wear.protolayout.expression.pipeline.Int32Nodes.StateInt32SourceNode;
+import androidx.wear.protolayout.expression.proto.AnimationParameterProto.AnimationSpec;
+import androidx.wear.protolayout.expression.proto.DynamicProto.AnimatableFixedFloat;
+import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticFloatOp;
+import androidx.wear.protolayout.expression.proto.DynamicProto.ArithmeticOpType;
+import androidx.wear.protolayout.expression.proto.DynamicProto.StateFloatSource;
+import androidx.wear.protolayout.expression.proto.DynamicProto.StateInt32Source;
+import androidx.wear.protolayout.expression.proto.FixedProto.FixedFloat;
+import androidx.wear.protolayout.expression.proto.FixedProto.FixedInt32;
+import androidx.wear.protolayout.expression.proto.StateEntryProto.StateEntryValue;
+
+import com.google.common.collect.ImmutableMap;
+import com.google.common.collect.Iterables;
+
+import java.util.ArrayList;
+import java.util.List;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+public class FloatNodeTest {
+
+    @Test
+    public void fixedFloatNodesTest() {
+        List<Float> results = new ArrayList<>();
+        float testValue = 6.6f;
+
+        FixedFloat protoNode = FixedFloat.newBuilder().setValue(testValue).build();
+        FixedFloatNode node = new FixedFloatNode(protoNode, new AddToListCallback<>(results));
+
+        node.preInit();
+        node.init();
+
+        assertThat(results).containsExactly(testValue);
+    }
+
+    @Test
+    public void stateFloatSourceNodeTest() {
+        List<Float> results = new ArrayList<>();
+        float testValue = 6.6f;
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setFloatVal(FixedFloat.newBuilder().setValue(testValue))
+                                        .build()));
+
+        StateFloatSource protoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+        StateFloatSourceNode node =
+                new StateFloatSourceNode(oss, protoNode, new AddToListCallback<>(results));
+
+        node.preInit();
+        node.init();
+
+        assertThat(results).containsExactly(testValue);
+    }
+
+    @Test
+    public void stateFloatSourceNode_updatesWithStateChanges() {
+        List<Float> results = new ArrayList<>();
+        float oldValue = 6.5f;
+        float newValue = 7.8f;
+
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setFloatVal(FixedFloat.newBuilder().setValue(oldValue))
+                                        .build()));
+
+        StateFloatSource protoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+        StateFloatSourceNode node =
+                new StateFloatSourceNode(oss, protoNode, new AddToListCallback<>(results));
+
+        node.preInit();
+        node.init();
+        assertThat(results).containsExactly(oldValue);
+
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setFloatVal(FixedFloat.newBuilder().setValue(newValue))
+                                .build()));
+
+        assertThat(results).containsExactly(oldValue, newValue).inOrder();
+    }
+
+    @Test
+    public void stateFloatSourceNode_noUpdatesAfterDestroy() {
+        List<Float> results = new ArrayList<>();
+        float oldValue = 6.5f;
+        float newValue = 7.8f;
+
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setFloatVal(FixedFloat.newBuilder().setValue(oldValue))
+                                        .build()));
+
+        StateFloatSource protoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+        StateFloatSourceNode node =
+                new StateFloatSourceNode(oss, protoNode, new AddToListCallback<>(results));
+
+        node.preInit();
+        node.init();
+        assertThat(results).containsExactly(oldValue);
+
+        results.clear();
+        node.destroy();
+
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setFloatVal(FixedFloat.newBuilder().setValue(newValue))
+                                .build()));
+
+        assertThat(results).isEmpty();
+    }
+
+    @Test
+    public void arithmeticFloat_add() {
+        List<Float> results = new ArrayList<>();
+        ArithmeticFloatOp protoNode =
+                ArithmeticFloatOp.newBuilder()
+                        .setOperationType(ArithmeticOpType.ARITHMETIC_OP_TYPE_ADD)
+                        .build();
+
+        ArithmeticFloatNode node = new ArithmeticFloatNode(protoNode,
+                new AddToListCallback<>(results));
+
+        float lhsValue = 6.6f;
+        FixedFloat lhsProtoNode = FixedFloat.newBuilder().setValue(lhsValue).build();
+        FixedFloatNode lhsNode = new FixedFloatNode(lhsProtoNode, node.getLhsIncomingCallback());
+        lhsNode.init();
+
+        float oldRhsValue = 6.5f;
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setFloatVal(FixedFloat.newBuilder().setValue(oldRhsValue))
+                                        .build()));
+        StateFloatSource rhsProtoNode = StateFloatSource.newBuilder().setSourceKey("foo").build();
+        StateFloatSourceNode rhsNode =
+                new StateFloatSourceNode(oss, rhsProtoNode, node.getRhsIncomingCallback());
+
+        rhsNode.preInit();
+        rhsNode.init();
+
+        assertThat(results).containsExactly(lhsValue + oldRhsValue);
+
+        float newRhsValue = 7.8f;
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setFloatVal(FixedFloat.newBuilder().setValue(newRhsValue))
+                                .build()));
+        assertThat(results).containsExactly(lhsValue + oldRhsValue,
+                lhsValue + newRhsValue).inOrder();
+    }
+
+    @Test
+    public void int32ToFloatTest() {
+        List<Float> results = new ArrayList<>();
+        Int32ToFloatNode node = new Int32ToFloatNode(new AddToListCallback<>(results));
+
+        int oldIntValue = 65;
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setInt32Val(FixedInt32.newBuilder().setValue(oldIntValue))
+                                        .build()));
+
+        StateInt32Source protoNode = StateInt32Source.newBuilder().setSourceKey("foo").build();
+        StateInt32SourceNode intNode =
+                new StateInt32SourceNode(oss, protoNode, node.getIncomingCallback());
+
+        intNode.preInit();
+        intNode.init();
+
+        assertThat(results).containsExactly((float) oldIntValue);
+
+        int newIntValue = 12;
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setInt32Val(FixedInt32.newBuilder().setValue(newIntValue))
+                                .build()));
+
+        assertThat(results).containsExactly((float) oldIntValue, (float) newIntValue).inOrder();
+    }
+
+    @Test
+    public void animatableFixedFloat_animates() {
+        float startValue = 3.0f;
+        float endValue = 33.0f;
+        List<Float> results = new ArrayList<>();
+        QuotaManager quotaManager = new FixedQuotaManagerImpl(MAX_VALUE);
+        AnimatableFixedFloat protoNode =
+                AnimatableFixedFloat.newBuilder().setFromValue(startValue).setToValue(
+                        endValue).build();
+        AnimatableFixedFloatNode node =
+                new AnimatableFixedFloatNode(protoNode, new AddToListCallback<>(results),
+                        quotaManager);
+        node.setVisibility(true);
+
+        node.preInit();
+        node.init();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(results.size()).isGreaterThan(2);
+        assertThat(results.get(0)).isEqualTo(startValue);
+        assertThat(Iterables.getLast(results)).isEqualTo(endValue);
+    }
+
+    @Test
+    public void animatableFixedFloat_whenInvisible_skipToEnd() {
+        float startValue = 3.0f;
+        float endValue = 33.0f;
+        List<Float> results = new ArrayList<>();
+        QuotaManager quotaManager = new FixedQuotaManagerImpl(MAX_VALUE);
+        AnimatableFixedFloat protoNode =
+                AnimatableFixedFloat.newBuilder().setFromValue(startValue).setToValue(
+                        endValue).build();
+        AnimatableFixedFloatNode node =
+                new AnimatableFixedFloatNode(protoNode, new AddToListCallback<>(results),
+                        quotaManager);
+        node.setVisibility(false);
+
+        node.preInit();
+        node.init();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(results).hasSize(1);
+        assertThat(results).containsExactly(endValue);
+    }
+
+    @Test
+    public void animatableFixedFloat_whenNoQuota_skip() {
+        float startValue = 3.0f;
+        float endValue = 33.0f;
+        List<Float> results = new ArrayList<>();
+        QuotaManager quotaManager = new FixedQuotaManagerImpl(0);
+        AnimatableFixedFloat protoNode =
+                AnimatableFixedFloat.newBuilder().setFromValue(startValue).setToValue(
+                        endValue).build();
+        AnimatableFixedFloatNode node =
+                new AnimatableFixedFloatNode(protoNode, new AddToListCallback<>(results),
+                        quotaManager);
+        node.setVisibility(true);
+
+        node.preInit();
+        node.init();
+        shadowOf(Looper.getMainLooper()).idle();
+
+        assertThat(results).hasSize(1);
+        assertThat(results).containsExactly(endValue);
+    }
+
+    @Test
+    public void dynamicAnimatedFloat_onlyAnimateWhenVisible() {
+        float value1 = 3.0f;
+        float value2 = 11.0f;
+        float value3 = 17.0f;
+        List<Float> results = new ArrayList<>();
+        QuotaManager quotaManager = new FixedQuotaManagerImpl(MAX_VALUE);
+        ObservableStateStore oss =
+                new ObservableStateStore(
+                        ImmutableMap.of(
+                                "foo",
+                                StateEntryValue.newBuilder()
+                                        .setFloatVal(
+                                                FixedFloat.newBuilder().setValue(value1).build())
+                                        .build()));
+        DynamicAnimatedFloatNode floatNode =
+                new DynamicAnimatedFloatNode(
+                        new AddToListCallback<>(results), AnimationSpec.getDefaultInstance(),
+                        quotaManager);
+        floatNode.setVisibility(false);
+        StateFloatSourceNode stateNode =
+                new StateFloatSourceNode(
+                        oss,
+                        StateFloatSource.newBuilder().setSourceKey("foo").build(),
+                        floatNode.getInputCallback());
+
+        stateNode.preInit();
+        stateNode.init();
+
+        results.clear();
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setFloatVal(FixedFloat.newBuilder().setValue(value2))
+                                .build()));
+        shadowOf(Looper.getMainLooper()).idle();
+
+        // Only contains last value.
+        assertThat(results).hasSize(1);
+        assertThat(results).containsExactly(value2);
+
+        floatNode.setVisibility(true);
+        results.clear();
+        oss.setStateEntryValuesProto(
+                ImmutableMap.of(
+                        "foo",
+                        StateEntryValue.newBuilder()
+                                .setFloatVal(FixedFloat.newBuilder().setValue(value3))
+                                .build()));
+        shadowOf(Looper.getMainLooper()).idle();
+
+        // Contains intermediate values besides the initial and last.
+        assertThat(results.size()).isGreaterThan(2);
+        assertThat(results.get(0)).isEqualTo(value2);
+        assertThat(Iterables.getLast(results)).isEqualTo(value3);
+        assertThat(results).isInOrder();
+    }
+}
diff --git a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/InstantNodesTest.java b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/InstantNodesTest.java
index 14da1b7..5c4d32d 100644
--- a/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/InstantNodesTest.java
+++ b/wear/protolayout/protolayout-expression-pipeline/src/test/java/androidx/wear/protolayout/expression/pipeline/InstantNodesTest.java
@@ -47,6 +47,7 @@
         FixedInstant protoNode = FixedInstant.newBuilder().setEpochSeconds(1234567L).build();
         FixedInstantNode node = new FixedInstantNode(protoNode, new AddToListCallback<>(results));
 
+        node.preInit();
         node.init();
 
         assertThat(results).containsExactly(Instant.ofEpochSecond(1234567L));
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 4f610b2..46819e5 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
@@ -57,6 +57,7 @@
         FixedInt32 protoNode = FixedInt32.newBuilder().setValue(56).build();
         FixedInt32Node node = new FixedInt32Node(protoNode, new AddToListCallback<>(results));
 
+        node.preInit();
         node.init();
 
         assertThat(results).containsExactly(56);
@@ -219,6 +220,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(true);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -243,6 +245,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(false);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
@@ -266,6 +269,7 @@
                         protoNode, new AddToListCallback<>(results), quotaManager);
         node.setVisibility(true);
 
+        node.preInit();
         node.init();
         shadowOf(Looper.getMainLooper()).idle();
 
diff --git a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
index b67c387..fbf68ba 100644
--- a/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout-expression/api/public_plus_experimental_current.txt
@@ -248,6 +248,11 @@
   public static interface DynamicBuilders.DynamicType {
   }
 
+  @androidx.wear.protolayout.expression.ProtoLayoutExperimental public class PlatformHealthSources {
+    method @RequiresApi(android.os.Build.VERSION_CODES.Q) @RequiresPermission(android.Manifest.permission.ACTIVITY_RECOGNITION) @androidx.wear.protolayout.expression.ProtoLayoutExperimental public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 dailySteps();
+    method @RequiresPermission(android.Manifest.permission.BODY_SENSORS) @androidx.wear.protolayout.expression.ProtoLayoutExperimental public static androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32 heartRateBpm();
+  }
+
   @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) @java.lang.annotation.Target({java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.TYPE, java.lang.annotation.ElementType.FIELD}) public @interface ProtoLayoutExperimental {
   }
 
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
index 3d52769..ae5fa6c 100644
--- a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/DynamicBuilders.java
@@ -47,6 +47,47 @@
   private DynamicBuilders() {}
 
   /**
+   * The type of data to provide to a {@link PlatformInt32Source}.
+   *
+   * @since 1.2
+   */
+  @RestrictTo(RestrictTo.Scope.LIBRARY)
+  @IntDef({
+    PLATFORM_INT32_SOURCE_TYPE_UNDEFINED,
+    PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE,
+    PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT
+  })
+  @Retention(RetentionPolicy.SOURCE)
+  @ProtoLayoutExperimental
+  @interface PlatformInt32SourceType {}
+
+  /**
+   * Undefined source.
+   *
+   * @since 1.2
+   */
+  @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_UNDEFINED = 0;
+
+  /**
+   * The user's current heart rate. Note that to use this data source, your app must already have
+   * the "BODY_SENSORS" permission granted to it. If this permission is not present, this source
+   * type will never yield any data.
+   *
+   * @since 1.2
+   */
+  @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE = 1;
+
+  /**
+   * The user's current daily steps. This is the number of steps they have taken since midnight, and
+   * will reset to zero at midnight. Note that to use this data source, your app must already have
+   * the "ACTIVITY_RECOGNITION" permission granted to it. If this permission is not present, this
+   * source type will never yield any data.
+   *
+   * @since 1.2
+   */
+  @ProtoLayoutExperimental static final int PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT = 2;
+
+  /**
    * The type of arithmetic operation used in {@link ArithmeticInt32Op} and {@link
    * ArithmeticFloatOp}.
    *
@@ -340,6 +381,109 @@
   static final int DURATION_PART_TYPE_SECONDS = 8;
 
   /**
+   * A dynamic Int32 which sources its data from some platform data source, e.g. from sensors, or
+   * the current time.
+   *
+   * @since 1.2
+   */
+  @ProtoLayoutExperimental
+  static final class PlatformInt32Source implements DynamicInt32 {
+    private final DynamicProto.PlatformInt32Source mImpl;
+    @Nullable private final Fingerprint mFingerprint;
+
+    PlatformInt32Source(DynamicProto.PlatformInt32Source impl, @Nullable Fingerprint fingerprint) {
+      this.mImpl = impl;
+      this.mFingerprint = fingerprint;
+    }
+
+    /**
+     * Gets the source to load data from.
+     *
+     * @since 1.2
+     */
+    @PlatformInt32SourceType
+    public int getSourceType() {
+      return mImpl.getSourceType().getNumber();
+    }
+
+    /** @hide */
+    @Override
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @Nullable
+    public Fingerprint getFingerprint() {
+      return mFingerprint;
+    }
+    /**
+     * Creates a new wrapper instance from the proto.
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static PlatformInt32Source fromProto(
+        @NonNull DynamicProto.PlatformInt32Source proto, @Nullable Fingerprint fingerprint) {
+      return new PlatformInt32Source(proto, fingerprint);
+    }
+
+    @NonNull
+    static PlatformInt32Source fromProto(@NonNull DynamicProto.PlatformInt32Source proto) {
+      return fromProto(proto, null);
+    }
+
+    /**
+     * Returns the internal proto instance.
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    DynamicProto.PlatformInt32Source toProto() {
+      return mImpl;
+    }
+
+    /** @hide */
+    @Override
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public DynamicProto.DynamicInt32 toDynamicInt32Proto() {
+      return DynamicProto.DynamicInt32.newBuilder().setPlatformSource(mImpl).build();
+    }
+
+    @Override
+    @NonNull
+    public String toString() {
+      return "PlatformInt32Source{" + "sourceType=" + getSourceType() + "}";
+    }
+
+    /** Builder for {@link PlatformInt32Source}. */
+    public static final class Builder implements DynamicInt32.Builder {
+      private final DynamicProto.PlatformInt32Source.Builder mImpl =
+          DynamicProto.PlatformInt32Source.newBuilder();
+      private final Fingerprint mFingerprint = new Fingerprint(1355180718);
+
+      public Builder() {}
+
+      /**
+       * Sets the source to load data from.
+       *
+       * @since 1.2
+       */
+      @NonNull
+      public Builder setSourceType(@PlatformInt32SourceType int sourceType) {
+        mImpl.setSourceType(DynamicProto.PlatformInt32SourceType.forNumber(sourceType));
+        mFingerprint.recordPropertyUpdate(1, sourceType);
+        return this;
+      }
+
+      @Override
+      @NonNull
+      public PlatformInt32Source build() {
+        return new PlatformInt32Source(mImpl.build(), mFingerprint);
+      }
+    }
+  }
+
+  /**
    * An arithmetic operation, operating on two Int32 instances. This implements simple binary
    * operations of the form "result = LHS <op> RHS", where the available operation types are
    * described in {@code ArithmeticOpType}.
@@ -2178,6 +2322,9 @@
     if (proto.hasFixed()) {
       return FixedInt32.fromProto(proto.getFixed());
     }
+    if(proto.hasPlatformSource()) {
+        return PlatformInt32Source.fromProto(proto.getPlatformSource());
+    }
     if (proto.hasArithmeticOperation()) {
       return ArithmeticInt32Op.fromProto(proto.getArithmeticOperation());
     }
diff --git a/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
new file mode 100644
index 0000000..0974b70
--- /dev/null
+++ b/wear/protolayout/protolayout-expression/src/main/java/androidx/wear/protolayout/expression/PlatformHealthSources.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout.expression;
+
+import static androidx.wear.protolayout.expression.DynamicBuilders.PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE;
+import static androidx.wear.protolayout.expression.DynamicBuilders.PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT;
+
+import android.Manifest;
+import android.os.Build.VERSION_CODES;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RequiresPermission;
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicInt32;
+import androidx.wear.protolayout.expression.DynamicBuilders.PlatformInt32Source;
+
+/** Utility class provides utils to access health data. */
+@ProtoLayoutExperimental
+public class PlatformHealthSources {
+    private PlatformHealthSources() {
+    }
+
+    /** Creates a {@link DynamicInt32} which receives the current heat rate from the sensor. */
+    @RequiresPermission(Manifest.permission.BODY_SENSORS)
+    @ProtoLayoutExperimental
+    @NonNull
+    public static DynamicInt32 heartRateBpm() {
+        return new PlatformInt32Source.Builder()
+                .setSourceType(PLATFORM_INT32_SOURCE_TYPE_CURRENT_HEART_RATE)
+                .build();
+    }
+
+    /**
+     * Creates a {@link DynamicInt32} which receives the current daily steps from the sensor.
+     * This is the total step count over a day, where the previous day ends and a new day begins at
+     * 12:00 AM local time.
+     */
+    @RequiresApi(VERSION_CODES.Q)
+    @RequiresPermission(Manifest.permission.ACTIVITY_RECOGNITION)
+    @ProtoLayoutExperimental
+    @NonNull
+    public static DynamicInt32 dailySteps() {
+        return new PlatformInt32Source.Builder()
+                .setSourceType(PLATFORM_INT32_SOURCE_TYPE_DAILY_STEP_COUNT)
+                .build();
+    }
+}
diff --git a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/DefaultInlineImageResourceResolver.java b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/DefaultInlineImageResourceResolver.java
index 118a260..dfd21df 100644
--- a/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/DefaultInlineImageResourceResolver.java
+++ b/wear/protolayout/protolayout-renderer/src/main/java/androidx/wear/protolayout/renderer/inflater/DefaultInlineImageResourceResolver.java
@@ -22,6 +22,7 @@
 import android.graphics.BitmapFactory;
 import android.graphics.drawable.BitmapDrawable;
 import android.graphics.drawable.Drawable;
+import android.util.Log;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -35,6 +36,7 @@
 /** Resource resolver for inline resources. */
 public class DefaultInlineImageResourceResolver implements InlineImageResourceResolver {
     private static final int RGB565_BYTES_PER_PX = 2;
+    private static final String TAG = "InlineImageResolver";
 
     @NonNull private final Context mAppContext;
 
@@ -102,12 +104,15 @@
         return bitmap;
     }
 
-    @NonNull
+    @Nullable
     private Bitmap loadStructuredBitmap(@NonNull InlineImageResource inlineImage) {
         Bitmap bitmap =
                 BitmapFactory.decodeByteArray(
                         inlineImage.getData().toByteArray(), 0, inlineImage.getData().size());
-
+        if (bitmap == null) {
+            Log.e(TAG, "Unable to load structured bitmap.");
+            return null;
+        }
         return Bitmap.createScaledBitmap(
                 bitmap, inlineImage.getWidthPx(), inlineImage.getHeightPx(), /* filter= */ true);
     }
diff --git a/wear/protolayout/protolayout/api/current.txt b/wear/protolayout/protolayout/api/current.txt
index 497290d..c853bf5 100644
--- a/wear/protolayout/protolayout/api/current.txt
+++ b/wear/protolayout/protolayout/api/current.txt
@@ -134,12 +134,15 @@
 
   public static final class ColorBuilders.ColorProp {
     method @ColorInt public int getArgb();
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor? getDynamicValue();
   }
 
   public static final class ColorBuilders.ColorProp.Builder {
-    ctor public ColorBuilders.ColorProp.Builder();
+    ctor @Deprecated public ColorBuilders.ColorProp.Builder();
+    ctor public ColorBuilders.ColorProp.Builder(@ColorInt int);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp build();
     method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setArgb(@ColorInt int);
+    method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor);
   }
 
   public final class DeviceParametersBuilders {
@@ -177,33 +180,37 @@
     method public static androidx.wear.protolayout.DimensionBuilders.DpProp dp(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(int);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(float);
+    method public static androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp expand();
     method public static androidx.wear.protolayout.DimensionBuilders.SpProp sp(@Dimension(unit=androidx.annotation.Dimension.SP) float);
+    method public static androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp wrap();
   }
 
   public static interface DimensionBuilders.ContainerDimension {
   }
 
-  public static interface DimensionBuilders.ContainerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension build();
-  }
-
   public static final class DimensionBuilders.DegreesProp {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method public float getValue();
   }
 
   public static final class DimensionBuilders.DegreesProp.Builder {
-    ctor public DimensionBuilders.DegreesProp.Builder();
+    ctor @Deprecated public DimensionBuilders.DegreesProp.Builder();
+    ctor public DimensionBuilders.DegreesProp.Builder(float);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setValue(float);
   }
 
   public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method @Dimension(unit=androidx.annotation.Dimension.DP) public float getValue();
   }
 
-  public static final class DimensionBuilders.DpProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension.Builder androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder androidx.wear.protolayout.DimensionBuilders.SpacerDimension.Builder {
-    ctor public DimensionBuilders.DpProp.Builder();
+  public static final class DimensionBuilders.DpProp.Builder {
+    ctor @Deprecated public DimensionBuilders.DpProp.Builder();
+    ctor public DimensionBuilders.DpProp.Builder(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setValue(@Dimension(unit=androidx.annotation.Dimension.DP) float);
   }
 
@@ -217,11 +224,17 @@
     method public androidx.wear.protolayout.DimensionBuilders.EmProp.Builder setValue(float);
   }
 
-  public static interface DimensionBuilders.ImageDimension {
+  public static final class DimensionBuilders.ExpandedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension {
+    method public androidx.wear.protolayout.TypeBuilders.FloatProp? getLayoutWeight();
   }
 
-  public static interface DimensionBuilders.ImageDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ImageDimension build();
+  public static final class DimensionBuilders.ExpandedDimensionProp.Builder {
+    ctor public DimensionBuilders.ExpandedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp.Builder setLayoutWeight(androidx.wear.protolayout.TypeBuilders.FloatProp);
+  }
+
+  public static interface DimensionBuilders.ImageDimension {
   }
 
   public static final class DimensionBuilders.ProportionalDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ImageDimension {
@@ -229,7 +242,7 @@
     method @IntRange(from=0) public int getAspectRatioWidth();
   }
 
-  public static final class DimensionBuilders.ProportionalDimensionProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder {
+  public static final class DimensionBuilders.ProportionalDimensionProp.Builder {
     ctor public DimensionBuilders.ProportionalDimensionProp.Builder();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp build();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp.Builder setAspectRatioHeight(@IntRange(from=0) int);
@@ -249,8 +262,14 @@
   public static interface DimensionBuilders.SpacerDimension {
   }
 
-  public static interface DimensionBuilders.SpacerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.SpacerDimension build();
+  public static final class DimensionBuilders.WrappedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension {
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp? getMinimumSize();
+  }
+
+  public static final class DimensionBuilders.WrappedDimensionProp.Builder {
+    ctor public DimensionBuilders.WrappedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp.Builder setMinimumSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
   public final class LayoutElementBuilders {
diff --git a/wear/protolayout/protolayout/api/public_plus_experimental_current.txt b/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
index a46315a..f882d52 100644
--- a/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
+++ b/wear/protolayout/protolayout/api/public_plus_experimental_current.txt
@@ -134,12 +134,15 @@
 
   public static final class ColorBuilders.ColorProp {
     method @ColorInt public int getArgb();
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor? getDynamicValue();
   }
 
   public static final class ColorBuilders.ColorProp.Builder {
-    ctor public ColorBuilders.ColorProp.Builder();
+    ctor @Deprecated public ColorBuilders.ColorProp.Builder();
+    ctor public ColorBuilders.ColorProp.Builder(@ColorInt int);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp build();
     method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setArgb(@ColorInt int);
+    method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor);
   }
 
   public final class DeviceParametersBuilders {
@@ -189,33 +192,37 @@
     method public static androidx.wear.protolayout.DimensionBuilders.DpProp dp(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(int);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(float);
+    method public static androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp expand();
     method public static androidx.wear.protolayout.DimensionBuilders.SpProp sp(@Dimension(unit=androidx.annotation.Dimension.SP) float);
+    method public static androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp wrap();
   }
 
   public static interface DimensionBuilders.ContainerDimension {
   }
 
-  public static interface DimensionBuilders.ContainerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension build();
-  }
-
   public static final class DimensionBuilders.DegreesProp {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method public float getValue();
   }
 
   public static final class DimensionBuilders.DegreesProp.Builder {
-    ctor public DimensionBuilders.DegreesProp.Builder();
+    ctor @Deprecated public DimensionBuilders.DegreesProp.Builder();
+    ctor public DimensionBuilders.DegreesProp.Builder(float);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setValue(float);
   }
 
   public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method @Dimension(unit=androidx.annotation.Dimension.DP) public float getValue();
   }
 
-  public static final class DimensionBuilders.DpProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension.Builder androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder androidx.wear.protolayout.DimensionBuilders.SpacerDimension.Builder {
-    ctor public DimensionBuilders.DpProp.Builder();
+  public static final class DimensionBuilders.DpProp.Builder {
+    ctor @Deprecated public DimensionBuilders.DpProp.Builder();
+    ctor public DimensionBuilders.DpProp.Builder(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setValue(@Dimension(unit=androidx.annotation.Dimension.DP) float);
   }
 
@@ -229,11 +236,17 @@
     method public androidx.wear.protolayout.DimensionBuilders.EmProp.Builder setValue(float);
   }
 
-  public static interface DimensionBuilders.ImageDimension {
+  public static final class DimensionBuilders.ExpandedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension {
+    method public androidx.wear.protolayout.TypeBuilders.FloatProp? getLayoutWeight();
   }
 
-  public static interface DimensionBuilders.ImageDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ImageDimension build();
+  public static final class DimensionBuilders.ExpandedDimensionProp.Builder {
+    ctor public DimensionBuilders.ExpandedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp.Builder setLayoutWeight(androidx.wear.protolayout.TypeBuilders.FloatProp);
+  }
+
+  public static interface DimensionBuilders.ImageDimension {
   }
 
   public static final class DimensionBuilders.ProportionalDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ImageDimension {
@@ -241,7 +254,7 @@
     method @IntRange(from=0) public int getAspectRatioWidth();
   }
 
-  public static final class DimensionBuilders.ProportionalDimensionProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder {
+  public static final class DimensionBuilders.ProportionalDimensionProp.Builder {
     ctor public DimensionBuilders.ProportionalDimensionProp.Builder();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp build();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp.Builder setAspectRatioHeight(@IntRange(from=0) int);
@@ -261,8 +274,14 @@
   public static interface DimensionBuilders.SpacerDimension {
   }
 
-  public static interface DimensionBuilders.SpacerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.SpacerDimension build();
+  public static final class DimensionBuilders.WrappedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension {
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp? getMinimumSize();
+  }
+
+  public static final class DimensionBuilders.WrappedDimensionProp.Builder {
+    ctor public DimensionBuilders.WrappedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp.Builder setMinimumSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
   public final class LayoutElementBuilders {
diff --git a/wear/protolayout/protolayout/api/restricted_current.txt b/wear/protolayout/protolayout/api/restricted_current.txt
index 497290d..c853bf5 100644
--- a/wear/protolayout/protolayout/api/restricted_current.txt
+++ b/wear/protolayout/protolayout/api/restricted_current.txt
@@ -134,12 +134,15 @@
 
   public static final class ColorBuilders.ColorProp {
     method @ColorInt public int getArgb();
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor? getDynamicValue();
   }
 
   public static final class ColorBuilders.ColorProp.Builder {
-    ctor public ColorBuilders.ColorProp.Builder();
+    ctor @Deprecated public ColorBuilders.ColorProp.Builder();
+    ctor public ColorBuilders.ColorProp.Builder(@ColorInt int);
     method public androidx.wear.protolayout.ColorBuilders.ColorProp build();
     method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setArgb(@ColorInt int);
+    method public androidx.wear.protolayout.ColorBuilders.ColorProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor);
   }
 
   public final class DeviceParametersBuilders {
@@ -177,33 +180,37 @@
     method public static androidx.wear.protolayout.DimensionBuilders.DpProp dp(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(int);
     method public static androidx.wear.protolayout.DimensionBuilders.EmProp em(float);
+    method public static androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp expand();
     method public static androidx.wear.protolayout.DimensionBuilders.SpProp sp(@Dimension(unit=androidx.annotation.Dimension.SP) float);
+    method public static androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp wrap();
   }
 
   public static interface DimensionBuilders.ContainerDimension {
   }
 
-  public static interface DimensionBuilders.ContainerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ContainerDimension build();
-  }
-
   public static final class DimensionBuilders.DegreesProp {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method public float getValue();
   }
 
   public static final class DimensionBuilders.DegreesProp.Builder {
-    ctor public DimensionBuilders.DegreesProp.Builder();
+    ctor @Deprecated public DimensionBuilders.DegreesProp.Builder();
+    ctor public DimensionBuilders.DegreesProp.Builder(float);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DegreesProp.Builder setValue(float);
   }
 
   public static final class DimensionBuilders.DpProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension androidx.wear.protolayout.DimensionBuilders.SpacerDimension {
+    method public androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat? getDynamicValue();
     method @Dimension(unit=androidx.annotation.Dimension.DP) public float getValue();
   }
 
-  public static final class DimensionBuilders.DpProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension.Builder androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder androidx.wear.protolayout.DimensionBuilders.SpacerDimension.Builder {
-    ctor public DimensionBuilders.DpProp.Builder();
+  public static final class DimensionBuilders.DpProp.Builder {
+    ctor @Deprecated public DimensionBuilders.DpProp.Builder();
+    ctor public DimensionBuilders.DpProp.Builder(@Dimension(unit=androidx.annotation.Dimension.DP) float);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setDynamicValue(androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat);
     method public androidx.wear.protolayout.DimensionBuilders.DpProp.Builder setValue(@Dimension(unit=androidx.annotation.Dimension.DP) float);
   }
 
@@ -217,11 +224,17 @@
     method public androidx.wear.protolayout.DimensionBuilders.EmProp.Builder setValue(float);
   }
 
-  public static interface DimensionBuilders.ImageDimension {
+  public static final class DimensionBuilders.ExpandedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension androidx.wear.protolayout.DimensionBuilders.ImageDimension {
+    method public androidx.wear.protolayout.TypeBuilders.FloatProp? getLayoutWeight();
   }
 
-  public static interface DimensionBuilders.ImageDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.ImageDimension build();
+  public static final class DimensionBuilders.ExpandedDimensionProp.Builder {
+    ctor public DimensionBuilders.ExpandedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.ExpandedDimensionProp.Builder setLayoutWeight(androidx.wear.protolayout.TypeBuilders.FloatProp);
+  }
+
+  public static interface DimensionBuilders.ImageDimension {
   }
 
   public static final class DimensionBuilders.ProportionalDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ImageDimension {
@@ -229,7 +242,7 @@
     method @IntRange(from=0) public int getAspectRatioWidth();
   }
 
-  public static final class DimensionBuilders.ProportionalDimensionProp.Builder implements androidx.wear.protolayout.DimensionBuilders.ImageDimension.Builder {
+  public static final class DimensionBuilders.ProportionalDimensionProp.Builder {
     ctor public DimensionBuilders.ProportionalDimensionProp.Builder();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp build();
     method public androidx.wear.protolayout.DimensionBuilders.ProportionalDimensionProp.Builder setAspectRatioHeight(@IntRange(from=0) int);
@@ -249,8 +262,14 @@
   public static interface DimensionBuilders.SpacerDimension {
   }
 
-  public static interface DimensionBuilders.SpacerDimension.Builder {
-    method public androidx.wear.protolayout.DimensionBuilders.SpacerDimension build();
+  public static final class DimensionBuilders.WrappedDimensionProp implements androidx.wear.protolayout.DimensionBuilders.ContainerDimension {
+    method public androidx.wear.protolayout.DimensionBuilders.DpProp? getMinimumSize();
+  }
+
+  public static final class DimensionBuilders.WrappedDimensionProp.Builder {
+    ctor public DimensionBuilders.WrappedDimensionProp.Builder();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp build();
+    method public androidx.wear.protolayout.DimensionBuilders.WrappedDimensionProp.Builder setMinimumSize(androidx.wear.protolayout.DimensionBuilders.DpProp);
   }
 
   public final class LayoutElementBuilders {
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 2c102ec..e70f637 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
@@ -16,92 +16,140 @@
 
 package androidx.wear.protolayout;
 
+import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;
+
 import androidx.annotation.ColorInt;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor;
 import androidx.wear.protolayout.expression.Fingerprint;
 import androidx.wear.protolayout.proto.ColorProto;
 
 /** Builders for color utilities for layout elements. */
 public final class ColorBuilders {
-  private ColorBuilders() {}
+    private ColorBuilders() {}
 
-  /** Shortcut for building a {@link ColorProp} using an ARGB value. */
-  @NonNull
-  public static ColorProp argb(@ColorInt int colorArgb) {
-    return new ColorProp.Builder().setArgb(colorArgb).build();
-  }
-
-  /**
-   * A property defining a color.
-   *
-   * @since 1.0
-   */
-  public static final class ColorProp {
-    private final ColorProto.ColorProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ColorProp(ColorProto.ColorProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+    /** Shortcut for building a {@link ColorProp} using an ARGB value. */
+    @NonNull
+    public static ColorProp argb(@ColorInt int colorArgb) {
+        return new ColorProp.Builder().setArgb(colorArgb).build();
     }
 
     /**
-     * Gets the color value, in ARGB format.
+     * A property defining a color.
      *
      * @since 1.0
      */
-    @ColorInt
-    public int getArgb() {
-      return mImpl.getArgb();
+    public static final class ColorProp {
+        private final ColorProto.ColorProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ColorProp(ColorProto.ColorProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the color value, in ARGB format.
+         *
+         * @since 1.0
+         */
+        @ColorInt
+        public int getArgb() {
+            return mImpl.getArgb();
+        }
+
+        /**
+         * Gets the dynamic value.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public DynamicColor getDynamicValue() {
+            if (mImpl.hasDynamicValue()) {
+                return DynamicBuilders.dynamicColorFromProto(mImpl.getDynamicValue());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ColorProp fromProto(@NonNull ColorProto.ColorProp proto) {
+            return new ColorProp(proto, null);
+        }
+
+        @NonNull
+        ColorProto.ColorProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "ColorProp{" + "argb=" + getArgb() + ", dynamicValue=" + getDynamicValue() + "}";
+        }
+
+        /** Builder for {@link ColorProp} */
+        public static final class Builder {
+            private final ColorProto.ColorProp.Builder mImpl = ColorProto.ColorProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1955659823);
+
+            /**
+             * @deprecated Use {@link #Builder(int)} instead.
+             */
+            @Deprecated
+            public Builder() {}
+
+            public Builder(@ColorInt int argb) {
+                setArgb(argb);
+            }
+
+            /**
+             * Sets the color value, in ARGB format.
+             * If a dynamic value is also set and the renderer supports dynamic values for the
+             * corresponding field, this static value will be ignored.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setArgb(@ColorInt int argb) {
+                mImpl.setArgb(argb);
+                mFingerprint.recordPropertyUpdate(1, argb);
+                return this;
+            }
+
+            /**
+             * Sets the dynamic value. Note that when setting this value, the static value is still
+             * required to be set to support older renderers that only read the static value.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setDynamicValue(@NonNull DynamicColor dynamicValue) {
+                mImpl.setDynamicValue(dynamicValue.toDynamicColorProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(dynamicValue.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ColorProp build() {
+                if (mImpl.hasDynamicValue() && !mImpl.hasArgb()) {
+                    throw new IllegalStateException("Static value is missing.");
+                }
+                return new ColorProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ColorProp fromProto(@NonNull ColorProto.ColorProp proto) {
-      return new ColorProp(proto, null);
-    }
-
-    @NonNull
-    ColorProto.ColorProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link ColorProp} */
-    public static final class Builder {
-      private final ColorProto.ColorProp.Builder mImpl = ColorProto.ColorProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1955659823);
-
-      public Builder() {}
-
-      /**
-       * Sets the color value, in ARGB format.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setArgb(@ColorInt int argb) {
-        mImpl.setArgb(argb);
-        mFingerprint.recordPropertyUpdate(1, argb);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ColorProp build() {
-        return new ColorProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
 }
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
index 8b43e9b..61ac7e0 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/DimensionBuilders.java
@@ -18,762 +18,1043 @@
 
 import static androidx.annotation.Dimension.DP;
 import static androidx.annotation.Dimension.SP;
+import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;
 
-import android.annotation.SuppressLint;
 import androidx.annotation.Dimension;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.wear.protolayout.TypeBuilders.FloatProp;
+import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat;
 import androidx.wear.protolayout.expression.Fingerprint;
 import androidx.wear.protolayout.proto.DimensionProto;
-import androidx.wear.protolayout.proto.TypesProto;
 
 /** Builders for dimensions for layout elements. */
 public final class DimensionBuilders {
-  private DimensionBuilders() {}
+    private DimensionBuilders() {}
 
-  /** Shortcut for building a {@link DpProp} using a measurement in DP. */
-  @NonNull
-  public static DpProp dp(@Dimension(unit = DP) float valueDp) {
-    return new DpProp.Builder().setValue(valueDp).build();
-  }
+    private static final ExpandedDimensionProp EXPAND = new ExpandedDimensionProp.Builder().build();
+    private static final WrappedDimensionProp WRAP = new WrappedDimensionProp.Builder().build();
 
-  /** Shortcut for building a {@link SpProp} using a measurement in SP. */
-  @NonNull
-  public static SpProp sp(@Dimension(unit = SP) float valueSp) {
-    return new SpProp.Builder().setValue(valueSp).build();
-  }
+    /** Shortcut for building a {@link DpProp} using a measurement in DP. */
+    @NonNull
+    public static DpProp dp(@Dimension(unit = DP) float valueDp) {
+        return new DpProp.Builder(valueDp).build();
+    }
 
-  /** Shortcut for building a {@link EmProp} using a measurement in EM. */
-  @NonNull
-  public static EmProp em(int valueEm) {
-    return new EmProp.Builder().setValue(valueEm).build();
-  }
-
-  /** Shortcut for building a {@link EmProp} using a measurement in EM. */
-  @NonNull
-  public static EmProp em(float valueEm) {
-    return new EmProp.Builder().setValue(valueEm).build();
-  }
-
-  /** Shortcut for building an {@link DegreesProp} using a measurement in degrees. */
-  @NonNull
-  public static DegreesProp degrees(float valueDegrees) {
-    return new DegreesProp.Builder().setValue(valueDegrees).build();
-  }
-
-  /** A type for linear dimensions, measured in dp.
-   *
-   * @since 1.0
-   */
-  public static final class DpProp implements ContainerDimension, ImageDimension, SpacerDimension {
-    private final DimensionProto.DpProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    DpProp(DimensionProto.DpProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+    /** Shortcut for building a {@link SpProp} using a measurement in SP. */
+    @NonNull
+    public static SpProp sp(@Dimension(unit = SP) float valueSp) {
+        return new SpProp.Builder().setValue(valueSp).build();
     }
 
     /**
-     * Gets the value, in dp.
+     * Shortcut for building a {@link EmProp} using a measurement in EM.
      *
      * @since 1.0
      */
-    @Dimension(unit = DP)
-    public float getValue() {
-      return mImpl.getValue();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
     @NonNull
-    static DpProp fromProto(@NonNull DimensionProto.DpProp proto) {
-      return new DpProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.DpProp toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.ContainerDimension toContainerDimensionProto() {
-      return DimensionProto.ContainerDimension.newBuilder().setLinearDimension(mImpl).build();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.ImageDimension toImageDimensionProto() {
-      return DimensionProto.ImageDimension.newBuilder().setLinearDimension(mImpl).build();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.SpacerDimension toSpacerDimensionProto() {
-      return DimensionProto.SpacerDimension.newBuilder().setLinearDimension(mImpl).build();
-    }
-
-    /** Builder for {@link DpProp}. */
-    public static final class Builder
-        implements ContainerDimension.Builder, ImageDimension.Builder, SpacerDimension.Builder {
-      private final DimensionProto.DpProp.Builder mImpl = DimensionProto.DpProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(756413087);
-
-      public Builder() {}
-
-      /**
-       * Sets the value, in dp.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setValue(@Dimension(unit = DP) float value) {
-        mImpl.setValue(value);
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(value));
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public DpProp build() {
-        return new DpProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for font sizes, measured in sp.
-   *
-   * @since 1.0
-   */
-  public static final class SpProp {
-    private final DimensionProto.SpProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SpProp(DimensionProto.SpProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+    public static EmProp em(int valueEm) {
+        return new EmProp.Builder().setValue(valueEm).build();
     }
 
     /**
-     * Gets the value, in sp.
+     * Shortcut for building a {@link EmProp} using a measurement in EM.
      *
      * @since 1.0
      */
-    @Dimension(unit = SP)
-    public float getValue() {
-      return mImpl.getValue();
+    @NonNull
+    public static EmProp em(float valueEm) {
+        return new EmProp.Builder().setValue(valueEm).build();
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static SpProp fromProto(@NonNull DimensionProto.SpProp proto) {
-      return new SpProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.SpProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link SpProp} */
-    public static final class Builder {
-      private final DimensionProto.SpProp.Builder mImpl = DimensionProto.SpProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(631793260);
-
-      public Builder() {}
-
-      /**
-       * Sets the value, in sp.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setValue(@Dimension(unit = SP) float value) {
-        mImpl.setValue(value);
-        mFingerprint.recordPropertyUpdate(2, Float.floatToIntBits(value));
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public SpProp build() {
-        return new SpProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for font spacing, measured in em.
-   *
-   * @since 1.0
-   */
-  public static final class EmProp {
-    private final DimensionProto.EmProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    EmProp(DimensionProto.EmProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the value, in em.
+     * Shortcut for building an {@link DegreesProp} using a measurement in degrees.
      *
      * @since 1.0
      */
-    public float getValue() {
-      return mImpl.getValue();
+    @NonNull
+    public static DegreesProp degrees(float valueDegrees) {
+        return new DegreesProp.Builder(valueDegrees).build();
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static EmProp fromProto(@NonNull DimensionProto.EmProp proto) {
-      return new EmProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.EmProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link EmProp} */
-    public static final class Builder {
-      private final DimensionProto.EmProp.Builder mImpl = DimensionProto.EmProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-659639046);
-
-      public Builder() {}
-
-      /**
-       * Sets the value, in em.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setValue(float value) {
-        mImpl.setValue(value);
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(value));
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public EmProp build() {
-        return new EmProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for angular dimensions, measured in degrees.
-   *
-   * @since 1.0
-   */
-  public static final class DegreesProp {
-    private final DimensionProto.DegreesProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    DegreesProp(DimensionProto.DegreesProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the value, in degrees.
+     * Shortcut for building an {@link ExpandedDimensionProp} that will expand to the size of its
+     * parent.
      *
      * @since 1.0
      */
-    public float getValue() {
-      return mImpl.getValue();
+    @NonNull
+    public static ExpandedDimensionProp expand() {
+        return EXPAND;
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static DegreesProp fromProto(@NonNull DimensionProto.DegreesProp proto) {
-      return new DegreesProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.DegreesProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link DegreesProp} */
-    public static final class Builder {
-      private final DimensionProto.DegreesProp.Builder mImpl =
-          DimensionProto.DegreesProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1927567665);
-
-      public Builder() {}
-
-      /**
-       * Sets the value, in degrees.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setValue(float value) {
-        mImpl.setValue(value);
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(value));
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public DegreesProp build() {
-        return new DegreesProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for a dimension that fills all the space it can (i.e. MATCH_PARENT in Android parlance).
-   *
-   * @since 1.0
-   */
-  @RestrictTo(Scope.LIBRARY_GROUP)
-  public static final class ExpandedDimensionProp implements ContainerDimension, ImageDimension {
-    private final DimensionProto.ExpandedDimensionProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ExpandedDimensionProp(
-        DimensionProto.ExpandedDimensionProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the layout weight (a dimensionless scalar value) for this element. This will only affect
-     * the width of children of a {@link androidx.wear.protolayout.LayoutElementBuilders.Row} or the
-     * height of children of a {@link androidx.wear.protolayout.LayoutElementBuilders.Column}. By
-     * default, all children have equal weight. Where applicable, the width or height of the element
-     * is proportional to the sum of the weights of its siblings.
-     *
-     * @since 1.2
-     */
-    public float getLayoutWeight() {
-      return mImpl.getLayoutWeight().getValue();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ExpandedDimensionProp fromProto(@NonNull DimensionProto.ExpandedDimensionProp proto) {
-      return new ExpandedDimensionProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.ExpandedDimensionProp toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.ContainerDimension toContainerDimensionProto() {
-      return DimensionProto.ContainerDimension.newBuilder().setExpandedDimension(mImpl).build();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.ImageDimension toImageDimensionProto() {
-      return DimensionProto.ImageDimension.newBuilder().setExpandedDimension(mImpl).build();
-    }
-
-    /** Builder for {@link ExpandedDimensionProp}. */
-    public static final class Builder
-        implements ContainerDimension.Builder, ImageDimension.Builder {
-      private final DimensionProto.ExpandedDimensionProp.Builder mImpl =
-          DimensionProto.ExpandedDimensionProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-997720604);
-
-      public Builder() {}
-
-      /**
-       * Sets the layout weight (a dimensionless scalar value) for this element. This will only
-       * affect the width of children of a {@link
-       * androidx.wear.protolayout.LayoutElementBuilders.Row} or the height of children of a {@link
-       * androidx.wear.protolayout.LayoutElementBuilders.Column}. By default, all children have
-       * equal weight. Where applicable, the width or height of the element is proportional to the
-       * sum of the weights of its siblings.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setLayoutWeight(float layoutWeight) {
-        mImpl.setLayoutWeight(TypesProto.FloatProp.newBuilder().setValue(layoutWeight));
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(layoutWeight));
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public ExpandedDimensionProp build() {
-        return new ExpandedDimensionProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for a dimension that sizes itself to the size of its children (i.e. WRAP_CONTENT in
-   * Android parlance).
-   *
-   * @since 1.0
-   */
-  @RestrictTo(Scope.LIBRARY_GROUP)
-  public static final class WrappedDimensionProp implements ContainerDimension {
-    private final DimensionProto.WrappedDimensionProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    WrappedDimensionProp(
-        DimensionProto.WrappedDimensionProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the minimum size of this dimension. If not set, then there is no minimum size.
-     *
-     * @since 1.2
-     */
-    @Dimension(unit = DP)
-    public float getMinimumSizeDp() {
-      return mImpl.getMinimumSize().getValue();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static WrappedDimensionProp fromProto(@NonNull DimensionProto.WrappedDimensionProp proto) {
-      return new WrappedDimensionProp(proto, null);
-    }
-
-    @NonNull
-    DimensionProto.WrappedDimensionProp toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public DimensionProto.ContainerDimension toContainerDimensionProto() {
-      return DimensionProto.ContainerDimension.newBuilder().setWrappedDimension(mImpl).build();
-    }
-
-    /** Builder for {@link WrappedDimensionProp}. */
-    public static final class Builder implements ContainerDimension.Builder {
-      private final DimensionProto.WrappedDimensionProp.Builder mImpl =
-          DimensionProto.WrappedDimensionProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1118918114);
-
-      public Builder() {}
-
-      /**
-       * Sets the minimum size of this dimension. If not set, then there is no minimum size.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setMinimumSizeDp(@Dimension(unit = DP) float minimumSize) {
-        mImpl.setMinimumSize(DimensionProto.DpProp.newBuilder().setValue(minimumSize));
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(minimumSize));
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public WrappedDimensionProp build() {
-        return new WrappedDimensionProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A type for a dimension that scales itself proportionally to another dimension such that the
-   * aspect ratio defined by the given width and height values is preserved.
-   *
-   * <p>Note that the width and height are unitless; only their ratio is relevant. This allows for
-   * specifying an element's size using common ratios (e.g. width=4, height=3), or to allow an
-   * element to be resized proportionally based on the size of an underlying asset (e.g. an 800x600
-   * image being added to a smaller container and resized accordingly).
-   *
-   * @since 1.0
-   */
-  public static final class ProportionalDimensionProp implements ImageDimension {
-    private final DimensionProto.ProportionalDimensionProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ProportionalDimensionProp(
-        DimensionProto.ProportionalDimensionProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the width to be used when calculating the aspect ratio to preserve.
+     * Shortcut for building an {@link WrappedDimensionProp} that will shrink to the size of its
+     * children.
      *
      * @since 1.0
      */
-    @IntRange(from = 0)
-    public int getAspectRatioWidth() {
-      return mImpl.getAspectRatioWidth();
+    @NonNull
+    public static WrappedDimensionProp wrap() {
+        return WRAP;
     }
 
     /**
-     * Gets the height to be used when calculating the aspect ratio ratio to preserve.
+     * A type for linear dimensions, measured in dp.
      *
      * @since 1.0
      */
-    @IntRange(from = 0)
-    public int getAspectRatioHeight() {
-      return mImpl.getAspectRatioHeight();
+    public static final class DpProp
+            implements ContainerDimension, ImageDimension, SpacerDimension {
+        private final DimensionProto.DpProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        DpProp(DimensionProto.DpProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the static value, in dp.
+         *
+         * @since 1.0
+         */
+        @Dimension(unit = DP)
+        public float getValue() {
+            return mImpl.getValue();
+        }
+
+        /**
+         * Gets the dynamic value, in dp.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public DynamicFloat getDynamicValue() {
+            if (mImpl.hasDynamicValue()) {
+                return DynamicBuilders.dynamicFloatFromProto(mImpl.getDynamicValue());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static DpProp fromProto(
+                @NonNull DimensionProto.DpProp proto, @Nullable Fingerprint fingerprint) {
+            return new DpProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static DpProp fromProto(@NonNull DimensionProto.DpProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.DpProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ContainerDimension toContainerDimensionProto() {
+            return DimensionProto.ContainerDimension.newBuilder().setLinearDimension(mImpl).build();
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ImageDimension toImageDimensionProto() {
+            return DimensionProto.ImageDimension.newBuilder().setLinearDimension(mImpl).build();
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.SpacerDimension toSpacerDimensionProto() {
+            return DimensionProto.SpacerDimension.newBuilder().setLinearDimension(mImpl).build();
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "DpProp{" + "value=" + getValue() + ", dynamicValue=" + getDynamicValue() + "}";
+        }
+
+        /** Builder for {@link DpProp}. */
+        public static final class Builder
+                implements ContainerDimension.Builder,
+                        ImageDimension.Builder,
+                        SpacerDimension.Builder {
+            private final DimensionProto.DpProp.Builder mImpl = DimensionProto.DpProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(756413087);
+
+            /**
+             * @deprecated Use {@link #Builder(float)} instead.
+             */
+            @Deprecated
+            public Builder() {}
+
+            /**
+             * Creates a instance of {@link Builder}.
+             *
+             * @param staticValue the static value, in dp.
+             */
+            public Builder(@Dimension(unit = DP) float staticValue) {
+                setValue(staticValue);
+            }
+
+            /**
+             * Sets the static value, in dp. If a dynamic value is also set and the renderer
+             * supports dynamic values for the corresponding field, this static value will be
+             * ignored.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setValue(@Dimension(unit = DP) float staticValue) {
+                mImpl.setValue(staticValue);
+                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(staticValue));
+                return this;
+            }
+
+            /**
+             * Sets the dynamic value, in dp. Note that when setting this value, the static value is
+             * still required to be set to support older renderers that only read the static value.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setDynamicValue(@NonNull DynamicFloat dynamicValue) {
+                mImpl.setDynamicValue(dynamicValue.toDynamicFloatProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(dynamicValue.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public DpProp build() {
+                if (mImpl.hasDynamicValue() && !mImpl.hasValue()) {
+                    throw new IllegalStateException("Static value is missing.");
+                }
+                return new DpProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    @Override
+    /**
+     * A type for font sizes, measured in sp.
+     *
+     * @since 1.0
+     */
+    public static final class SpProp {
+        private final DimensionProto.SpProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SpProp(DimensionProto.SpProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the value, in sp.
+         *
+         * @since 1.0
+         */
+        @Dimension(unit = SP)
+        public float getValue() {
+            return mImpl.getValue();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static SpProp fromProto(
+                @NonNull DimensionProto.SpProp proto, @Nullable Fingerprint fingerprint) {
+            return new SpProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static SpProp fromProto(@NonNull DimensionProto.SpProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.SpProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "SpProp{" + "value=" + getValue() + "}";
+        }
+
+        /** Builder for {@link SpProp} */
+        public static final class Builder {
+            private final DimensionProto.SpProp.Builder mImpl = DimensionProto.SpProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(631793260);
+
+            public Builder() {}
+
+            /**
+             * Sets the value, in sp. If a dynamic value is also set and the renderer supports
+             * dynamic values for the corresponding field, this static value will be ignored.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setValue(@Dimension(unit = SP) float value) {
+                mImpl.setValue(value);
+                mFingerprint.recordPropertyUpdate(2, Float.floatToIntBits(value));
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public SpProp build() {
+                return new SpProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A type for font spacing, measured in em.
+     *
+     * @since 1.0
+     */
+    public static final class EmProp {
+        private final DimensionProto.EmProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        EmProp(DimensionProto.EmProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the value, in em.
+         *
+         * @since 1.0
+         */
+        public float getValue() {
+            return mImpl.getValue();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static EmProp fromProto(
+                @NonNull DimensionProto.EmProp proto, @Nullable Fingerprint fingerprint) {
+            return new EmProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static EmProp fromProto(@NonNull DimensionProto.EmProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.EmProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "EmProp{" + "value=" + getValue() + "}";
+        }
+
+        /** Builder for {@link EmProp} */
+        public static final class Builder {
+            private final DimensionProto.EmProp.Builder mImpl = DimensionProto.EmProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-659639046);
+
+            public Builder() {}
+
+            /**
+             * Sets the value, in em.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setValue(float value) {
+                mImpl.setValue(value);
+                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(value));
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public EmProp build() {
+                return new EmProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A type for angular dimensions, measured in degrees.
+     *
+     * @since 1.0
+     */
+    public static final class DegreesProp {
+        private final DimensionProto.DegreesProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        DegreesProp(DimensionProto.DegreesProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the static value, in degrees.
+         *
+         * @since 1.0
+         */
+        public float getValue() {
+            return mImpl.getValue();
+        }
+
+        /**
+         * Gets the dynamic value, in degrees.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public DynamicFloat getDynamicValue() {
+            if (mImpl.hasDynamicValue()) {
+                return DynamicBuilders.dynamicFloatFromProto(mImpl.getDynamicValue());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static DegreesProp fromProto(
+                @NonNull DimensionProto.DegreesProp proto, @Nullable Fingerprint fingerprint) {
+            return new DegreesProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static DegreesProp fromProto(@NonNull DimensionProto.DegreesProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.DegreesProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "DegreesProp{"
+                    + "value="
+                    + getValue()
+                    + ", dynamicValue="
+                    + getDynamicValue()
+                    + "}";
+        }
+
+        /** Builder for {@link DegreesProp} */
+        public static final class Builder {
+            private final DimensionProto.DegreesProp.Builder mImpl =
+                    DimensionProto.DegreesProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1927567665);
+
+            /**
+             * @deprecated Use {@link #Builder(float)} instead.
+             */
+            @Deprecated
+            public Builder() {}
+
+            /**
+             * Creates a instance of {@link Builder}.
+             *
+             * @param staticValue the static value, in degrees.
+             */
+            public Builder(float staticValue) {
+                setValue(staticValue);
+            }
+
+            /**
+             * Sets the static value, in degrees. If a dynamic value is also set and the renderer
+             * supports dynamic values for the corresponding field, this static value will be
+             * ignored.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setValue(float staticValue) {
+                mImpl.setValue(staticValue);
+                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(staticValue));
+                return this;
+            }
+
+            /**
+             * Sets the dynamic value, in degrees. Note that when setting this value, the static
+             * value is still required to be set to support older renderers that only read the
+             * static value.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setDynamicValue(@NonNull DynamicFloat dynamicValue) {
+                mImpl.setDynamicValue(dynamicValue.toDynamicFloatProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(dynamicValue.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public DegreesProp build() {
+                if (mImpl.hasDynamicValue() && !mImpl.hasValue()) {
+                    throw new IllegalStateException("Static value is missing.");
+                }
+                return new DegreesProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A type for a dimension that fills all the space it can (i.e. MATCH_PARENT in Android
+     * parlance).
+     *
+     * @since 1.0
+     */
+    public static final class ExpandedDimensionProp implements ContainerDimension, ImageDimension {
+        private final DimensionProto.ExpandedDimensionProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ExpandedDimensionProp(
+                DimensionProto.ExpandedDimensionProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the layout weight (a dimensionless scalar value) for this element. This will only
+         * affect the width of children of a {@link
+         * androidx.wear.protolayout.LayoutElementBuilders.Row} or the height of children of a
+         * {@link androidx.wear.protolayout.LayoutElementBuilders.Column}. By default, all children
+         * have equal weight. Where applicable, the width or height of the element is proportional
+         * to the sum of the weights of its siblings.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public FloatProp getLayoutWeight() {
+            if (mImpl.hasLayoutWeight()) {
+                return FloatProp.fromProto(mImpl.getLayoutWeight());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static ExpandedDimensionProp fromProto(
+                @NonNull DimensionProto.ExpandedDimensionProp proto,
+                @Nullable Fingerprint fingerprint) {
+            return new ExpandedDimensionProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static ExpandedDimensionProp fromProto(
+                @NonNull DimensionProto.ExpandedDimensionProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.ExpandedDimensionProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ContainerDimension toContainerDimensionProto() {
+            return DimensionProto.ContainerDimension.newBuilder()
+                    .setExpandedDimension(mImpl)
+                    .build();
+        }
+
+        /* */
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ImageDimension toImageDimensionProto() {
+            return DimensionProto.ImageDimension.newBuilder().setExpandedDimension(mImpl).build();
+        }
+
+        /** Builder for {@link ExpandedDimensionProp}. */
+        public static final class Builder
+                implements ContainerDimension.Builder, ImageDimension.Builder {
+            private final DimensionProto.ExpandedDimensionProp.Builder mImpl =
+                    DimensionProto.ExpandedDimensionProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-997720604);
+
+            public Builder() {}
+
+            /**
+             * Sets the layout weight (a dimensionless scalar value) for this element. This will
+             * only affect the width of children of a {@link
+             * androidx.wear.protolayout.LayoutElementBuilders.Row} or the height of children of a
+             * {@link androidx.wear.protolayout.LayoutElementBuilders.Column}. By default, all
+             * children have equal weight. Where applicable, the width or height of the element is
+             * proportional to the sum of the weights of its siblings.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setLayoutWeight(@NonNull FloatProp layoutWeight) {
+                mImpl.setLayoutWeight(layoutWeight.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(layoutWeight.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ExpandedDimensionProp build() {
+                return new ExpandedDimensionProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A type for a dimension that sizes itself to the size of its children (i.e. WRAP_CONTENT in
+     * Android parlance).
+     *
+     * @since 1.0
+     */
+    public static final class WrappedDimensionProp implements ContainerDimension {
+        private final DimensionProto.WrappedDimensionProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        WrappedDimensionProp(
+                DimensionProto.WrappedDimensionProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the minimum size of this dimension. If not set, then there is no minimum size.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public DpProp getMinimumSize() {
+            if (mImpl.hasMinimumSize()) {
+                return DpProp.fromProto(mImpl.getMinimumSize());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static WrappedDimensionProp fromProto(
+                @NonNull DimensionProto.WrappedDimensionProp proto,
+                @Nullable Fingerprint fingerprint) {
+            return new WrappedDimensionProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static WrappedDimensionProp fromProto(@NonNull DimensionProto.WrappedDimensionProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.WrappedDimensionProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ContainerDimension toContainerDimensionProto() {
+            return DimensionProto.ContainerDimension.newBuilder()
+                    .setWrappedDimension(mImpl)
+                    .build();
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "WrappedDimensionProp{" + "minimumSize=" + getMinimumSize() + "}";
+        }
+
+        /** Builder for {@link WrappedDimensionProp}. */
+        public static final class Builder implements ContainerDimension.Builder {
+            private final DimensionProto.WrappedDimensionProp.Builder mImpl =
+                    DimensionProto.WrappedDimensionProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1118918114);
+
+            public Builder() {}
+
+            /**
+             * Sets the minimum size of this dimension. If not set, then there is no minimum size.
+             *
+             * <p>Note that this field only supports static values.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setMinimumSize(@NonNull DpProp minimumSize) {
+                if (minimumSize.getDynamicValue() != null) {
+                    throw new IllegalArgumentException(
+                            "setMinimumSize doesn't support dynamic values.");
+                }
+
+                mImpl.setMinimumSize(minimumSize.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(minimumSize.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public WrappedDimensionProp build() {
+                return new WrappedDimensionProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A type for a dimension that scales itself proportionally to another dimension such that the
+     * aspect ratio defined by the given width and height values is preserved.
+     *
+     * <p>Note that the width and height are unitless; only their ratio is relevant. This allows for
+     * specifying an element's size using common ratios (e.g. width=4, height=3), or to allow an
+     * element to be resized proportionally based on the size of an underlying asset (e.g. an
+     * 800x600 image being added to a smaller container and resized accordingly).
+     *
+     * @since 1.0
+     */
+    public static final class ProportionalDimensionProp implements ImageDimension {
+        private final DimensionProto.ProportionalDimensionProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ProportionalDimensionProp(
+                DimensionProto.ProportionalDimensionProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the width to be used when calculating the aspect ratio to preserve.
+         *
+         * @since 1.0
+         */
+        @IntRange(from = 0)
+        public int getAspectRatioWidth() {
+            return mImpl.getAspectRatioWidth();
+        }
+
+        /**
+         * Gets the height to be used when calculating the aspect ratio ratio to preserve.
+         *
+         * @since 1.0
+         */
+        @IntRange(from = 0)
+        public int getAspectRatioHeight() {
+            return mImpl.getAspectRatioHeight();
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static ProportionalDimensionProp fromProto(
+                @NonNull DimensionProto.ProportionalDimensionProp proto,
+                @Nullable Fingerprint fingerprint) {
+            return new ProportionalDimensionProp(proto, fingerprint);
+        }
+
+        @NonNull
+        static ProportionalDimensionProp fromProto(
+                @NonNull DimensionProto.ProportionalDimensionProp proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.ProportionalDimensionProp toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public DimensionProto.ImageDimension toImageDimensionProto() {
+            return DimensionProto.ImageDimension.newBuilder()
+                    .setProportionalDimension(mImpl)
+                    .build();
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "ProportionalDimensionProp{"
+                    + "aspectRatioWidth="
+                    + getAspectRatioWidth()
+                    + ", aspectRatioHeight="
+                    + getAspectRatioHeight()
+                    + "}";
+        }
+
+        /** Builder for {@link ProportionalDimensionProp}. */
+        public static final class Builder implements ImageDimension.Builder {
+            private final DimensionProto.ProportionalDimensionProp.Builder mImpl =
+                    DimensionProto.ProportionalDimensionProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1725027476);
+
+            public Builder() {}
+
+            /**
+             * Sets the width to be used when calculating the aspect ratio to preserve.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setAspectRatioWidth(@IntRange(from = 0) int aspectRatioWidth) {
+                mImpl.setAspectRatioWidth(aspectRatioWidth);
+                mFingerprint.recordPropertyUpdate(1, aspectRatioWidth);
+                return this;
+            }
+
+            /**
+             * Sets the height to be used when calculating the aspect ratio ratio to preserve.
+             *
+             * @since 1.0
+             */
+            @NonNull
+            public Builder setAspectRatioHeight(@IntRange(from = 0) int aspectRatioHeight) {
+                mImpl.setAspectRatioHeight(aspectRatioHeight);
+                mFingerprint.recordPropertyUpdate(2, aspectRatioHeight);
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ProportionalDimensionProp build() {
+                return new ProportionalDimensionProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * Interface defining a dimension that can be applied to a container.
+     *
+     * @since 1.0
+     */
+    public interface ContainerDimension {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.ContainerDimension toContainerDimensionProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link ContainerDimension} objects. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            ContainerDimension build();
+        }
+    }
+
+    /** Creates a new wrapper instance from the proto. */
     @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
+    @NonNull
+    public static ContainerDimension containerDimensionFromProto(
+            @NonNull DimensionProto.ContainerDimension proto, @Nullable Fingerprint fingerprint) {
+        if (proto.hasLinearDimension()) {
+            return DpProp.fromProto(proto.getLinearDimension(), fingerprint);
+        }
+        if (proto.hasExpandedDimension()) {
+            return ExpandedDimensionProp.fromProto(proto.getExpandedDimension(), fingerprint);
+        }
+        if (proto.hasWrappedDimension()) {
+            return WrappedDimensionProp.fromProto(proto.getWrappedDimension(), fingerprint);
+        }
+        throw new IllegalStateException(
+                "Proto was not a recognised instance of ContainerDimension");
     }
 
     @NonNull
-    static ProportionalDimensionProp fromProto(
-        @NonNull DimensionProto.ProportionalDimensionProp proto) {
-      return new ProportionalDimensionProp(proto, null);
+    static ContainerDimension containerDimensionFromProto(
+            @NonNull DimensionProto.ContainerDimension proto) {
+        return containerDimensionFromProto(proto, null);
+    }
+
+    /**
+     * Interface defining a dimension that can be applied to an image.
+     *
+     * @since 1.0
+     */
+    public interface ImageDimension {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.ImageDimension toImageDimensionProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link ImageDimension} objects. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            ImageDimension build();
+        }
+    }
+
+    /** Creates a new wrapper instance from the proto. */
+    @RestrictTo(Scope.LIBRARY_GROUP)
+    @NonNull
+    public static ImageDimension imageDimensionFromProto(
+            @NonNull DimensionProto.ImageDimension proto, @Nullable Fingerprint fingerprint) {
+        if (proto.hasLinearDimension()) {
+            return DpProp.fromProto(proto.getLinearDimension(), fingerprint);
+        }
+        if (proto.hasExpandedDimension()) {
+            return ExpandedDimensionProp.fromProto(proto.getExpandedDimension(), fingerprint);
+        }
+        if (proto.hasProportionalDimension()) {
+            return ProportionalDimensionProp.fromProto(
+                    proto.getProportionalDimension(), fingerprint);
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of ImageDimension");
     }
 
     @NonNull
-    DimensionProto.ProportionalDimensionProp toProto() {
-      return mImpl;
+    static ImageDimension imageDimensionFromProto(@NonNull DimensionProto.ImageDimension proto) {
+        return imageDimensionFromProto(proto, null);
     }
 
-    @Override
+    /**
+     * Interface defining a dimension that can be applied to a spacer.
+     *
+     * @since 1.0
+     */
+    public interface SpacerDimension {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        DimensionProto.SpacerDimension toSpacerDimensionProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link SpacerDimension} objects. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            SpacerDimension build();
+        }
+    }
+
+    /** Creates a new wrapper instance from the proto. */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
-    public DimensionProto.ImageDimension toImageDimensionProto() {
-      return DimensionProto.ImageDimension.newBuilder().setProportionalDimension(mImpl).build();
+    public static SpacerDimension spacerDimensionFromProto(
+            @NonNull DimensionProto.SpacerDimension proto, @Nullable Fingerprint fingerprint) {
+        if (proto.hasLinearDimension()) {
+            return DpProp.fromProto(proto.getLinearDimension(), fingerprint);
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of SpacerDimension");
     }
 
-    /** Builder for {@link ProportionalDimensionProp}. */
-    public static final class Builder implements ImageDimension.Builder {
-      private final DimensionProto.ProportionalDimensionProp.Builder mImpl =
-          DimensionProto.ProportionalDimensionProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1725027476);
-
-      public Builder() {}
-
-      /**
-       * Sets the width to be used when calculating the aspect ratio to preserve.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setAspectRatioWidth(@IntRange(from = 0) int aspectRatioWidth) {
-        mImpl.setAspectRatioWidth(aspectRatioWidth);
-        mFingerprint.recordPropertyUpdate(1, aspectRatioWidth);
-        return this;
-      }
-
-      /**
-       * Sets the height to be used when calculating the aspect ratio ratio to preserve.
-       *
-       * @since 1.0
-       */
-      @NonNull
-      public Builder setAspectRatioHeight(@IntRange(from = 0) int aspectRatioHeight) {
-        mImpl.setAspectRatioHeight(aspectRatioHeight);
-        mFingerprint.recordPropertyUpdate(2, aspectRatioHeight);
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public ProportionalDimensionProp build() {
-        return new ProportionalDimensionProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * Interface defining a dimension that can be applied to a container.
-   *
-   * @since 1.0
-   */
-  public interface ContainerDimension {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
-    DimensionProto.ContainerDimension toContainerDimensionProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link ContainerDimension} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      ContainerDimension build();
+    static SpacerDimension spacerDimensionFromProto(@NonNull DimensionProto.SpacerDimension proto) {
+        return spacerDimensionFromProto(proto, null);
     }
-  }
-
-  @NonNull
-  static ContainerDimension containerDimensionFromProto(
-      @NonNull DimensionProto.ContainerDimension proto) {
-    if (proto.hasLinearDimension()) {
-      return DpProp.fromProto(proto.getLinearDimension());
-    }
-    if (proto.hasExpandedDimension()) {
-      return ExpandedDimensionProp.fromProto(proto.getExpandedDimension());
-    }
-    if (proto.hasWrappedDimension()) {
-      return WrappedDimensionProp.fromProto(proto.getWrappedDimension());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of ContainerDimension");
-  }
-
-  /**
-   * Interface defining a dimension that can be applied to an image.
-   *
-   * @since 1.0
-   */
-  public interface ImageDimension {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    DimensionProto.ImageDimension toImageDimensionProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link ImageDimension} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      ImageDimension build();
-    }
-  }
-
-  @NonNull
-  static ImageDimension imageDimensionFromProto(@NonNull DimensionProto.ImageDimension proto) {
-    if (proto.hasLinearDimension()) {
-      return DpProp.fromProto(proto.getLinearDimension());
-    }
-    if (proto.hasExpandedDimension()) {
-      return ExpandedDimensionProp.fromProto(proto.getExpandedDimension());
-    }
-    if (proto.hasProportionalDimension()) {
-      return ProportionalDimensionProp.fromProto(proto.getProportionalDimension());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of ImageDimension");
-  }
-
-  /**
-   * Interface defining a dimension that can be applied to a spacer.
-   *
-   * @since 1.0
-   */
-  public interface SpacerDimension {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    DimensionProto.SpacerDimension toSpacerDimensionProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link SpacerDimension} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      SpacerDimension build();
-    }
-  }
-
-  @NonNull
-  static SpacerDimension spacerDimensionFromProto(@NonNull DimensionProto.SpacerDimension proto) {
-    if (proto.hasLinearDimension()) {
-      return DpProp.fromProto(proto.getLinearDimension());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of SpacerDimension");
-  }
 }
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 03a015a..3aa4228 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
@@ -56,4052 +56,3960 @@
 import java.util.Collections;
 import java.util.List;
 
-/** Builders for composable layout elements that can be combined together to create renderable
- * UI */
+/** Builders for composable layout elements that can be combined together to create renderable UI */
 public final class LayoutElementBuilders {
-  private LayoutElementBuilders() {}
+    private LayoutElementBuilders() {}
 
-  /**
-   * The weight to be applied to the font.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({FONT_WEIGHT_UNDEFINED, FONT_WEIGHT_NORMAL, FONT_WEIGHT_MEDIUM, FONT_WEIGHT_BOLD})
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface FontWeight {}
+    /** The weight to be applied to the font. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({FONT_WEIGHT_UNDEFINED, FONT_WEIGHT_NORMAL, FONT_WEIGHT_MEDIUM, FONT_WEIGHT_BOLD})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FontWeight {}
 
-  /** Font weight is undefined. */
-  public static final int FONT_WEIGHT_UNDEFINED = 0;
+    /** Font weight is undefined. */
+    public static final int FONT_WEIGHT_UNDEFINED = 0;
 
-  /** Normal font weight. */
-  public static final int FONT_WEIGHT_NORMAL = 400;
+    /** Normal font weight. */
+    public static final int FONT_WEIGHT_NORMAL = 400;
 
-  /** Medium font weight. */
-  @ProtoLayoutExperimental public static final int FONT_WEIGHT_MEDIUM = 500;
+    /** Medium font weight. */
+    @ProtoLayoutExperimental public static final int FONT_WEIGHT_MEDIUM = 500;
 
-  /** Bold font weight. */
-  public static final int FONT_WEIGHT_BOLD = 700;
-
-  /**
-   * The variant of a font. Some renderers may use different fonts for title and body text, which
-   * can be selected using this field.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({FONT_VARIANT_UNDEFINED, FONT_VARIANT_TITLE, FONT_VARIANT_BODY})
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface FontVariant {}
-
-  /** Font variant is undefined. */
-  public static final int FONT_VARIANT_UNDEFINED = 0;
-
-  /** Font variant suited for title text. */
-  public static final int FONT_VARIANT_TITLE = 1;
-
-  /** Font variant suited for body text. */
-  public static final int FONT_VARIANT_BODY = 2;
-
-  /**
-   * The alignment of a {@link SpanImage} within the line height of the surrounding {@link
-   * Spannable}.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({
-    SPAN_VERTICAL_ALIGN_UNDEFINED,
-    SPAN_VERTICAL_ALIGN_BOTTOM,
-    SPAN_VERTICAL_ALIGN_TEXT_BASELINE
-  })
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface SpanVerticalAlignment {}
-
-  /** Alignment is undefined. */
-  public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0;
-
-  /**
-   * Align to the bottom of the line (descent of the largest text in this line). If there is no text
-   * in the line containing this image, this will align to the bottom of the line, where the line
-   * height is defined as the height of the largest image in the line.
-   */
-  public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1;
-
-  /**
-   * Align to the baseline of the text. Note that if the line in the {@link Spannable} which
-   * contains this image does not contain any text, the effects of using this alignment are
-   * undefined.
-   */
-  public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2;
-
-  /**
-   * How text that will not fit inside the bounds of a {@link Text} element will be handled.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({TEXT_OVERFLOW_UNDEFINED, TEXT_OVERFLOW_TRUNCATE, TEXT_OVERFLOW_ELLIPSIZE_END})
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface TextOverflow {}
-
-  /** Overflow behavior is undefined. */
-  public static final int TEXT_OVERFLOW_UNDEFINED = 0;
-
-  /**
-   * Truncate the text to fit inside of the {@link Text} element's bounds. If text is truncated, it
-   * will be truncated on a word boundary.
-   */
-  public static final int TEXT_OVERFLOW_TRUNCATE = 1;
-
-  /**
-   * Truncate the 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.
-   */
-  public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2;
-
-  /**
-   * How content which does not match the dimensions of its bounds (e.g. an image resource being
-   * drawn inside an {@link Image}) will be resized to fit its bounds.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({
-    CONTENT_SCALE_MODE_UNDEFINED,
-    CONTENT_SCALE_MODE_FIT,
-    CONTENT_SCALE_MODE_CROP,
-    CONTENT_SCALE_MODE_FILL_BOUNDS
-  })
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface ContentScaleMode {}
-
-  /** Content scaling is undefined. */
-  public static final int CONTENT_SCALE_MODE_UNDEFINED = 0;
-
-  /**
-   * Content will be scaled to fit inside its bounds, proportionally. As an example, If a 10x5 image
-   * was going to be drawn inside a 50x50 {@link Image} element, the actual image resource would be
-   * drawn as a 50x25 image, centered within the 50x50 bounds.
-   */
-  public static final int CONTENT_SCALE_MODE_FIT = 1;
-
-  /**
-   * Content will be resized proportionally so it completely fills its bounds, and anything outside
-   * of the bounds will be cropped. As an example, if a 10x5 image was going to be drawn inside a
-   * 50x50 {@link Image} element, the image resource would be drawn as a 100x50 image, centered
-   * within its bounds (and with 25px cropped from both the left and right sides).
-   */
-  public static final int CONTENT_SCALE_MODE_CROP = 2;
-
-  /**
-   * Content will be resized to fill its bounds, without taking into account the aspect ratio. If a
-   * 10x5 image was going to be drawn inside a 50x50 {@link Image} element, the image would be drawn
-   * as a 50x50 image, stretched vertically.
-   */
-  public static final int CONTENT_SCALE_MODE_FILL_BOUNDS = 3;
-
-  /** An extensible {@code FontWeight} property. */
-  public static final class FontWeightProp {
-    private final LayoutElementProto.FontWeightProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    FontWeightProp(
-        LayoutElementProto.FontWeightProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the value. Intended for testing purposes only. */
-    @FontWeight
-    public int getValue() {
-      return mImpl.getValue().getNumber();
-    }
+    /** Bold font weight. */
+    public static final int FONT_WEIGHT_BOLD = 700;
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * The variant of a font. Some renderers may use different fonts for title and body text, which
+     * can be selected using this field.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({FONT_VARIANT_UNDEFINED, FONT_VARIANT_TITLE, FONT_VARIANT_BODY})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface FontVariant {}
 
-    @NonNull
-    static FontWeightProp fromProto(@NonNull LayoutElementProto.FontWeightProp proto) {
-      return new FontWeightProp(proto, null);
-    }
+    /** Font variant is undefined. */
+    public static final int FONT_VARIANT_UNDEFINED = 0;
 
-    @NonNull
-    LayoutElementProto.FontWeightProp toProto() {
-      return mImpl;
-    }
+    /** Font variant suited for title text. */
+    public static final int FONT_VARIANT_TITLE = 1;
 
-    /** Builder for {@link FontWeightProp} */
-    public static final class Builder {
-      private final LayoutElementProto.FontWeightProp.Builder mImpl =
-          LayoutElementProto.FontWeightProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1793388920);
-
-      public Builder() {}
-
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@FontWeight int value) {
-        mImpl.setValue(LayoutElementProto.FontWeight.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public FontWeightProp build() {
-        return new FontWeightProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** An extensible {@code FontVariant} property. */
-  @ProtoLayoutExperimental
-  public static final class FontVariantProp {
-    private final LayoutElementProto.FontVariantProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    FontVariantProp(
-        LayoutElementProto.FontVariantProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the value. Intended for testing purposes only. */
-    @FontVariant
-    public int getValue() {
-      return mImpl.getValue().getNumber();
-    }
+    /** Font variant suited for body text. */
+    public static final int FONT_VARIANT_BODY = 2;
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * The alignment of a {@link SpanImage} within the line height of the surrounding {@link
+     * Spannable}.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        SPAN_VERTICAL_ALIGN_UNDEFINED,
+        SPAN_VERTICAL_ALIGN_BOTTOM,
+        SPAN_VERTICAL_ALIGN_TEXT_BASELINE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface SpanVerticalAlignment {}
 
-    @NonNull
-    static FontVariantProp fromProto(@NonNull LayoutElementProto.FontVariantProp proto) {
-      return new FontVariantProp(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.FontVariantProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link FontVariantProp} */
-    public static final class Builder {
-      private final LayoutElementProto.FontVariantProp.Builder mImpl =
-          LayoutElementProto.FontVariantProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-293831500);
-
-      public Builder() {}
-
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@FontVariant int value) {
-        mImpl.setValue(LayoutElementProto.FontVariant.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public FontVariantProp build() {
-        return new FontVariantProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** An extensible {@code SpanVerticalAlignment} property. */
-  public static final class SpanVerticalAlignmentProp {
-    private final LayoutElementProto.SpanVerticalAlignmentProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SpanVerticalAlignmentProp(
-        LayoutElementProto.SpanVerticalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the value. Intended for testing purposes only. */
-    @SpanVerticalAlignment
-    public int getValue() {
-      return mImpl.getValue().getNumber();
-    }
+    /** Alignment is undefined. */
+    public static final int SPAN_VERTICAL_ALIGN_UNDEFINED = 0;
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * Align to the bottom of the line (descent of the largest text in this line). If there is no
+     * text in the line containing this image, this will align to the bottom of the line, where the
+     * line height is defined as the height of the largest image in the line.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static SpanVerticalAlignmentProp fromProto(
-        @NonNull LayoutElementProto.SpanVerticalAlignmentProp proto) {
-      return new SpanVerticalAlignmentProp(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.SpanVerticalAlignmentProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link SpanVerticalAlignmentProp} */
-    public static final class Builder {
-      private final LayoutElementProto.SpanVerticalAlignmentProp.Builder mImpl =
-          LayoutElementProto.SpanVerticalAlignmentProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1008812329);
-
-      public Builder() {}
-
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@SpanVerticalAlignment int value) {
-        mImpl.setValue(LayoutElementProto.SpanVerticalAlignment.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public SpanVerticalAlignmentProp build() {
-        return new SpanVerticalAlignmentProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** The styling of a font (e.g. font size, and metrics). */
-  public static final class FontStyle {
-    private final LayoutElementProto.FontStyle mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    FontStyle(LayoutElementProto.FontStyle impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+    public static final int SPAN_VERTICAL_ALIGN_BOTTOM = 1;
 
     /**
-     * Gets the size of the font, in scaled pixels (sp). If not specified, defaults to the size of
-     * the system's "body" font. Intended for testing purposes only.
+     * Align to the baseline of the text. Note that if the line in the {@link Spannable} which
+     * contains this image does not contain any text, the effects of using this alignment are
+     * undefined.
      */
-    @Nullable
-    public SpProp getSize() {
-      if (mImpl.hasSize()) {
-        return SpProp.fromProto(mImpl.getSize());
-      } else {
-        return null;
-      }
-    }
+    public static final int SPAN_VERTICAL_ALIGN_TEXT_BASELINE = 2;
+
+    /** How text that will not fit inside the bounds of a {@link Text} element will be handled. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({TEXT_OVERFLOW_UNDEFINED, TEXT_OVERFLOW_TRUNCATE, TEXT_OVERFLOW_ELLIPSIZE_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TextOverflow {}
+
+    /** Overflow behavior is undefined. */
+    public static final int TEXT_OVERFLOW_UNDEFINED = 0;
 
     /**
-     * Gets whether the text should be rendered in a italic typeface. If not specified, defaults to
-     * "false". Intended for testing purposes only.
+     * Truncate the text to fit inside of the {@link Text} element's bounds. If text is truncated,
+     * it will be truncated on a word boundary.
      */
-    @Nullable
-    public BoolProp getItalic() {
-      if (mImpl.hasItalic()) {
-        return BoolProp.fromProto(mImpl.getItalic());
-      } else {
-        return null;
-      }
-    }
+    public static final int TEXT_OVERFLOW_TRUNCATE = 1;
 
     /**
-     * Gets whether the text should be rendered with an underline. If not specified, defaults to
-     * "false". Intended for testing purposes only.
+     * Truncate the 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.
      */
-    @Nullable
-    public BoolProp getUnderline() {
-      if (mImpl.hasUnderline()) {
-        return BoolProp.fromProto(mImpl.getUnderline());
-      } else {
-        return null;
-      }
-    }
+    public static final int TEXT_OVERFLOW_ELLIPSIZE_END = 2;
 
     /**
-     * Gets the text color. If not defined, defaults to white. Intended for testing purposes only.
+     * How content which does not match the dimensions of its bounds (e.g. an image resource being
+     * drawn inside an {@link Image}) will be resized to fit its bounds.
      */
-    @Nullable
-    public ColorProp getColor() {
-      if (mImpl.hasColor()) {
-        return ColorProp.fromProto(mImpl.getColor());
-      } else {
-        return null;
-      }
-    }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        CONTENT_SCALE_MODE_UNDEFINED,
+        CONTENT_SCALE_MODE_FIT,
+        CONTENT_SCALE_MODE_CROP,
+        CONTENT_SCALE_MODE_FILL_BOUNDS
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ContentScaleMode {}
+
+    /** Content scaling is undefined. */
+    public static final int CONTENT_SCALE_MODE_UNDEFINED = 0;
 
     /**
-     * Gets the weight of the font. If the provided value is not supported on a platform, the
-     * nearest supported value will be used. If not defined, or when set to an invalid value,
-     * defaults to "normal". Intended for testing purposes only.
+     * Content will be scaled to fit inside its bounds, proportionally. As an example, If a 10x5
+     * image was going to be drawn inside a 50x50 {@link Image} element, the actual image resource
+     * would be drawn as a 50x25 image, centered within the 50x50 bounds.
      */
-    @Nullable
-    public FontWeightProp getWeight() {
-      if (mImpl.hasWeight()) {
-        return FontWeightProp.fromProto(mImpl.getWeight());
-      } else {
-        return null;
-      }
-    }
+    public static final int CONTENT_SCALE_MODE_FIT = 1;
 
     /**
-     * Gets the text letter-spacing. Positive numbers increase the space between letters while
-     * negative numbers tighten the space. If not specified, defaults to 0. Intended for testing
-     * purposes only.
+     * Content will be resized proportionally so it completely fills its bounds, and anything
+     * outside of the bounds will be cropped. As an example, if a 10x5 image was going to be drawn
+     * inside a 50x50 {@link Image} element, the image resource would be drawn as a 100x50 image,
+     * centered within its bounds (and with 25px cropped from both the left and right sides).
      */
-    @Nullable
-    public EmProp getLetterSpacing() {
-      if (mImpl.hasLetterSpacing()) {
-        return EmProp.fromProto(mImpl.getLetterSpacing());
-      } else {
-        return null;
-      }
-    }
+    public static final int CONTENT_SCALE_MODE_CROP = 2;
 
     /**
-     * Gets the variant of a font. Some renderers may use different fonts for title and body
-     * text, which can be selected using this field. If not specified, defaults to "body".
-     * Intended for testing purposes only.
+     * Content will be resized to fill its bounds, without taking into account the aspect ratio. If
+     * a 10x5 image was going to be drawn inside a 50x50 {@link Image} element, the image would be
+     * drawn as a 50x50 image, stretched vertically.
      */
+    public static final int CONTENT_SCALE_MODE_FILL_BOUNDS = 3;
+
+    /** An extensible {@code FontWeight} property. */
+    public static final class FontWeightProp {
+        private final LayoutElementProto.FontWeightProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        FontWeightProp(LayoutElementProto.FontWeightProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @FontWeight
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static FontWeightProp fromProto(@NonNull LayoutElementProto.FontWeightProp proto) {
+            return new FontWeightProp(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.FontWeightProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link FontWeightProp} */
+        public static final class Builder {
+            private final LayoutElementProto.FontWeightProp.Builder mImpl =
+                    LayoutElementProto.FontWeightProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1793388920);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@FontWeight int value) {
+                mImpl.setValue(LayoutElementProto.FontWeight.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public FontWeightProp build() {
+                return new FontWeightProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** An extensible {@code FontVariant} property. */
     @ProtoLayoutExperimental
-    @Nullable
-    public FontVariantProp getVariant() {
-      if (mImpl.hasVariant()) {
-        return FontVariantProp.fromProto(mImpl.getVariant());
-      } else {
-        return null;
-      }
-    }
+    public static final class FontVariantProp {
+        private final LayoutElementProto.FontVariantProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static FontStyle fromProto(@NonNull LayoutElementProto.FontStyle proto) {
-      return new FontStyle(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.FontStyle toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link FontStyle} */
-    public static final class Builder {
-      private final LayoutElementProto.FontStyle.Builder mImpl =
-          LayoutElementProto.FontStyle.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(181264306);
-
-      public Builder() {}
-
-      /**
-       * Sets the size of the font, in scaled pixels (sp). If not specified, defaults to the size of
-       * the system's "body" font.
-       */
-      @NonNull
-      public Builder setSize(@NonNull SpProp size) {
-        mImpl.setSize(size.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets whether the text should be rendered in a italic typeface. If not specified, defaults
-       * to "false".
-       */
-      @NonNull
-      public Builder setItalic(@NonNull BoolProp italic) {
-        mImpl.setItalic(italic.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(italic.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets whether the text should be rendered in a italic typeface. If not specified,
-       * defaults to "false".
-       */
-      @SuppressLint("MissingGetterMatchingBuilder")
-      @NonNull
-      public Builder setItalic(boolean italic) {
-        mImpl.setItalic(TypesProto.BoolProp.newBuilder().setValue(italic));
-        return this;
-      }
-      /**
-       * Sets whether the text should be rendered with an underline. If not specified, defaults to
-       * "false".
-       */
-      @NonNull
-      public Builder setUnderline(@NonNull BoolProp underline) {
-        mImpl.setUnderline(underline.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(underline.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets whether the text should be rendered with an underline. If not specified,
-       * defaults to "false".
-       */
-      @SuppressLint("MissingGetterMatchingBuilder")
-      @NonNull
-      public Builder setUnderline(boolean underline) {
-        mImpl.setUnderline(TypesProto.BoolProp.newBuilder().setValue(underline));
-        return this;
-      }
-
-      /**
-       * Sets the text color. If not defined, defaults to white.
-       */
-      @NonNull
-      public Builder setColor(@NonNull ColorProp color) {
-        mImpl.setColor(color.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the weight of the font. If the provided value is not supported on a platform, the
-       * nearest supported value will be used. If not defined, or when set to an invalid value,
-       * defaults to "normal".
-       */
-      @NonNull
-      public Builder setWeight(@NonNull FontWeightProp weight) {
-        mImpl.setWeight(weight.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(weight.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the weight of the font. If the provided value is not supported on a platform,
-       * the nearest supported value will be used. If not defined, or when set to an invalid
-       * value, defaults to "normal".
-       */
-      @NonNull
-      public Builder setWeight(@FontWeight int weight) {
-        mImpl.setWeight(
-                LayoutElementProto.FontWeightProp.newBuilder()
-                        .setValue(LayoutElementProto.FontWeight.forNumber(weight)));
-        return this;
-      }
-
-      /**
-       * Sets the text letter-spacing. Positive numbers increase the space between letters while
-       * negative numbers tighten the space. If not specified, defaults to 0.
-       */
-      @NonNull
-      public Builder setLetterSpacing(@NonNull EmProp letterSpacing) {
-        mImpl.setLetterSpacing(letterSpacing.toProto());
-        mFingerprint.recordPropertyUpdate(
-            6, checkNotNull(letterSpacing.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the variant of a font. Some renderers may use different fonts for title and body
-       * text, which can be selected using this field. If not specified, defaults to "body".
-       */
-      @ProtoLayoutExperimental
-      @NonNull
-      public Builder setVariant(@NonNull FontVariantProp variant) {
-        mImpl.setVariant(variant.toProto());
-        return this;
-      }
-      /**
-       * Sets the variant of a font. Some renderers may use different fonts for title and body
-       * text, which can be selected using this field. If not specified, defaults to "body".
-       */
-      @ProtoLayoutExperimental
-      @NonNull
-      public Builder setVariant(@FontVariant int variant) {
-        mImpl.setVariant(
-                LayoutElementProto.FontVariantProp.newBuilder()
-                        .setValue(LayoutElementProto.FontVariant.forNumber(variant)));
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public FontStyle build() {
-        return new FontStyle(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** An extensible {@code TextOverflow} property. */
-  public static final class TextOverflowProp {
-    private final LayoutElementProto.TextOverflowProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    TextOverflowProp(
-        LayoutElementProto.TextOverflowProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the value. Intended for testing purposes only. */
-    @TextOverflow
-    public int getValue() {
-      return mImpl.getValue().getNumber();
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static TextOverflowProp fromProto(@NonNull LayoutElementProto.TextOverflowProp proto) {
-      return new TextOverflowProp(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.TextOverflowProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link TextOverflowProp} */
-    public static final class Builder {
-      private final LayoutElementProto.TextOverflowProp.Builder mImpl =
-          LayoutElementProto.TextOverflowProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1183432233);
-
-      public Builder() {}
-
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@TextOverflow int value) {
-        mImpl.setValue(LayoutElementProto.TextOverflow.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public TextOverflowProp build() {
-        return new TextOverflowProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A text string. */
-  public static final class Text implements LayoutElement {
-    private final LayoutElementProto.Text mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Text(LayoutElementProto.Text impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the text to render. Intended for testing purposes only. */
-    @Nullable
-    public StringProp getText() {
-      if (mImpl.hasText()) {
-        return StringProp.fromProto(mImpl.getText());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the style of font to use (size, bold etc). If not specified, defaults to the platform's
-     * default body font. Intended for testing purposes only.
-     */
-    @Nullable
-    public FontStyle getFontStyle() {
-      if (mImpl.hasFontStyle()) {
-        return FontStyle.fromProto(mImpl.getFontStyle());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the maximum number of lines that can be represented by the {@link Text} element. If not
-     * defined, the {@link Text} element will be treated as a single-line element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Int32Prop getMaxLines() {
-      if (mImpl.hasMaxLines()) {
-        return Int32Prop.fromProto(mImpl.getMaxLines());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets alignment of the text within its bounds. Note that a {@link Text} element will size
-     * itself to wrap its contents, so this option is meaningless for single-line text (for that,
-     * use alignment of the outer container). For multi-line text, however, this will set the
-     * alignment of lines relative to the {@link Text} element bounds. If not defined, defaults to
-     * TEXT_ALIGN_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public TextAlignmentProp getMultilineAlignment() {
-      if (mImpl.hasMultilineAlignment()) {
-        return TextAlignmentProp.fromProto(mImpl.getMultilineAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets how to handle text which overflows the bound of the {@link Text} element. A {@link Text}
-     * element will grow as large as possible inside its parent container (while still respecting
-     * max_lines); if it cannot grow large enough to render all of its text, the text which cannot
-     * fit inside its container will be truncated. If not defined, defaults to
-     * TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
-     */
-    @Nullable
-    public TextOverflowProp getOverflow() {
-      if (mImpl.hasOverflow()) {
-        return TextOverflowProp.fromProto(mImpl.getOverflow());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the explicit height between lines of text. This is equivalent to the vertical distance
-     * between subsequent baselines. If not specified, defaults the font's recommended interline
-     * spacing. Intended for testing purposes only.
-     */
-    @Nullable
-    public SpProp getLineHeight() {
-      if (mImpl.hasLineHeight()) {
-        return SpProp.fromProto(mImpl.getLineHeight());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Text fromProto(@NonNull LayoutElementProto.Text proto) {
-      return new Text(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Text toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setText(mImpl).build();
-    }
-
-    /** Builder for {@link Text}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Text.Builder mImpl = LayoutElementProto.Text.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1976530157);
-
-      public Builder() {}
-
-      /**
-       * Sets the text to render.
-       */
-      @NonNull
-      public Builder setText(@NonNull StringProp text) {
-        mImpl.setText(text.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /** Sets the text to render. */
-      @NonNull
-      public Builder setText(@NonNull String text) {
-        mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-        return this;
-      }
-
-      /**
-       * Sets the style of font to use (size, bold etc). If not specified, defaults to the
-       * platform's default body font.
-       */
-      @NonNull
-      public Builder setFontStyle(@NonNull FontStyle fontStyle) {
-        mImpl.setFontStyle(fontStyle.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the maximum number of lines that can be represented by the {@link Text} element. If
-       * not defined, the {@link Text} element will be treated as a single-line element.
-       */
-      @NonNull
-      public Builder setMaxLines(@NonNull Int32Prop maxLines) {
-        mImpl.setMaxLines(maxLines.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the maximum number of lines that can be represented by the {@link Text} element.
-       * If not defined, the {@link Text} element will be treated as a single-line element.
-       */
-      @NonNull
-      public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
-        mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
-        return this;
-      }
-
-      /**
-       * Sets alignment of the text within its bounds. Note that a {@link Text} element will size
-       * itself to wrap its contents, so this option is meaningless for single-line text (for that,
-       * use alignment of the outer container). For multi-line text, however, this will set the
-       * alignment of lines relative to the {@link Text} element bounds. If not defined, defaults to
-       * TEXT_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setMultilineAlignment(@NonNull TextAlignmentProp multilineAlignment) {
-        mImpl.setMultilineAlignment(multilineAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets alignment of the text within its bounds. Note that a {@link Text} element will
-       * size itself to wrap its contents, so this option is meaningless for single-line text
-       * (for that, use alignment of the outer container). For multi-line text, however, this
-       * will set the alignment of lines relative to the {@link Text} element bounds. If not
-       * defined, defaults to TEXT_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setMultilineAlignment(@TextAlignment int multilineAlignment) {
-        mImpl.setMultilineAlignment(
-                AlignmentProto.TextAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.TextAlignment.forNumber(
-                                        multilineAlignment)));
-        return this;
-      }
-
-      /**
-       * Sets how to handle text which overflows the bound of the {@link Text} element. A {@link
-       * Text} element will grow as large as possible inside its parent container (while still
-       * respecting max_lines); if it cannot grow large enough to render all of its text, the text
-       * which cannot fit inside its container will be truncated. If not defined, defaults to
-       * TEXT_OVERFLOW_TRUNCATE.
-       */
-      @NonNull
-      public Builder setOverflow(@NonNull TextOverflowProp overflow) {
-        mImpl.setOverflow(overflow.toProto());
-        mFingerprint.recordPropertyUpdate(
-            6, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets how to handle text which overflows the bound of the {@link Text} element. A
-       * {@link Text} element will grow as large as possible inside its parent container
-       * (while still respecting max_lines); if it cannot grow large enough to render all of
-       * its text, the text which cannot fit inside its container will be truncated. If not
-       * defined, defaults to TEXT_OVERFLOW_TRUNCATE.
-       */
-      @NonNull
-      public Builder setOverflow(@TextOverflow int overflow) {
-        mImpl.setOverflow(
-                LayoutElementProto.TextOverflowProp.newBuilder()
-                        .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
-        return this;
-      }
-
-      /**
-       * Sets the explicit height between lines of text. This is equivalent to the vertical distance
-       * between subsequent baselines. If not specified, defaults the font's recommended interline
-       * spacing.
-       */
-      @NonNull
-      public Builder setLineHeight(@NonNull SpProp lineHeight) {
-        mImpl.setLineHeight(lineHeight.toProto());
-        mFingerprint.recordPropertyUpdate(
-            7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public Text build() {
-        return new Text(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** An extensible {@code ContentScaleMode} property. */
-  public static final class ContentScaleModeProp {
-    private final LayoutElementProto.ContentScaleModeProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ContentScaleModeProp(
-        LayoutElementProto.ContentScaleModeProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the value. Intended for testing purposes only. */
-    @ContentScaleMode
-    public int getValue() {
-      return mImpl.getValue().getNumber();
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ContentScaleModeProp fromProto(@NonNull LayoutElementProto.ContentScaleModeProp proto) {
-      return new ContentScaleModeProp(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ContentScaleModeProp toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link ContentScaleModeProp} */
-    public static final class Builder {
-      private final LayoutElementProto.ContentScaleModeProp.Builder mImpl =
-          LayoutElementProto.ContentScaleModeProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-893830536);
-
-      public Builder() {}
-
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@ContentScaleMode int value) {
-        mImpl.setValue(LayoutElementProto.ContentScaleMode.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ContentScaleModeProp build() {
-        return new ContentScaleModeProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** Filtering parameters used for images. This can be used to apply a color tint to images. */
-  public static final class ColorFilter {
-    private final LayoutElementProto.ColorFilter mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ColorFilter(LayoutElementProto.ColorFilter impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the tint color to use. If specified, the image will be tinted, using SRC_IN blending
-     * (that is, all color information will be stripped from the target image, and only the alpha
-     * channel will be blended with the requested color).
-     *
-     * <p>Note that only Android image resources can be tinted; Inline images will not be tinted,
-     * and this property will have no effect. Intended for testing purposes only.
-     */
-    @Nullable
-    public ColorProp getTint() {
-      if (mImpl.hasTint()) {
-        return ColorProp.fromProto(mImpl.getTint());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ColorFilter fromProto(@NonNull LayoutElementProto.ColorFilter proto) {
-      return new ColorFilter(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ColorFilter toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link ColorFilter} */
-    public static final class Builder {
-      private final LayoutElementProto.ColorFilter.Builder mImpl =
-          LayoutElementProto.ColorFilter.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(181311326);
-
-      public Builder() {}
-
-      /**
-       * Sets the tint color to use. If specified, the image will be tinted, using SRC_IN blending
-       * (that is, all color information will be stripped from the target image, and only the alpha
-       * channel will be blended with the requested color).
-       *
-       * <p>Note that only Android image resources can be tinted; Inline images will not be tinted,
-       * and this property will have no effect.
-       */
-      @NonNull
-      public Builder setTint(@NonNull ColorProp tint) {
-        mImpl.setTint(tint.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(tint.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ColorFilter build() {
-        return new ColorFilter(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * An image.
-   *
-   * <p>Images used in this element must exist in the resource bundle that corresponds to this
-   * layout. Images must have their dimension specified, and will be rendered at this width and
-   * height, regardless of their native dimension.
-   */
-  public static final class Image implements LayoutElement {
-    private final LayoutElementProto.Image mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Image(LayoutElementProto.Image impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the resource_id of the image to render. This must exist in the supplied resource bundle.
-     * Intended for testing purposes only.
-     */
-    @Nullable
-    public StringProp getResourceId() {
-      if (mImpl.hasResourceId()) {
-        return StringProp.fromProto(mImpl.getResourceId());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the width of this image. If not defined, the image will not be rendered. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public ImageDimension getWidth() {
-      if (mImpl.hasWidth()) {
-        return DimensionBuilders.imageDimensionFromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the height of this image. If not defined, the image will not be rendered. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public ImageDimension getHeight() {
-      if (mImpl.hasHeight()) {
-        return DimensionBuilders.imageDimensionFromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets how to scale the image resource inside the bounds specified by width/height if its size
-     * does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT. Intended for testing
-     * purposes only.
-     */
-    @Nullable
-    public ContentScaleModeProp getContentScaleMode() {
-      if (mImpl.hasContentScaleMode()) {
-        return ContentScaleModeProp.fromProto(mImpl.getContentScaleMode());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets filtering parameters for this image. If not specified, defaults to no filtering.
-     * Intended for testing purposes only.
-     */
-    @Nullable
-    public ColorFilter getColorFilter() {
-      if (mImpl.hasColorFilter()) {
-        return ColorFilter.fromProto(mImpl.getColorFilter());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Image fromProto(@NonNull LayoutElementProto.Image proto) {
-      return new Image(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Image toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setImage(mImpl).build();
-    }
-
-    /** Builder for {@link Image}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Image.Builder mImpl = LayoutElementProto.Image.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-543078544);
-
-      public Builder() {}
-
-      /**
-       * Sets the resource_id of the image to render. This must exist in the supplied resource
-       * bundle.
-       */
-      @NonNull
-      public Builder setResourceId(@NonNull StringProp resourceId) {
-        mImpl.setResourceId(resourceId.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the resource_id of the image to render. This must exist in the supplied resource
-       * bundle.
-       */
-      @NonNull
-      public Builder setResourceId(@NonNull String resourceId) {
-        mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
-        return this;
-      }
-
-      /**
-       * Sets the width of this image. If not defined, the image will not be rendered.
-       */
-      @NonNull
-      public Builder setWidth(@NonNull ImageDimension width) {
-        mImpl.setWidth(width.toImageDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the height of this image. If not defined, the image will not be rendered.
-       */
-      @NonNull
-      public Builder setHeight(@NonNull ImageDimension height) {
-        mImpl.setHeight(height.toImageDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets how to scale the image resource inside the bounds specified by width/height if its
-       * size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
-       */
-      @NonNull
-      public Builder setContentScaleMode(@NonNull ContentScaleModeProp contentScaleMode) {
-        mImpl.setContentScaleMode(contentScaleMode.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(contentScaleMode.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets how to scale the image resource inside the bounds specified by width/height if
-       * its size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
-       */
-      @NonNull
-      public Builder setContentScaleMode(@ContentScaleMode int contentScaleMode) {
-        mImpl.setContentScaleMode(
-                LayoutElementProto.ContentScaleModeProp.newBuilder()
-                        .setValue(
-                                LayoutElementProto.ContentScaleMode.forNumber(
-                                        contentScaleMode)));
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets filtering parameters for this image. If not specified, defaults to no filtering. */
-      @NonNull
-      public Builder setColorFilter(@NonNull ColorFilter colorFilter) {
-        mImpl.setColorFilter(colorFilter.toProto());
-        mFingerprint.recordPropertyUpdate(
-            6, checkNotNull(colorFilter.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public Image build() {
-        return new Image(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A simple spacer, typically used to provide padding between adjacent elements. */
-  public static final class Spacer implements LayoutElement {
-    private final LayoutElementProto.Spacer mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Spacer(LayoutElementProto.Spacer impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the width of this {@link Spacer}. When this is added as the direct child of an {@link
-     * Arc}, this must be specified as an angular dimension, otherwise a linear dimension must be
-     * used. If not defined, defaults to 0. Intended for testing purposes only.
-     */
-    @Nullable
-    public SpacerDimension getWidth() {
-      if (mImpl.hasWidth()) {
-        return DimensionBuilders.spacerDimensionFromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the height of this spacer. If not defined, defaults to 0. Intended for testing purposes
-     * only.
-     */
-    @Nullable
-    public SpacerDimension getHeight() {
-      if (mImpl.hasHeight()) {
-        return DimensionBuilders.spacerDimensionFromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Spacer fromProto(@NonNull LayoutElementProto.Spacer proto) {
-      return new Spacer(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Spacer toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setSpacer(mImpl).build();
-    }
-
-    /** Builder for {@link Spacer}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Spacer.Builder mImpl =
-          LayoutElementProto.Spacer.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1748084575);
-
-      public Builder() {}
-
-      /**
-       * Sets the width of this {@link Spacer}. When this is added as the direct child of an {@link
-       * Arc}, this must be specified as an angular dimension, otherwise a linear dimension must be
-       * used. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setWidth(@NonNull SpacerDimension width) {
-        mImpl.setWidth(width.toSpacerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the height of this spacer. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setHeight(@NonNull SpacerDimension height) {
-        mImpl.setHeight(height.toSpacerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public Spacer build() {
-        return new Spacer(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A container which stacks all of its children on top of one another. This also allows to add a
-   * background color, or to have a border around them with some padding.
-   */
-  public static final class Box implements LayoutElement {
-    private final LayoutElementProto.Box mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Box(LayoutElementProto.Box impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the child element(s) to wrap. Intended for testing purposes only. */
-    @NonNull
-    public List<LayoutElement> getContents() {
-      List<LayoutElement> list = new ArrayList<>();
-      for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
-        list.add(LayoutElementBuilders.layoutElementFromProto(item));
-      }
-      return Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Gets the height of this {@link Box}. If not defined, this will size itself to fit all of its
-     * children (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getHeight() {
-      if (mImpl.hasHeight()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the width of this {@link Box}. If not defined, this will size itself to fit all of its
-     * children (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getWidth() {
-      if (mImpl.hasWidth()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the horizontal alignment of the element inside this {@link Box}. If not defined,
-     * defaults to HORIZONTAL_ALIGN_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public HorizontalAlignmentProp getHorizontalAlignment() {
-      if (mImpl.hasHorizontalAlignment()) {
-        return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the vertical alignment of the element inside this {@link Box}. If not defined, defaults
-     * to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public VerticalAlignmentProp getVerticalAlignment() {
-      if (mImpl.hasVerticalAlignment()) {
-        return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Box fromProto(@NonNull LayoutElementProto.Box proto) {
-      return new Box(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Box toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setBox(mImpl).build();
-    }
-
-    /** Builder for {@link Box}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Box.Builder mImpl = LayoutElementProto.Box.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1881256071);
-
-      public Builder() {}
-
-      /** Adds one item to the child element(s) to wrap. */
-      @NonNull
-      public Builder addContent(@NonNull LayoutElement content) {
-        mImpl.addContents(content.toLayoutElementProto());
-        mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
-        return this;
-      }
-
-      /**
-       * Sets the height of this {@link Box}. If not defined, this will size itself to fit all of
-       * its children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setHeight(@NonNull ContainerDimension height) {
-        mImpl.setHeight(height.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the width of this {@link Box}. If not defined, this will size itself to fit all of its
-       * children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setWidth(@NonNull ContainerDimension width) {
-        mImpl.setWidth(width.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
-       * defaults to HORIZONTAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setHorizontalAlignment(@NonNull HorizontalAlignmentProp horizontalAlignment) {
-        mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
-       * defaults to HORIZONTAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
-        mImpl.setHorizontalAlignment(
-                AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.HorizontalAlignment.forNumber(
-                                        horizontalAlignment)));
-        return this;
-      }
-
-      /**
-       * Sets the vertical alignment of the element inside this {@link Box}. If not defined,
-       * defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
-        mImpl.setVerticalAlignment(verticalAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the vertical alignment of the element inside this {@link Box}. If not defined,
-       * defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
-        mImpl.setVerticalAlignment(
-                AlignmentProto.VerticalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.VerticalAlignment.forNumber(
-                                        verticalAlignment)));
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            6, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public Box build() {
-        return new Box(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A portion of text which can be added to a {@link Span}. Two different {@link SpanText} elements
-   * on the same line will be aligned to the same baseline, regardless of the size of each {@link
-   * SpanText}.
-   */
-  public static final class SpanText implements Span {
-    private final LayoutElementProto.SpanText mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SpanText(LayoutElementProto.SpanText impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the text to render. Intended for testing purposes only. */
-    @Nullable
-    public StringProp getText() {
-      if (mImpl.hasText()) {
-        return StringProp.fromProto(mImpl.getText());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the style of font to use (size, bold etc). If not specified, defaults to the platform's
-     * default body font. Intended for testing purposes only.
-     */
-    @Nullable
-    public FontStyle getFontStyle() {
-      if (mImpl.hasFontStyle()) {
-        return FontStyle.fromProto(mImpl.getFontStyle());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public SpanModifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return SpanModifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static SpanText fromProto(@NonNull LayoutElementProto.SpanText proto) {
-      return new SpanText(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.SpanText toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.Span toSpanProto() {
-      return LayoutElementProto.Span.newBuilder().setText(mImpl).build();
-    }
-
-    /** Builder for {@link SpanText}. */
-    public static final class Builder implements Span.Builder {
-      private final LayoutElementProto.SpanText.Builder mImpl =
-          LayoutElementProto.SpanText.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-221774557);
-
-      public Builder() {}
-
-      /**
-       * Sets the text to render.
-       */
-      @NonNull
-      public Builder setText(@NonNull StringProp text) {
-        mImpl.setText(text.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /** Sets the text to render. */
-      @NonNull
-      public Builder setText(@NonNull String text) {
-        mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-        return this;
-      }
-
-      /**
-       * Sets the style of font to use (size, bold etc). If not specified, defaults to the
-       * platform's default body font.
-       */
-      @NonNull
-      public Builder setFontStyle(@NonNull FontStyle fontStyle) {
-        mImpl.setFontStyle(fontStyle.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull SpanModifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public SpanText build() {
-        return new SpanText(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** An image which can be added to a {@link Span}. */
-  public static final class SpanImage implements Span {
-    private final LayoutElementProto.SpanImage mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SpanImage(LayoutElementProto.SpanImage impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the resource_id of the image to render. This must exist in the supplied resource bundle.
-     * Intended for testing purposes only.
-     */
-    @Nullable
-    public StringProp getResourceId() {
-      if (mImpl.hasResourceId()) {
-        return StringProp.fromProto(mImpl.getResourceId());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the width of this image. If not defined, the image will not be rendered. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public DpProp getWidth() {
-      if (mImpl.hasWidth()) {
-        return DpProp.fromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the height of this image. If not defined, the image will not be rendered. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public DpProp getHeight() {
-      if (mImpl.hasHeight()) {
-        return DpProp.fromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public SpanModifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return SpanModifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets alignment of this image within the line height of the surrounding {@link Spannable}. If
-     * undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM. Intended for testing purposes only.
-     */
-    @Nullable
-    public SpanVerticalAlignmentProp getAlignment() {
-      if (mImpl.hasAlignment()) {
-        return SpanVerticalAlignmentProp.fromProto(mImpl.getAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static SpanImage fromProto(@NonNull LayoutElementProto.SpanImage proto) {
-      return new SpanImage(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.SpanImage toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.Span toSpanProto() {
-      return LayoutElementProto.Span.newBuilder().setImage(mImpl).build();
-    }
-
-    /** Builder for {@link SpanImage}. */
-    public static final class Builder implements Span.Builder {
-      private final LayoutElementProto.SpanImage.Builder mImpl =
-          LayoutElementProto.SpanImage.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(502289772);
-
-      public Builder() {}
-
-      /**
-       * Sets the resource_id of the image to render. This must exist in the supplied resource
-       * bundle.
-       */
-      @NonNull
-      public Builder setResourceId(@NonNull StringProp resourceId) {
-        mImpl.setResourceId(resourceId.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the resource_id of the image to render. This must exist in the supplied resource
-       * bundle.
-       */
-      @NonNull
-      public Builder setResourceId(@NonNull String resourceId) {
-        mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
-        return this;
-      }
-
-      /**
-       * Sets the width of this image. If not defined, the image will not be rendered.
-       */
-      @NonNull
-      public Builder setWidth(@NonNull DpProp width) {
-        mImpl.setWidth(width.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the height of this image. If not defined, the image will not be rendered.
-       */
-      @NonNull
-      public Builder setHeight(@NonNull DpProp height) {
-        mImpl.setHeight(height.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull SpanModifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets alignment of this image within the line height of the surrounding {@link Spannable}.
-       * If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
-       */
-      @NonNull
-      public Builder setAlignment(@NonNull SpanVerticalAlignmentProp alignment) {
-        mImpl.setAlignment(alignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(alignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets alignment of this image within the line height of the surrounding {@link
-       * Spannable}. If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
-       */
-      @NonNull
-      public Builder setAlignment(@SpanVerticalAlignment int alignment) {
-        mImpl.setAlignment(
-                LayoutElementProto.SpanVerticalAlignmentProp.newBuilder()
-                        .setValue(
-                                LayoutElementProto.SpanVerticalAlignment.forNumber(
-                                        alignment)));
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public SpanImage build() {
-        return new SpanImage(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * Interface defining a single {@link Span}. Each {@link Span} forms part of a larger {@link
-   * Spannable} widget. At the moment, the only widgets which can be added to {@link Spannable}
-   * containers are {@link SpanText} and {@link SpanImage} elements.
-   */
-  public interface Span {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    LayoutElementProto.Span toSpanProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link Span} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      Span build();
-    }
-  }
-
-  @NonNull
-  static Span spanFromProto(@NonNull LayoutElementProto.Span proto) {
-    if (proto.hasText()) {
-      return SpanText.fromProto(proto.getText());
-    }
-    if (proto.hasImage()) {
-      return SpanImage.fromProto(proto.getImage());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of Span");
-  }
-
-  /**
-   * A container of {@link Span} elements. Currently, this supports {@link SpanImage} and {@link
-   * SpanText} elements, where each individual {@link Span} can have different styling applied to it
-   * but the resulting text will flow naturally. This allows sections of a paragraph of text to have
-   * different styling applied to it, for example, making one or two words bold or italic.
-   */
-  public static final class Spannable implements LayoutElement {
-    private final LayoutElementProto.Spannable mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Spannable(LayoutElementProto.Spannable impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the {@link Span} elements that form this {@link Spannable}. Intended for testing
-     * purposes only.
-     */
-    @NonNull
-    public List<Span> getSpans() {
-      List<Span> list = new ArrayList<>();
-      for (LayoutElementProto.Span item : mImpl.getSpansList()) {
-        list.add(LayoutElementBuilders.spanFromProto(item));
-      }
-      return Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the maximum number of lines that can be represented by the {@link Spannable} element. If
-     * not defined, the {@link Spannable} element will be treated as a single-line element. Intended
-     * for testing purposes only.
-     */
-    @Nullable
-    public Int32Prop getMaxLines() {
-      if (mImpl.hasMaxLines()) {
-        return Int32Prop.fromProto(mImpl.getMaxLines());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets alignment of the {@link Spannable} content within its bounds. Note that a {@link
-     * Spannable} element will size itself to wrap its contents, so this option is meaningless for
-     * single-line content (for that, use alignment of the outer container). For multi-line content,
-     * however, this will set the alignment of lines relative to the {@link Spannable} element
-     * bounds. If not defined, defaults to TEXT_ALIGN_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public HorizontalAlignmentProp getMultilineAlignment() {
-      if (mImpl.hasMultilineAlignment()) {
-        return HorizontalAlignmentProp.fromProto(mImpl.getMultilineAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets how to handle content which overflows the bound of the {@link Spannable} element. A
-     * {@link Spannable} element will grow as large as possible inside its parent container (while
-     * still respecting max_lines); if it cannot grow large enough to render all of its content, the
-     * content which cannot fit inside its container will be truncated. If not defined, defaults to
-     * TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
-     */
-    @Nullable
-    public TextOverflowProp getOverflow() {
-      if (mImpl.hasOverflow()) {
-        return TextOverflowProp.fromProto(mImpl.getOverflow());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the explicit height between lines of text. This is equivalent to the vertical distance
-     * between subsequent baselines. If not specified, defaults the font's recommended interline
-     * spacing. Intended for testing purposes only.
-     */
-    @Nullable
-    public SpProp getLineHeight() {
-      if (mImpl.hasLineHeight()) {
-        return SpProp.fromProto(mImpl.getLineHeight());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Spannable fromProto(@NonNull LayoutElementProto.Spannable proto) {
-      return new Spannable(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Spannable toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setSpannable(mImpl).build();
-    }
-
-    /** Builder for {@link Spannable}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Spannable.Builder mImpl =
-          LayoutElementProto.Spannable.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1690284372);
-
-      public Builder() {}
-
-      /** Adds one item to the {@link Span} elements that form this {@link Spannable}. */
-      @NonNull
-      public Builder addSpan(@NonNull Span span) {
-        mImpl.addSpans(span.toSpanProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(span.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the maximum number of lines that can be represented by the {@link Spannable} element.
-       * If not defined, the {@link Spannable} element will be treated as a single-line element.
-       */
-      @NonNull
-      public Builder setMaxLines(@NonNull Int32Prop maxLines) {
-        mImpl.setMaxLines(maxLines.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets the maximum number of lines that can be represented by the {@link Spannable}
-       * element. If not defined, the {@link Spannable} element will be treated as a
-       * single-line element.
-       */
-      @NonNull
-      public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
-        mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
-        return this;
-      }
-
-      /**
-       * Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
-       * Spannable} element will size itself to wrap its contents, so this option is meaningless for
-       * single-line content (for that, use alignment of the outer container). For multi-line
-       * content, however, this will set the alignment of lines relative to the {@link Spannable}
-       * element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setMultilineAlignment(@NonNull HorizontalAlignmentProp multilineAlignment) {
-        mImpl.setMultilineAlignment(multilineAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
-       * Spannable} element will size itself to wrap its contents, so this option is
-       * meaningless for single-line content (for that, use alignment of the outer container).
-       * For multi-line content, however, this will set the alignment of lines relative to the
-       * {@link Spannable} element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setMultilineAlignment(@HorizontalAlignment int multilineAlignment) {
-        mImpl.setMultilineAlignment(
-                AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.HorizontalAlignment.forNumber(
-                                        multilineAlignment)));
-        return this;
-      }
-
-      /**
-       * Sets how to handle content which overflows the bound of the {@link Spannable} element. A
-       * {@link Spannable} element will grow as large as possible inside its parent container (while
-       * still respecting max_lines); if it cannot grow large enough to render all of its content,
-       * the content which cannot fit inside its container will be truncated. If not defined,
-       * defaults to TEXT_OVERFLOW_TRUNCATE.
-       */
-      @NonNull
-      public Builder setOverflow(@NonNull TextOverflowProp overflow) {
-        mImpl.setOverflow(overflow.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets how to handle content which overflows the bound of the {@link Spannable}
-       * element. A {@link Spannable} element will grow as large as possible inside its parent
-       * container (while still respecting max_lines); if it cannot grow large enough to
-       * render all of its content, the content which cannot fit inside its container will be
-       * truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
-       */
-      @NonNull
-      public Builder setOverflow(@TextOverflow int overflow) {
-        mImpl.setOverflow(
-                LayoutElementProto.TextOverflowProp.newBuilder()
-                        .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
-        return this;
-      }
-
-      /**
-       * Sets the explicit height between lines of text. This is equivalent to the vertical distance
-       * between subsequent baselines. If not specified, defaults the font's recommended interline
-       * spacing.
-       */
-      @NonNull
-      public Builder setLineHeight(@NonNull SpProp lineHeight) {
-        mImpl.setLineHeight(lineHeight.toProto());
-        mFingerprint.recordPropertyUpdate(
-            7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public Spannable build() {
-        return new Spannable(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A column of elements. Each child element will be laid out vertically, one after another (i.e.
-   * stacking down). This element will size itself to the smallest size required to hold all of its
-   * children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting column
-   * will be 30x60).
-   *
-   * <p>If specified, horizontal_alignment can be used to control the gravity inside the container,
-   * affecting the horizontal placement of children whose width are smaller than the resulting
-   * column width.
-   */
-  public static final class Column implements LayoutElement {
-    private final LayoutElementProto.Column mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Column(LayoutElementProto.Column impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the list of child elements to place inside this {@link Column}. Intended for testing
-     * purposes only.
-     */
-    @NonNull
-    public List<LayoutElement> getContents() {
-      List<LayoutElement> list = new ArrayList<>();
-      for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
-        list.add(LayoutElementBuilders.layoutElementFromProto(item));
-      }
-      return Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Gets the horizontal alignment of elements inside this column, if they are narrower than the
-     * resulting width of the column. If not defined, defaults to HORIZONTAL_ALIGN_CENTER. Intended
-     * for testing purposes only.
-     */
-    @Nullable
-    public HorizontalAlignmentProp getHorizontalAlignment() {
-      if (mImpl.hasHorizontalAlignment()) {
-        return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the width of this column. If not defined, this will size itself to fit all of its
-     * children (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getWidth() {
-      if (mImpl.hasWidth()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the height of this column. If not defined, this will size itself to fit all of its
-     * children (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getHeight() {
-      if (mImpl.hasHeight()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Column fromProto(@NonNull LayoutElementProto.Column proto) {
-      return new Column(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Column toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setColumn(mImpl).build();
-    }
-
-    /** Builder for {@link Column}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Column.Builder mImpl =
-          LayoutElementProto.Column.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1411218529);
-
-      public Builder() {}
-
-      /** Adds one item to the list of child elements to place inside this {@link Column}. */
-      @NonNull
-      public Builder addContent(@NonNull LayoutElement content) {
-        mImpl.addContents(content.toLayoutElementProto());
-        mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
-        return this;
-      }
-
-      /**
-       * Sets the horizontal alignment of elements inside this column, if they are narrower than the
-       * resulting width of the column. If not defined, defaults to HORIZONTAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setHorizontalAlignment(@NonNull HorizontalAlignmentProp horizontalAlignment) {
-        mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the horizontal alignment of elements inside this column, if they are narrower
-       * than the resulting width of the column. If not defined, defaults to
-       * HORIZONTAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
-        mImpl.setHorizontalAlignment(
-                AlignmentProto.HorizontalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.HorizontalAlignment.forNumber(
-                                        horizontalAlignment)));
-        return this;
-      }
-
-      /**
-       * Sets the width of this column. If not defined, this will size itself to fit all of its
-       * children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setWidth(@NonNull ContainerDimension width) {
-        mImpl.setWidth(width.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the height of this column. If not defined, this will size itself to fit all of its
-       * children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setHeight(@NonNull ContainerDimension height) {
-        mImpl.setHeight(height.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public Column build() {
-        return new Column(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A row of elements. Each child will be laid out horizontally, one after another (i.e. stacking
-   * to the right). This element will size itself to the smallest size required to hold all of its
-   * children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting row
-   * will be 60x30).
-   *
-   * <p>If specified, vertical_alignment can be used to control the gravity inside the container,
-   * affecting the vertical placement of children whose width are smaller than the resulting row
-   * height.
-   */
-  public static final class Row implements LayoutElement {
-    private final LayoutElementProto.Row mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Row(LayoutElementProto.Row impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the list of child elements to place inside this {@link Row}. Intended for testing
-     * purposes only.
-     */
-    @NonNull
-    public List<LayoutElement> getContents() {
-      List<LayoutElement> list = new ArrayList<>();
-      for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
-        list.add(LayoutElementBuilders.layoutElementFromProto(item));
-      }
-      return Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Gets the vertical alignment of elements inside this row, if they are narrower than the
-     * resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public VerticalAlignmentProp getVerticalAlignment() {
-      if (mImpl.hasVerticalAlignment()) {
-        return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the width of this row. If not defined, this will size itself to fit all of its children
-     * (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getWidth() {
-      if (mImpl.hasWidth()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the height of this row. If not defined, this will size itself to fit all of its children
-     * (i.e. a WrappedDimension). Intended for testing purposes only.
-     */
-    @Nullable
-    public ContainerDimension getHeight() {
-      if (mImpl.hasHeight()) {
-        return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Row fromProto(@NonNull LayoutElementProto.Row proto) {
-      return new Row(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Row toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setRow(mImpl).build();
-    }
-
-    /** Builder for {@link Row}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Row.Builder mImpl = LayoutElementProto.Row.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1537205448);
-
-      public Builder() {}
-
-      /** Adds one item to the list of child elements to place inside this {@link Row}. */
-      @NonNull
-      public Builder addContent(@NonNull LayoutElement content) {
-        mImpl.addContents(content.toLayoutElementProto());
-        mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
-        return this;
-      }
-
-      /**
-       * Sets the vertical alignment of elements inside this row, if they are narrower than the
-       * resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
-        mImpl.setVerticalAlignment(verticalAlignment.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the vertical alignment of elements inside this row, if they are narrower than
-       * the resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
-        mImpl.setVerticalAlignment(
-                AlignmentProto.VerticalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.VerticalAlignment.forNumber(
-                                        verticalAlignment)));
-        return this;
-      }
-
-      /**
-       * Sets the width of this row. If not defined, this will size itself to fit all of its
-       * children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setWidth(@NonNull ContainerDimension width) {
-        mImpl.setWidth(width.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the height of this row. If not defined, this will size itself to fit all of its
-       * children (i.e. a WrappedDimension).
-       */
-      @NonNull
-      public Builder setHeight(@NonNull ContainerDimension height) {
-        mImpl.setHeight(height.toContainerDimensionProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public Row build() {
-        return new Row(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * An arc container. This container will fill itself to a circle, which fits inside its parent
-   * container, and all of its children will be placed on that circle. The fields anchor_angle and
-   * anchor_type can be used to specify where to draw children within this circle.
-   */
-  public static final class Arc implements LayoutElement {
-    private final LayoutElementProto.Arc mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Arc(LayoutElementProto.Arc impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets contents of this container. Intended for testing purposes only. */
-    @NonNull
-    public List<ArcLayoutElement> getContents() {
-      List<ArcLayoutElement> list = new ArrayList<>();
-      for (LayoutElementProto.ArcLayoutElement item : mImpl.getContentsList()) {
-        list.add(LayoutElementBuilders.arcLayoutElementFromProto(item));
-      }
-      return Collections.unmodifiableList(list);
-    }
-
-    /**
-     * Gets the angle for the anchor, used with anchor_type to determine where to draw children.
-     * Note that 0 degrees is the 12 o clock position on a device, and the angle sweeps clockwise.
-     * If not defined, defaults to 0 degrees.
-     *
-     * <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees will sweep
-     * anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values >360 will be be
-     * placed at X mod 360 degrees. Intended for testing purposes only.
-     */
-    @Nullable
-    public DegreesProp getAnchorAngle() {
-      if (mImpl.hasAnchorAngle()) {
-        return DegreesProp.fromProto(mImpl.getAnchorAngle());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets how to align the contents of this container relative to anchor_angle. If not defined,
-     * defaults to ARC_ANCHOR_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public ArcAnchorTypeProp getAnchorType() {
-      if (mImpl.hasAnchorType()) {
-        return ArcAnchorTypeProp.fromProto(mImpl.getAnchorType());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is larger
-     * than the thickness of the element being drawn, this controls whether the element should be
-     * drawn towards the inner or outer edge of the arc, or drawn in the center. If not defined,
-     * defaults to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
-     */
-    @Nullable
-    public VerticalAlignmentProp getVerticalAlign() {
-      if (mImpl.hasVerticalAlign()) {
-        return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlign());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Modifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return Modifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Arc fromProto(@NonNull LayoutElementProto.Arc proto) {
-      return new Arc(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.Arc toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.LayoutElement toLayoutElementProto() {
-      return LayoutElementProto.LayoutElement.newBuilder().setArc(mImpl).build();
-    }
-
-    /** Builder for {@link Arc}. */
-    public static final class Builder implements LayoutElement.Builder {
-      private final LayoutElementProto.Arc.Builder mImpl = LayoutElementProto.Arc.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(299028337);
-
-      public Builder() {}
-
-      /** Adds one item to contents of this container. */
-      @NonNull
-      public Builder addContent(@NonNull ArcLayoutElement content) {
-        mImpl.addContents(content.toArcLayoutElementProto());
-        mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
-        return this;
-      }
-
-      /**
-       * Sets the angle for the anchor, used with anchor_type to determine where to draw children.
-       * Note that 0 degrees is the 12 o clock position on a device, and the angle sweeps clockwise.
-       * If not defined, defaults to 0 degrees.
-       *
-       * <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees will
-       * sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values >360 will
-       * be be placed at X mod 360 degrees.
-       */
-      @NonNull
-      public Builder setAnchorAngle(@NonNull DegreesProp anchorAngle) {
-        mImpl.setAnchorAngle(anchorAngle.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(anchorAngle.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets how to align the contents of this container relative to anchor_angle. If not defined,
-       * defaults to ARC_ANCHOR_CENTER.
-       */
-      @NonNull
-      public Builder setAnchorType(@NonNull ArcAnchorTypeProp anchorType) {
-        mImpl.setAnchorType(anchorType.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(anchorType.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets how to align the contents of this container relative to anchor_angle. If not
-       * defined, defaults to ARC_ANCHOR_CENTER.
-       */
-      @NonNull
-      public Builder setAnchorType(@ArcAnchorType int anchorType) {
-        mImpl.setAnchorType(
-                AlignmentProto.ArcAnchorTypeProp.newBuilder()
-                        .setValue(AlignmentProto.ArcAnchorType.forNumber(anchorType)));
-        return this;
-      }
-
-      /**
-       * Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
-       * larger than the thickness of the element being drawn, this controls whether the element
-       * should be drawn towards the inner or outer edge of the arc, or drawn in the center. If not
-       * defined, defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlign(@NonNull VerticalAlignmentProp verticalAlign) {
-        mImpl.setVerticalAlign(verticalAlign.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(verticalAlign.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-      /**
-       * Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
-       * larger than the thickness of the element being drawn, this controls whether the
-       * element should be drawn towards the inner or outer edge of the arc, or drawn in the
-       * center. If not defined, defaults to VERTICAL_ALIGN_CENTER.
-       */
-      @NonNull
-      public Builder setVerticalAlign(@VerticalAlignment int verticalAlign) {
-        mImpl.setVerticalAlign(
-                AlignmentProto.VerticalAlignmentProp.newBuilder()
-                        .setValue(
-                                AlignmentProto.VerticalAlignment.forNumber(
-                                        verticalAlign)));
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull Modifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public Arc build() {
-        return new Arc(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A text element that can be used in an {@link Arc}. */
-  public static final class ArcText implements ArcLayoutElement {
-    private final LayoutElementProto.ArcText mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ArcText(LayoutElementProto.ArcText impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the text to render. Intended for testing purposes only. */
-    @Nullable
-    public StringProp getText() {
-      if (mImpl.hasText()) {
-        return StringProp.fromProto(mImpl.getText());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the style of font to use (size, bold etc). If not specified, defaults to the platform's
-     * default body font. Intended for testing purposes only.
-     */
-    @Nullable
-    public FontStyle getFontStyle() {
-      if (mImpl.hasFontStyle()) {
-        return FontStyle.fromProto(mImpl.getFontStyle());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public ArcModifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return ArcModifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ArcText fromProto(@NonNull LayoutElementProto.ArcText proto) {
-      return new ArcText(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ArcText toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
-      return LayoutElementProto.ArcLayoutElement.newBuilder().setText(mImpl).build();
-    }
-
-    /** Builder for {@link ArcText}. */
-    public static final class Builder implements ArcLayoutElement.Builder {
-      private final LayoutElementProto.ArcText.Builder mImpl =
-          LayoutElementProto.ArcText.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(434391973);
-
-      public Builder() {}
-
-      /**
-       * Sets the text to render.
-       */
-      @NonNull
-      public Builder setText(@NonNull StringProp text) {
-        mImpl.setText(text.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets the text to render. */
-      @NonNull
-      public Builder setText(@NonNull String text) {
-        mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
-        return this;
-      }
-
-      /**
-       * Sets the style of font to use (size, bold etc). If not specified, defaults to the
-       * platform's default body font.
-       */
-      @NonNull
-      public Builder setFontStyle(@NonNull FontStyle fontStyle) {
-        mImpl.setFontStyle(fontStyle.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull ArcModifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public ArcText build() {
-        return new ArcText(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A line that can be used in an {@link Arc} and renders as a round progress bar. */
-  public static final class ArcLine implements ArcLayoutElement {
-    private final LayoutElementProto.ArcLine mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ArcLine(LayoutElementProto.ArcLine impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the length of this line, in degrees. If not defined, defaults to 0. Intended for testing
-     * purposes only.
-     */
-    @Nullable
-    public DegreesProp getLength() {
-      if (mImpl.hasLength()) {
-        return DegreesProp.fromProto(mImpl.getLength());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the thickness of this line. If not defined, defaults to 0. Intended for testing purposes
-     * only.
-     */
-    @Nullable
-    public DpProp getThickness() {
-      if (mImpl.hasThickness()) {
-        return DpProp.fromProto(mImpl.getThickness());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the color of this line. Intended for testing purposes only. */
-    @Nullable
-    public ColorProp getColor() {
-      if (mImpl.hasColor()) {
-        return ColorProp.fromProto(mImpl.getColor());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public ArcModifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return ArcModifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ArcLine fromProto(@NonNull LayoutElementProto.ArcLine proto) {
-      return new ArcLine(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ArcLine toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
-      return LayoutElementProto.ArcLayoutElement.newBuilder().setLine(mImpl).build();
-    }
-
-    /** Builder for {@link ArcLine}. */
-    public static final class Builder implements ArcLayoutElement.Builder {
-      private final LayoutElementProto.ArcLine.Builder mImpl =
-          LayoutElementProto.ArcLine.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1371793535);
-
-      public Builder() {}
-
-      /**
-       * Sets the length of this line, in degrees. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setLength(@NonNull DegreesProp length) {
-        mImpl.setLength(length.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the thickness of this line. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setThickness(@NonNull DpProp thickness) {
-        mImpl.setThickness(thickness.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the color of this line.
-       */
-      @NonNull
-      public Builder setColor(@NonNull ColorProp color) {
-        mImpl.setColor(color.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull ArcModifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-
-      @Override
-      @NonNull
-      public ArcLine build() {
-        return new ArcLine(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A simple spacer used to provide padding between adjacent elements in an {@link Arc}. */
-  public static final class ArcSpacer implements ArcLayoutElement {
-    private final LayoutElementProto.ArcSpacer mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ArcSpacer(LayoutElementProto.ArcSpacer impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the length of this spacer, in degrees. If not defined, defaults to 0. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public DegreesProp getLength() {
-      if (mImpl.hasLength()) {
-        return DegreesProp.fromProto(mImpl.getLength());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the thickness of this spacer, in DP. If not defined, defaults to 0. Intended for testing
-     * purposes only.
-     */
-    @Nullable
-    public DpProp getThickness() {
-      if (mImpl.hasThickness()) {
-        return DpProp.fromProto(mImpl.getThickness());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public ArcModifiers getModifiers() {
-      if (mImpl.hasModifiers()) {
-        return ArcModifiers.fromProto(mImpl.getModifiers());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ArcSpacer fromProto(@NonNull LayoutElementProto.ArcSpacer proto) {
-      return new ArcSpacer(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ArcSpacer toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
-      return LayoutElementProto.ArcLayoutElement.newBuilder().setSpacer(mImpl).build();
-    }
-
-    /** Builder for {@link ArcSpacer}. */
-    public static final class Builder implements ArcLayoutElement.Builder {
-      private final LayoutElementProto.ArcSpacer.Builder mImpl =
-          LayoutElementProto.ArcSpacer.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-179760535);
-
-      public Builder() {}
-
-      /**
-       * Sets the length of this spacer, in degrees. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setLength(@NonNull DegreesProp length) {
-        mImpl.setLength(length.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the thickness of this spacer, in DP. If not defined, defaults to 0.
-       */
-      @NonNull
-      public Builder setThickness(@NonNull DpProp thickness) {
-        mImpl.setThickness(thickness.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
-      @NonNull
-      public Builder setModifiers(@NonNull ArcModifiers modifiers) {
-        mImpl.setModifiers(modifiers.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public ArcSpacer build() {
-        return new ArcSpacer(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A container that allows a standard {@link LayoutElement} to be added to an {@link Arc}. */
-  public static final class ArcAdapter implements ArcLayoutElement {
-    private final LayoutElementProto.ArcAdapter mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ArcAdapter(LayoutElementProto.ArcAdapter impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the element to adapt to an {@link Arc}. Intended for testing purposes only. */
-    @Nullable
-    public LayoutElement getContent() {
-      if (mImpl.hasContent()) {
-        return LayoutElementBuilders.layoutElementFromProto(mImpl.getContent());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets whether this adapter's contents should be rotated, according to its position in the arc
-     * or not. As an example, assume that an {@link Image} has been added to the arc, and ends up at
-     * the 3 o clock position. If rotate_contents = true, the image will be placed at the 3 o clock
-     * position, and will be rotated clockwise through 90 degrees. If rotate_contents = false, the
-     * image will be placed at the 3 o clock position, but itself will not be rotated. If not
-     * defined, defaults to false. Intended for testing purposes only.
-     */
-    @Nullable
-    public BoolProp getRotateContents() {
-      if (mImpl.hasRotateContents()) {
-        return BoolProp.fromProto(mImpl.getRotateContents());
-      } else {
-        return null;
-      }
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ArcAdapter fromProto(@NonNull LayoutElementProto.ArcAdapter proto) {
-      return new ArcAdapter(proto, null);
-    }
-
-    @NonNull
-    LayoutElementProto.ArcAdapter toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
-      return LayoutElementProto.ArcLayoutElement.newBuilder().setAdapter(mImpl).build();
-    }
-
-    /** Builder for {@link ArcAdapter}. */
-    public static final class Builder implements ArcLayoutElement.Builder {
-      private final LayoutElementProto.ArcAdapter.Builder mImpl =
-          LayoutElementProto.ArcAdapter.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1696473935);
-
-      public Builder() {}
-
-      /** Sets the element to adapt to an {@link Arc}. */
-      @NonNull
-      public Builder setContent(@NonNull LayoutElement content) {
-        mImpl.setContent(content.toLayoutElementProto());
-        mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
-        return this;
-      }
-
-      /**
-       * Sets whether this adapter's contents should be rotated, according to its position in the
-       * arc or not. As an example, assume that an {@link Image} has been added to the arc, and ends
-       * up at the 3 o clock position. If rotate_contents = true, the image will be placed at the 3
-       * o clock position, and will be rotated clockwise through 90 degrees. If rotate_contents =
-       * false, the image will be placed at the 3 o clock position, but itself will not be rotated.
-       * If not defined, defaults to false.
-       */
-      @NonNull
-      public Builder setRotateContents(@NonNull BoolProp rotateContents) {
-        mImpl.setRotateContents(rotateContents.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(rotateContents.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets whether this adapter's contents should be rotated, according to its position in
-       * the arc or not. As an example, assume that an {@link Image} has been added to the
-       * arc, and ends up at the 3 o clock position. If rotate_contents = true, the image will
-       * be placed at the 3 o clock position, and will be rotated clockwise through 90
-       * degrees. If rotate_contents = false, the image will be placed at the 3 o clock
-       * position, but itself will not be rotated. If not defined, defaults to false.
-       */
-      @SuppressLint("MissingGetterMatchingBuilder")
-      @NonNull
-      public Builder setRotateContents(boolean rotateContents) {
-        mImpl.setRotateContents(TypesProto.BoolProp.newBuilder().setValue(rotateContents));
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public ArcAdapter build() {
-        return new ArcAdapter(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * Interface defining the root of all layout elements. This exists to act as a holder for all of
-   * the actual layout elements above.
-   */
-  public interface LayoutElement {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    LayoutElementProto.LayoutElement toLayoutElementProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link LayoutElement} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      LayoutElement build();
-    }
-  }
-
-  @NonNull
-  static LayoutElement layoutElementFromProto(@NonNull LayoutElementProto.LayoutElement proto) {
-    if (proto.hasColumn()) {
-      return Column.fromProto(proto.getColumn());
-    }
-    if (proto.hasRow()) {
-      return Row.fromProto(proto.getRow());
-    }
-    if (proto.hasBox()) {
-      return Box.fromProto(proto.getBox());
-    }
-    if (proto.hasSpacer()) {
-      return Spacer.fromProto(proto.getSpacer());
-    }
-    if (proto.hasText()) {
-      return Text.fromProto(proto.getText());
-    }
-    if (proto.hasImage()) {
-      return Image.fromProto(proto.getImage());
-    }
-    if (proto.hasArc()) {
-      return Arc.fromProto(proto.getArc());
-    }
-    if (proto.hasSpannable()) {
-      return Spannable.fromProto(proto.getSpannable());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of LayoutElement");
-  }
-
-  /**
-   * Interface defining the root of all elements that can be used in an {@link Arc}. This exists to
-   * act as a holder for all of the actual arc layout elements above.
-   */
-  public interface ArcLayoutElement {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    LayoutElementProto.ArcLayoutElement toArcLayoutElementProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /** Builder to create {@link ArcLayoutElement} objects. */
-    @SuppressLint("StaticFinalBuilder")
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      ArcLayoutElement build();
-    }
-  }
-
-  @NonNull
-  static ArcLayoutElement arcLayoutElementFromProto(
-      @NonNull LayoutElementProto.ArcLayoutElement proto) {
-    if (proto.hasText()) {
-      return ArcText.fromProto(proto.getText());
-    }
-    if (proto.hasLine()) {
-      return ArcLine.fromProto(proto.getLine());
-    }
-    if (proto.hasSpacer()) {
-      return ArcSpacer.fromProto(proto.getSpacer());
-    }
-    if (proto.hasAdapter()) {
-      return ArcAdapter.fromProto(proto.getAdapter());
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of ArcLayoutElement");
-  }
-
-  /** A complete layout. */
-  public static final class Layout {
-    private final LayoutElementProto.Layout mImpl;
-
-    private Layout(LayoutElementProto.Layout impl) {
-      this.mImpl = impl;
-    }
-
-    /** Gets the root element in the layout. Intended for testing purposes only. */
-    @Nullable
-    public LayoutElement getRoot() {
-      if (mImpl.hasRoot()) {
-        return LayoutElementBuilders.layoutElementFromProto(mImpl.getRoot());
-      } else {
-        return null;
-      }
-    }
-
-    /** Creates a {@link Layout} object containing the given layout element. */
-    @NonNull
-    public static Layout fromLayoutElement(@NonNull LayoutElement layoutElement) {
-      return new Builder().setRoot(layoutElement).build();
-    }
-
-    /** Converts to byte array representation. */
-    @NonNull
-    @ProtoLayoutExperimental
-    public byte[] toByteArray() {
-      return mImpl.toByteArray();
-    }
-
-    /** Converts from byte array representation. */
-    @SuppressWarnings("ProtoParseWithRegistry")
-    @Nullable
-    @ProtoLayoutExperimental
-    public static Layout fromByteArray(@NonNull byte[] byteArray) {
-      try {
-        return fromProto(LayoutElementProto.Layout.parseFrom(byteArray));
-      } catch (InvalidProtocolBufferException e) {
-        return null;
-      }
-    }
-
-    @NonNull
-    static Layout fromProto(@NonNull LayoutElementProto.Layout proto) {
-      return new Layout(proto);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public LayoutElementProto.Layout toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Layout} */
-    public static final class Builder {
-      private final LayoutElementProto.Layout.Builder mImpl =
-          LayoutElementProto.Layout.newBuilder();
-
-      public Builder() {}
-
-      /** Sets the root element in the layout. */
-      @NonNull
-      public Builder setRoot(@NonNull LayoutElement root) {
-        mImpl.setRoot(root.toLayoutElementProto());
-        @Nullable Fingerprint fingerprint = root.getFingerprint();
-        if (fingerprint != null) {
-          mImpl.setFingerprint(
-              TreeFingerprint.newBuilder().setRoot(fingerprintToProto(fingerprint)));
+        FontVariantProp(
+                LayoutElementProto.FontVariantProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
         }
-        return this;
-      }
 
-      private static FingerprintProto.NodeFingerprint fingerprintToProto(Fingerprint fingerprint) {
-        FingerprintProto.NodeFingerprint.Builder builder =
-            FingerprintProto.NodeFingerprint.newBuilder();
-        if (fingerprint.selfTypeValue() != 0) {
-          builder.setSelfTypeValue(fingerprint.selfTypeValue());
+        /** Gets the value. Intended for testing purposes only. */
+        @FontVariant
+        public int getValue() {
+            return mImpl.getValue().getNumber();
         }
-        if (fingerprint.selfPropsValue() != 0) {
-          builder.setSelfPropsValue(fingerprint.selfPropsValue());
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
         }
-        if (fingerprint.childNodesValue() != 0) {
-          builder.setChildNodesValue(fingerprint.childNodesValue());
+
+        @NonNull
+        static FontVariantProp fromProto(@NonNull LayoutElementProto.FontVariantProp proto) {
+            return new FontVariantProp(proto, null);
         }
-        for (Fingerprint childNode : fingerprint.childNodes()) {
-          builder.addChildNodes(fingerprintToProto(childNode));
+
+        @NonNull
+        LayoutElementProto.FontVariantProp toProto() {
+            return mImpl;
         }
-        return builder.build();
-      }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Layout build() {
-        return Layout.fromProto(mImpl.build());
-      }
-    }
-  }
+        /** Builder for {@link FontVariantProp} */
+        public static final class Builder {
+            private final LayoutElementProto.FontVariantProp.Builder mImpl =
+                    LayoutElementProto.FontVariantProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-293831500);
 
-  /**
-   * The horizontal alignment of an element within its container.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({
-    HORIZONTAL_ALIGN_UNDEFINED,
-    HORIZONTAL_ALIGN_LEFT,
-    HORIZONTAL_ALIGN_CENTER,
-    HORIZONTAL_ALIGN_RIGHT,
-    HORIZONTAL_ALIGN_START,
-    HORIZONTAL_ALIGN_END
-  })
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface HorizontalAlignment {}
+            public Builder() {}
 
-  /** Horizontal alignment is undefined. */
-  public static final int HORIZONTAL_ALIGN_UNDEFINED = 0;
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@FontVariant int value) {
+                mImpl.setValue(LayoutElementProto.FontVariant.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
 
-  /** Horizontally align to the left. */
-  public static final int HORIZONTAL_ALIGN_LEFT = 1;
-
-  /** Horizontally align to center. */
-  public static final int HORIZONTAL_ALIGN_CENTER = 2;
-
-  /** Horizontally align to the right. */
-  public static final int HORIZONTAL_ALIGN_RIGHT = 3;
-
-  /** Horizontally align to the content start (left in LTR layouts, right in RTL layouts). */
-  public static final int HORIZONTAL_ALIGN_START = 4;
-
-  /** Horizontally align to the content end (right in LTR layouts, left in RTL layouts). */
-  public static final int HORIZONTAL_ALIGN_END = 5;
-
-  /**
-   * The vertical alignment of an element within its container.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({
-    VERTICAL_ALIGN_UNDEFINED,
-    VERTICAL_ALIGN_TOP,
-    VERTICAL_ALIGN_CENTER,
-    VERTICAL_ALIGN_BOTTOM
-  })
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface VerticalAlignment {}
-
-  /** Vertical alignment is undefined. */
-  public static final int VERTICAL_ALIGN_UNDEFINED = 0;
-
-  /** Vertically align to the top. */
-  public static final int VERTICAL_ALIGN_TOP = 1;
-
-  /** Vertically align to center. */
-  public static final int VERTICAL_ALIGN_CENTER = 2;
-
-  /** Vertically align to the bottom. */
-  public static final int VERTICAL_ALIGN_BOTTOM = 3;
-
-  /**
-   * Alignment of a text element.
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({TEXT_ALIGN_UNDEFINED, TEXT_ALIGN_START, TEXT_ALIGN_CENTER, TEXT_ALIGN_END})
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface TextAlignment {}
-
-  /** Alignment is undefined. */
-  public static final int TEXT_ALIGN_UNDEFINED = 0;
-
-  /**
-   * Align to the "start" of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element
-   * (left in LTR layouts, right in RTL layouts).
-   */
-  public static final int TEXT_ALIGN_START = 1;
-
-  /** Align to the center of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element. */
-  public static final int TEXT_ALIGN_CENTER = 2;
-
-  /**
-   * Align to the "end" of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element (right
-   * in LTR layouts, left in RTL layouts).
-   */
-  public static final int TEXT_ALIGN_END = 3;
-
-  /**
-   * The anchor position of an {@link androidx.wear.tiles.LayoutElementBuilders.Arc}'s elements.
-   * This is used to specify how elements added to an {@link
-   * androidx.wear.tiles.LayoutElementBuilders.Arc} should be laid out with respect to anchor_angle.
-   *
-   * <p>As an example, assume that the following diagrams are wrapped to an arc, and each represents
-   * an {@link androidx.wear.tiles.LayoutElementBuilders.Arc} element containing a single {@link
-   * androidx.wear.tiles.LayoutElementBuilders.Text} element. The {@link
-   * androidx.wear.tiles.LayoutElementBuilders.Text} element's anchor_angle is "0" for all cases.
-   *
-   * <pre>{@code
-   * ARC_ANCHOR_START:
-   * -180                                0                                    180
-   *                                     Hello World!
-   *
-   *
-   * ARC_ANCHOR_CENTER:
-   * -180                                0                                    180
-   *                                Hello World!
-   *
-   * ARC_ANCHOR_END:
-   * -180                                0                                    180
-   *                          Hello World!
-   *
-   * }</pre>
-   *
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({ARC_ANCHOR_UNDEFINED, ARC_ANCHOR_START, ARC_ANCHOR_CENTER, ARC_ANCHOR_END})
-  @Retention(RetentionPolicy.SOURCE)
-  public @interface ArcAnchorType {}
-
-  /** Anchor position is undefined. */
-  public static final int ARC_ANCHOR_UNDEFINED = 0;
-
-  /**
-   * Anchor at the start of the elements. This will cause elements added to an arc to begin at the
-   * given anchor_angle, and sweep around to the right.
-   */
-  public static final int ARC_ANCHOR_START = 1;
-
-  /**
-   * Anchor at the center of the elements. This will cause the center of the whole set of elements
-   * added to an arc to be pinned at the given anchor_angle.
-   */
-  public static final int ARC_ANCHOR_CENTER = 2;
-
-  /**
-   * Anchor at the end of the elements. This will cause the set of elements inside the arc to end at
-   * the specified anchor_angle, i.e. all elements should be to the left of anchor_angle.
-   */
-  public static final int ARC_ANCHOR_END = 3;
-
-  /** An extensible {@code HorizontalAlignment} property. */
-  public static final class HorizontalAlignmentProp {
-    private final AlignmentProto.HorizontalAlignmentProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    HorizontalAlignmentProp(
-        AlignmentProto.HorizontalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public FontVariantProp build() {
+                return new FontVariantProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    /** Gets the value. Intended for testing purposes only. */
-    @HorizontalAlignment
-    public int getValue() {
-      return mImpl.getValue().getNumber();
+    /** An extensible {@code SpanVerticalAlignment} property. */
+    public static final class SpanVerticalAlignmentProp {
+        private final LayoutElementProto.SpanVerticalAlignmentProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SpanVerticalAlignmentProp(
+                LayoutElementProto.SpanVerticalAlignmentProp impl,
+                @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @SpanVerticalAlignment
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static SpanVerticalAlignmentProp fromProto(
+                @NonNull LayoutElementProto.SpanVerticalAlignmentProp proto) {
+            return new SpanVerticalAlignmentProp(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.SpanVerticalAlignmentProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link SpanVerticalAlignmentProp} */
+        public static final class Builder {
+            private final LayoutElementProto.SpanVerticalAlignmentProp.Builder mImpl =
+                    LayoutElementProto.SpanVerticalAlignmentProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1008812329);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@SpanVerticalAlignment int value) {
+                mImpl.setValue(LayoutElementProto.SpanVerticalAlignment.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public SpanVerticalAlignmentProp build() {
+                return new SpanVerticalAlignmentProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** The styling of a font (e.g. font size, and metrics). */
+    public static final class FontStyle {
+        private final LayoutElementProto.FontStyle mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        FontStyle(LayoutElementProto.FontStyle impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the size of the font, in scaled pixels (sp). If not specified, defaults to the size
+         * of the system's "body" font. Intended for testing purposes only.
+         */
+        @Nullable
+        public SpProp getSize() {
+            if (mImpl.hasSize()) {
+                return SpProp.fromProto(mImpl.getSize());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets whether the text should be rendered in a italic typeface. If not specified, defaults
+         * to "false". Intended for testing purposes only.
+         */
+        @Nullable
+        public BoolProp getItalic() {
+            if (mImpl.hasItalic()) {
+                return BoolProp.fromProto(mImpl.getItalic());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets whether the text should be rendered with an underline. If not specified, defaults to
+         * "false". Intended for testing purposes only.
+         */
+        @Nullable
+        public BoolProp getUnderline() {
+            if (mImpl.hasUnderline()) {
+                return BoolProp.fromProto(mImpl.getUnderline());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the text color. If not defined, defaults to white. Intended for testing purposes
+         * only.
+         */
+        @Nullable
+        public ColorProp getColor() {
+            if (mImpl.hasColor()) {
+                return ColorProp.fromProto(mImpl.getColor());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the weight of the font. If the provided value is not supported on a platform, the
+         * nearest supported value will be used. If not defined, or when set to an invalid value,
+         * defaults to "normal". Intended for testing purposes only.
+         */
+        @Nullable
+        public FontWeightProp getWeight() {
+            if (mImpl.hasWeight()) {
+                return FontWeightProp.fromProto(mImpl.getWeight());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the text letter-spacing. Positive numbers increase the space between letters while
+         * negative numbers tighten the space. If not specified, defaults to 0. Intended for testing
+         * purposes only.
+         */
+        @Nullable
+        public EmProp getLetterSpacing() {
+            if (mImpl.hasLetterSpacing()) {
+                return EmProp.fromProto(mImpl.getLetterSpacing());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the variant of a font. Some renderers may use different fonts for title and body
+         * text, which can be selected using this field. If not specified, defaults to "body".
+         * Intended for testing purposes only.
+         */
+        @ProtoLayoutExperimental
+        @Nullable
+        public FontVariantProp getVariant() {
+            if (mImpl.hasVariant()) {
+                return FontVariantProp.fromProto(mImpl.getVariant());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static FontStyle fromProto(@NonNull LayoutElementProto.FontStyle proto) {
+            return new FontStyle(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.FontStyle toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link FontStyle} */
+        public static final class Builder {
+            private final LayoutElementProto.FontStyle.Builder mImpl =
+                    LayoutElementProto.FontStyle.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(181264306);
+
+            public Builder() {}
+
+            /**
+             * Sets the size of the font, in scaled pixels (sp). If not specified, defaults to the
+             * size of the system's "body" font.
+             */
+            @NonNull
+            public Builder setSize(@NonNull SpProp size) {
+                mImpl.setSize(size.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(size.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets whether the text should be rendered in a italic typeface. If not specified,
+             * defaults to "false".
+             */
+            @NonNull
+            public Builder setItalic(@NonNull BoolProp italic) {
+                mImpl.setItalic(italic.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(italic.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets whether the text should be rendered in a italic typeface. If not specified,
+             * defaults to "false".
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setItalic(boolean italic) {
+                mImpl.setItalic(TypesProto.BoolProp.newBuilder().setValue(italic));
+                return this;
+            }
+            /**
+             * Sets whether the text should be rendered with an underline. If not specified,
+             * defaults to "false".
+             */
+            @NonNull
+            public Builder setUnderline(@NonNull BoolProp underline) {
+                mImpl.setUnderline(underline.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(underline.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets whether the text should be rendered with an underline. If not specified,
+             * defaults to "false".
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setUnderline(boolean underline) {
+                mImpl.setUnderline(TypesProto.BoolProp.newBuilder().setValue(underline));
+                return this;
+            }
+
+            /** Sets the text color. If not defined, defaults to white. */
+            @NonNull
+            public Builder setColor(@NonNull ColorProp color) {
+                mImpl.setColor(color.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the weight of the font. If the provided value is not supported on a platform,
+             * the nearest supported value will be used. If not defined, or when set to an invalid
+             * value, defaults to "normal".
+             */
+            @NonNull
+            public Builder setWeight(@NonNull FontWeightProp weight) {
+                mImpl.setWeight(weight.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(weight.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the weight of the font. If the provided value is not supported on a platform,
+             * the nearest supported value will be used. If not defined, or when set to an invalid
+             * value, defaults to "normal".
+             */
+            @NonNull
+            public Builder setWeight(@FontWeight int weight) {
+                mImpl.setWeight(
+                        LayoutElementProto.FontWeightProp.newBuilder()
+                                .setValue(LayoutElementProto.FontWeight.forNumber(weight)));
+                return this;
+            }
+
+            /**
+             * Sets the text letter-spacing. Positive numbers increase the space between letters
+             * while negative numbers tighten the space. If not specified, defaults to 0.
+             */
+            @NonNull
+            public Builder setLetterSpacing(@NonNull EmProp letterSpacing) {
+                mImpl.setLetterSpacing(letterSpacing.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(letterSpacing.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the variant of a font. Some renderers may use different fonts for title and body
+             * text, which can be selected using this field. If not specified, defaults to "body".
+             */
+            @ProtoLayoutExperimental
+            @NonNull
+            public Builder setVariant(@NonNull FontVariantProp variant) {
+                mImpl.setVariant(variant.toProto());
+                return this;
+            }
+            /**
+             * Sets the variant of a font. Some renderers may use different fonts for title and body
+             * text, which can be selected using this field. If not specified, defaults to "body".
+             */
+            @ProtoLayoutExperimental
+            @NonNull
+            public Builder setVariant(@FontVariant int variant) {
+                mImpl.setVariant(
+                        LayoutElementProto.FontVariantProp.newBuilder()
+                                .setValue(LayoutElementProto.FontVariant.forNumber(variant)));
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public FontStyle build() {
+                return new FontStyle(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** An extensible {@code TextOverflow} property. */
+    public static final class TextOverflowProp {
+        private final LayoutElementProto.TextOverflowProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        TextOverflowProp(
+                LayoutElementProto.TextOverflowProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @TextOverflow
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static TextOverflowProp fromProto(@NonNull LayoutElementProto.TextOverflowProp proto) {
+            return new TextOverflowProp(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.TextOverflowProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link TextOverflowProp} */
+        public static final class Builder {
+            private final LayoutElementProto.TextOverflowProp.Builder mImpl =
+                    LayoutElementProto.TextOverflowProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1183432233);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@TextOverflow int value) {
+                mImpl.setValue(LayoutElementProto.TextOverflow.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public TextOverflowProp build() {
+                return new TextOverflowProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A text string. */
+    public static final class Text implements LayoutElement {
+        private final LayoutElementProto.Text mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Text(LayoutElementProto.Text impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the text to render. Intended for testing purposes only. */
+        @Nullable
+        public StringProp getText() {
+            if (mImpl.hasText()) {
+                return StringProp.fromProto(mImpl.getText());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the style of font to use (size, bold etc). If not specified, defaults to the
+         * platform's default body font. Intended for testing purposes only.
+         */
+        @Nullable
+        public FontStyle getFontStyle() {
+            if (mImpl.hasFontStyle()) {
+                return FontStyle.fromProto(mImpl.getFontStyle());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the maximum number of lines that can be represented by the {@link Text} element. If
+         * not defined, the {@link Text} element will be treated as a single-line element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Int32Prop getMaxLines() {
+            if (mImpl.hasMaxLines()) {
+                return Int32Prop.fromProto(mImpl.getMaxLines());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets alignment of the text within its bounds. Note that a {@link Text} element will size
+         * itself to wrap its contents, so this option is meaningless for single-line text (for
+         * that, use alignment of the outer container). For multi-line text, however, this will set
+         * the alignment of lines relative to the {@link Text} element bounds. If not defined,
+         * defaults to TEXT_ALIGN_CENTER. Intended for testing purposes only.
+         */
+        @Nullable
+        public TextAlignmentProp getMultilineAlignment() {
+            if (mImpl.hasMultilineAlignment()) {
+                return TextAlignmentProp.fromProto(mImpl.getMultilineAlignment());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets how to handle text which overflows the bound of the {@link Text} element. A {@link
+         * Text} element will grow as large as possible inside its parent container (while still
+         * respecting max_lines); if it cannot grow large enough to render all of its text, the text
+         * which cannot fit inside its container will be truncated. If not defined, defaults to
+         * TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
+         */
+        @Nullable
+        public TextOverflowProp getOverflow() {
+            if (mImpl.hasOverflow()) {
+                return TextOverflowProp.fromProto(mImpl.getOverflow());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the explicit height between lines of text. This is equivalent to the vertical
+         * distance between subsequent baselines. If not specified, defaults the font's recommended
+         * interline spacing. Intended for testing purposes only.
+         */
+        @Nullable
+        public SpProp getLineHeight() {
+            if (mImpl.hasLineHeight()) {
+                return SpProp.fromProto(mImpl.getLineHeight());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Text fromProto(@NonNull LayoutElementProto.Text proto) {
+            return new Text(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Text toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setText(mImpl).build();
+        }
+
+        /** Builder for {@link Text}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Text.Builder mImpl =
+                    LayoutElementProto.Text.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1976530157);
+
+            public Builder() {}
+
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull StringProp text) {
+                mImpl.setText(text.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull String text) {
+                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
+                return this;
+            }
+
+            /**
+             * Sets the style of font to use (size, bold etc). If not specified, defaults to the
+             * platform's default body font.
+             */
+            @NonNull
+            public Builder setFontStyle(@NonNull FontStyle fontStyle) {
+                mImpl.setFontStyle(fontStyle.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the maximum number of lines that can be represented by the {@link Text} element.
+             * If not defined, the {@link Text} element will be treated as a single-line element.
+             */
+            @NonNull
+            public Builder setMaxLines(@NonNull Int32Prop maxLines) {
+                mImpl.setMaxLines(maxLines.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the maximum number of lines that can be represented by the {@link Text} element.
+             * If not defined, the {@link Text} element will be treated as a single-line element.
+             */
+            @NonNull
+            public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
+                mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
+                return this;
+            }
+
+            /**
+             * Sets alignment of the text within its bounds. Note that a {@link Text} element will
+             * size itself to wrap its contents, so this option is meaningless for single-line text
+             * (for that, use alignment of the outer container). For multi-line text, however, this
+             * will set the alignment of lines relative to the {@link Text} element bounds. If not
+             * defined, defaults to TEXT_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setMultilineAlignment(@NonNull TextAlignmentProp multilineAlignment) {
+                mImpl.setMultilineAlignment(multilineAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets alignment of the text within its bounds. Note that a {@link Text} element will
+             * size itself to wrap its contents, so this option is meaningless for single-line text
+             * (for that, use alignment of the outer container). For multi-line text, however, this
+             * will set the alignment of lines relative to the {@link Text} element bounds. If not
+             * defined, defaults to TEXT_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setMultilineAlignment(@TextAlignment int multilineAlignment) {
+                mImpl.setMultilineAlignment(
+                        AlignmentProto.TextAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.TextAlignment.forNumber(
+                                                multilineAlignment)));
+                return this;
+            }
+
+            /**
+             * Sets how to handle text which overflows the bound of the {@link Text} element. A
+             * {@link Text} element will grow as large as possible inside its parent container
+             * (while still respecting max_lines); if it cannot grow large enough to render all of
+             * its text, the text which cannot fit inside its container will be truncated. If not
+             * defined, defaults to TEXT_OVERFLOW_TRUNCATE.
+             */
+            @NonNull
+            public Builder setOverflow(@NonNull TextOverflowProp overflow) {
+                mImpl.setOverflow(overflow.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets how to handle text which overflows the bound of the {@link Text} element. A
+             * {@link Text} element will grow as large as possible inside its parent container
+             * (while still respecting max_lines); if it cannot grow large enough to render all of
+             * its text, the text which cannot fit inside its container will be truncated. If not
+             * defined, defaults to TEXT_OVERFLOW_TRUNCATE.
+             */
+            @NonNull
+            public Builder setOverflow(@TextOverflow int overflow) {
+                mImpl.setOverflow(
+                        LayoutElementProto.TextOverflowProp.newBuilder()
+                                .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
+                return this;
+            }
+
+            /**
+             * Sets the explicit height between lines of text. This is equivalent to the vertical
+             * distance between subsequent baselines. If not specified, defaults the font's
+             * recommended interline spacing.
+             */
+            @NonNull
+            public Builder setLineHeight(@NonNull SpProp lineHeight) {
+                mImpl.setLineHeight(lineHeight.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Text build() {
+                return new Text(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** An extensible {@code ContentScaleMode} property. */
+    public static final class ContentScaleModeProp {
+        private final LayoutElementProto.ContentScaleModeProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ContentScaleModeProp(
+                LayoutElementProto.ContentScaleModeProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @ContentScaleMode
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ContentScaleModeProp fromProto(
+                @NonNull LayoutElementProto.ContentScaleModeProp proto) {
+            return new ContentScaleModeProp(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ContentScaleModeProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link ContentScaleModeProp} */
+        public static final class Builder {
+            private final LayoutElementProto.ContentScaleModeProp.Builder mImpl =
+                    LayoutElementProto.ContentScaleModeProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-893830536);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@ContentScaleMode int value) {
+                mImpl.setValue(LayoutElementProto.ContentScaleMode.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ContentScaleModeProp build() {
+                return new ContentScaleModeProp(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** Filtering parameters used for images. This can be used to apply a color tint to images. */
+    public static final class ColorFilter {
+        private final LayoutElementProto.ColorFilter mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ColorFilter(LayoutElementProto.ColorFilter impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the tint color to use. If specified, the image will be tinted, using SRC_IN blending
+         * (that is, all color information will be stripped from the target image, and only the
+         * alpha channel will be blended with the requested color).
+         *
+         * <p>Note that only Android image resources can be tinted; Inline images will not be
+         * tinted, and this property will have no effect. Intended for testing purposes only.
+         */
+        @Nullable
+        public ColorProp getTint() {
+            if (mImpl.hasTint()) {
+                return ColorProp.fromProto(mImpl.getTint());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ColorFilter fromProto(@NonNull LayoutElementProto.ColorFilter proto) {
+            return new ColorFilter(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ColorFilter toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link ColorFilter} */
+        public static final class Builder {
+            private final LayoutElementProto.ColorFilter.Builder mImpl =
+                    LayoutElementProto.ColorFilter.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(181311326);
+
+            public Builder() {}
+
+            /**
+             * Sets the tint color to use. If specified, the image will be tinted, using SRC_IN
+             * blending (that is, all color information will be stripped from the target image, and
+             * only the alpha channel will be blended with the requested color).
+             *
+             * <p>Note that only Android image resources can be tinted; Inline images will not be
+             * tinted, and this property will have no effect.
+             */
+            @NonNull
+            public Builder setTint(@NonNull ColorProp tint) {
+                mImpl.setTint(tint.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(tint.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ColorFilter build() {
+                return new ColorFilter(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
+     * An image.
      *
+     * <p>Images used in this element must exist in the resource bundle that corresponds to this
+     * layout. Images must have their dimension specified, and will be rendered at this width and
+     * height, regardless of their native dimension.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
+    public static final class Image implements LayoutElement {
+        private final LayoutElementProto.Image mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Image(LayoutElementProto.Image impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the resource_id of the image to render. This must exist in the supplied resource
+         * bundle. Intended for testing purposes only.
+         */
+        @Nullable
+        public StringProp getResourceId() {
+            if (mImpl.hasResourceId()) {
+                return StringProp.fromProto(mImpl.getResourceId());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the width of this image. If not defined, the image will not be rendered. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public ImageDimension getWidth() {
+            if (mImpl.hasWidth()) {
+                return DimensionBuilders.imageDimensionFromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the height of this image. If not defined, the image will not be rendered. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public ImageDimension getHeight() {
+            if (mImpl.hasHeight()) {
+                return DimensionBuilders.imageDimensionFromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets how to scale the image resource inside the bounds specified by width/height if its
+         * size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT. Intended for
+         * testing purposes only.
+         */
+        @Nullable
+        public ContentScaleModeProp getContentScaleMode() {
+            if (mImpl.hasContentScaleMode()) {
+                return ContentScaleModeProp.fromProto(mImpl.getContentScaleMode());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets filtering parameters for this image. If not specified, defaults to no filtering.
+         * Intended for testing purposes only.
+         */
+        @Nullable
+        public ColorFilter getColorFilter() {
+            if (mImpl.hasColorFilter()) {
+                return ColorFilter.fromProto(mImpl.getColorFilter());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Image fromProto(@NonNull LayoutElementProto.Image proto) {
+            return new Image(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Image toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setImage(mImpl).build();
+        }
+
+        /** Builder for {@link Image}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Image.Builder mImpl =
+                    LayoutElementProto.Image.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-543078544);
+
+            public Builder() {}
+
+            /**
+             * Sets the resource_id of the image to render. This must exist in the supplied resource
+             * bundle.
+             */
+            @NonNull
+            public Builder setResourceId(@NonNull StringProp resourceId) {
+                mImpl.setResourceId(resourceId.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the resource_id of the image to render. This must exist in the supplied resource
+             * bundle.
+             */
+            @NonNull
+            public Builder setResourceId(@NonNull String resourceId) {
+                mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
+                return this;
+            }
+
+            /** Sets the width of this image. If not defined, the image will not be rendered. */
+            @NonNull
+            public Builder setWidth(@NonNull ImageDimension width) {
+                mImpl.setWidth(width.toImageDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the height of this image. If not defined, the image will not be rendered. */
+            @NonNull
+            public Builder setHeight(@NonNull ImageDimension height) {
+                mImpl.setHeight(height.toImageDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets how to scale the image resource inside the bounds specified by width/height if
+             * its size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
+             */
+            @NonNull
+            public Builder setContentScaleMode(@NonNull ContentScaleModeProp contentScaleMode) {
+                mImpl.setContentScaleMode(contentScaleMode.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(contentScaleMode.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets how to scale the image resource inside the bounds specified by width/height if
+             * its size does not match those bounds. Defaults to CONTENT_SCALE_MODE_FIT.
+             */
+            @NonNull
+            public Builder setContentScaleMode(@ContentScaleMode int contentScaleMode) {
+                mImpl.setContentScaleMode(
+                        LayoutElementProto.ContentScaleModeProp.newBuilder()
+                                .setValue(
+                                        LayoutElementProto.ContentScaleMode.forNumber(
+                                                contentScaleMode)));
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets filtering parameters for this image. If not specified, defaults to no filtering.
+             */
+            @NonNull
+            public Builder setColorFilter(@NonNull ColorFilter colorFilter) {
+                mImpl.setColorFilter(colorFilter.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(colorFilter.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Image build() {
+                return new Image(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    @NonNull
-    static HorizontalAlignmentProp fromProto(
-        @NonNull AlignmentProto.HorizontalAlignmentProp proto) {
-      return new HorizontalAlignmentProp(proto, null);
-    }
+    /** A simple spacer, typically used to provide padding between adjacent elements. */
+    public static final class Spacer implements LayoutElement {
+        private final LayoutElementProto.Spacer mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    AlignmentProto.HorizontalAlignmentProp toProto() {
-      return mImpl;
-    }
+        Spacer(LayoutElementProto.Spacer impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-    /** Builder for {@link HorizontalAlignmentProp} */
-    public static final class Builder {
-      private final AlignmentProto.HorizontalAlignmentProp.Builder mImpl =
-          AlignmentProto.HorizontalAlignmentProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-384830516);
+        /**
+         * Gets the width of this {@link Spacer}. When this is added as the direct child of an
+         * {@link Arc}, this must be specified as an angular dimension, otherwise a linear dimension
+         * must be used. If not defined, defaults to 0. Intended for testing purposes only.
+         */
+        @Nullable
+        public SpacerDimension getWidth() {
+            if (mImpl.hasWidth()) {
+                return DimensionBuilders.spacerDimensionFromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
 
-      public Builder() {}
+        /**
+         * Gets the height of this spacer. If not defined, defaults to 0. Intended for testing
+         * purposes only.
+         */
+        @Nullable
+        public SpacerDimension getHeight() {
+            if (mImpl.hasHeight()) {
+                return DimensionBuilders.spacerDimensionFromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
 
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@HorizontalAlignment int value) {
-        mImpl.setValue(AlignmentProto.HorizontalAlignment.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public HorizontalAlignmentProp build() {
-        return new HorizontalAlignmentProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
 
-  /** An extensible {@code VerticalAlignment} property. */
-  public static final class VerticalAlignmentProp {
-    private final AlignmentProto.VerticalAlignmentProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        @NonNull
+        static Spacer fromProto(@NonNull LayoutElementProto.Spacer proto) {
+            return new Spacer(proto, null);
+        }
 
-    VerticalAlignmentProp(
-        AlignmentProto.VerticalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+        @NonNull
+        LayoutElementProto.Spacer toProto() {
+            return mImpl;
+        }
 
-    /** Gets the value. Intended for testing purposes only. */
-    @VerticalAlignment
-    public int getValue() {
-      return mImpl.getValue().getNumber();
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setSpacer(mImpl).build();
+        }
+
+        /** Builder for {@link Spacer}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Spacer.Builder mImpl =
+                    LayoutElementProto.Spacer.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1748084575);
+
+            public Builder() {}
+
+            /**
+             * Sets the width of this {@link Spacer}. When this is added as the direct child of an
+             * {@link Arc}, this must be specified as an angular dimension, otherwise a linear
+             * dimension must be used. If not defined, defaults to 0.
+             */
+            @NonNull
+            public Builder setWidth(@NonNull SpacerDimension width) {
+                mImpl.setWidth(width.toSpacerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the height of this spacer. If not defined, defaults to 0. */
+            @NonNull
+            public Builder setHeight(@NonNull SpacerDimension height) {
+                mImpl.setHeight(height.toSpacerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Spacer build() {
+                return new Spacer(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * A container which stacks all of its children on top of one another. This also allows to add a
+     * background color, or to have a border around them with some padding.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    public static final class Box implements LayoutElement {
+        private final LayoutElementProto.Box mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    static VerticalAlignmentProp fromProto(@NonNull AlignmentProto.VerticalAlignmentProp proto) {
-      return new VerticalAlignmentProp(proto, null);
-    }
+        Box(LayoutElementProto.Box impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-    @NonNull
-    AlignmentProto.VerticalAlignmentProp toProto() {
-      return mImpl;
-    }
+        /** Gets the child element(s) to wrap. Intended for testing purposes only. */
+        @NonNull
+        public List<LayoutElement> getContents() {
+            List<LayoutElement> list = new ArrayList<>();
+            for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
+                list.add(LayoutElementBuilders.layoutElementFromProto(item));
+            }
+            return Collections.unmodifiableList(list);
+        }
 
-    /** Builder for {@link VerticalAlignmentProp} */
-    public static final class Builder {
-      private final AlignmentProto.VerticalAlignmentProp.Builder mImpl =
-          AlignmentProto.VerticalAlignmentProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1443510393);
+        /**
+         * Gets the height of this {@link Box}. If not defined, this will size itself to fit all of
+         * its children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getHeight() {
+            if (mImpl.hasHeight()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
 
-      public Builder() {}
+        /**
+         * Gets the width of this {@link Box}. If not defined, this will size itself to fit all of
+         * its children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getWidth() {
+            if (mImpl.hasWidth()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
 
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@VerticalAlignment int value) {
-        mImpl.setValue(AlignmentProto.VerticalAlignment.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
+        /**
+         * Gets the horizontal alignment of the element inside this {@link Box}. If not defined,
+         * defaults to HORIZONTAL_ALIGN_CENTER. Intended for testing purposes only.
+         */
+        @Nullable
+        public HorizontalAlignmentProp getHorizontalAlignment() {
+            if (mImpl.hasHorizontalAlignment()) {
+                return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
+            } else {
+                return null;
+            }
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public VerticalAlignmentProp build() {
-        return new VerticalAlignmentProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        /**
+         * Gets the vertical alignment of the element inside this {@link Box}. If not defined,
+         * defaults to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
+         */
+        @Nullable
+        public VerticalAlignmentProp getVerticalAlignment() {
+            if (mImpl.hasVerticalAlignment()) {
+                return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
+            } else {
+                return null;
+            }
+        }
 
-  /** An extensible {@code TextAlignment} property. */
-  public static final class TextAlignmentProp {
-    private final AlignmentProto.TextAlignmentProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
 
-    TextAlignmentProp(
-        AlignmentProto.TextAlignmentProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
 
-    /** Gets the value. Intended for testing purposes only. */
-    @TextAlignment
-    public int getValue() {
-      return mImpl.getValue().getNumber();
+        @NonNull
+        static Box fromProto(@NonNull LayoutElementProto.Box proto) {
+            return new Box(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Box toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setBox(mImpl).build();
+        }
+
+        /** Builder for {@link Box}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Box.Builder mImpl =
+                    LayoutElementProto.Box.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1881256071);
+
+            public Builder() {}
+
+            /** Adds one item to the child element(s) to wrap. */
+            @NonNull
+            public Builder addContent(@NonNull LayoutElement content) {
+                mImpl.addContents(content.toLayoutElementProto());
+                mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
+                return this;
+            }
+
+            /**
+             * Sets the height of this {@link Box}. If not defined, this will size itself to fit all
+             * of its children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setHeight(@NonNull ContainerDimension height) {
+                mImpl.setHeight(height.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the width of this {@link Box}. If not defined, this will size itself to fit all
+             * of its children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setWidth(@NonNull ContainerDimension width) {
+                mImpl.setWidth(width.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
+             * defaults to HORIZONTAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setHorizontalAlignment(
+                    @NonNull HorizontalAlignmentProp horizontalAlignment) {
+                mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4,
+                        checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the horizontal alignment of the element inside this {@link Box}. If not defined,
+             * defaults to HORIZONTAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
+                mImpl.setHorizontalAlignment(
+                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.HorizontalAlignment.forNumber(
+                                                horizontalAlignment)));
+                return this;
+            }
+
+            /**
+             * Sets the vertical alignment of the element inside this {@link Box}. If not defined,
+             * defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
+                mImpl.setVerticalAlignment(verticalAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the vertical alignment of the element inside this {@link Box}. If not defined,
+             * defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
+                mImpl.setVerticalAlignment(
+                        AlignmentProto.VerticalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.VerticalAlignment.forNumber(
+                                                verticalAlignment)));
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Box build() {
+                return new Box(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * A portion of text which can be added to a {@link Span}. Two different {@link SpanText}
+     * elements on the same line will be aligned to the same baseline, regardless of the size of
+     * each {@link SpanText}.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
+    public static final class SpanText implements Span {
+        private final LayoutElementProto.SpanText mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SpanText(LayoutElementProto.SpanText impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the text to render. Intended for testing purposes only. */
+        @Nullable
+        public StringProp getText() {
+            if (mImpl.hasText()) {
+                return StringProp.fromProto(mImpl.getText());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the style of font to use (size, bold etc). If not specified, defaults to the
+         * platform's default body font. Intended for testing purposes only.
+         */
+        @Nullable
+        public FontStyle getFontStyle() {
+            if (mImpl.hasFontStyle()) {
+                return FontStyle.fromProto(mImpl.getFontStyle());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public SpanModifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return SpanModifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static SpanText fromProto(@NonNull LayoutElementProto.SpanText proto) {
+            return new SpanText(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.SpanText toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.Span toSpanProto() {
+            return LayoutElementProto.Span.newBuilder().setText(mImpl).build();
+        }
+
+        /** Builder for {@link SpanText}. */
+        public static final class Builder implements Span.Builder {
+            private final LayoutElementProto.SpanText.Builder mImpl =
+                    LayoutElementProto.SpanText.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-221774557);
+
+            public Builder() {}
+
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull StringProp text) {
+                mImpl.setText(text.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull String text) {
+                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
+                return this;
+            }
+
+            /**
+             * Sets the style of font to use (size, bold etc). If not specified, defaults to the
+             * platform's default body font.
+             */
+            @NonNull
+            public Builder setFontStyle(@NonNull FontStyle fontStyle) {
+                mImpl.setFontStyle(fontStyle.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull SpanModifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public SpanText build() {
+                return new SpanText(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    @NonNull
-    static TextAlignmentProp fromProto(@NonNull AlignmentProto.TextAlignmentProp proto) {
-      return new TextAlignmentProp(proto, null);
-    }
+    /** An image which can be added to a {@link Span}. */
+    public static final class SpanImage implements Span {
+        private final LayoutElementProto.SpanImage mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    AlignmentProto.TextAlignmentProp toProto() {
-      return mImpl;
-    }
+        SpanImage(LayoutElementProto.SpanImage impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-    /** Builder for {@link TextAlignmentProp} */
-    public static final class Builder {
-      private final AlignmentProto.TextAlignmentProp.Builder mImpl =
-          AlignmentProto.TextAlignmentProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(797507251);
+        /**
+         * Gets the resource_id of the image to render. This must exist in the supplied resource
+         * bundle. Intended for testing purposes only.
+         */
+        @Nullable
+        public StringProp getResourceId() {
+            if (mImpl.hasResourceId()) {
+                return StringProp.fromProto(mImpl.getResourceId());
+            } else {
+                return null;
+            }
+        }
 
-      public Builder() {}
+        /**
+         * Gets the width of this image. If not defined, the image will not be rendered. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public DpProp getWidth() {
+            if (mImpl.hasWidth()) {
+                return DpProp.fromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
 
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@TextAlignment int value) {
-        mImpl.setValue(AlignmentProto.TextAlignment.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
+        /**
+         * Gets the height of this image. If not defined, the image will not be rendered. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public DpProp getHeight() {
+            if (mImpl.hasHeight()) {
+                return DpProp.fromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public TextAlignmentProp build() {
-        return new TextAlignmentProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public SpanModifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return SpanModifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
 
-  /** An extensible {@code ArcAnchorType} property. */
-  public static final class ArcAnchorTypeProp {
-    private final AlignmentProto.ArcAnchorTypeProp mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        /**
+         * Gets alignment of this image within the line height of the surrounding {@link Spannable}.
+         * If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM. Intended for testing purposes only.
+         */
+        @Nullable
+        public SpanVerticalAlignmentProp getAlignment() {
+            if (mImpl.hasAlignment()) {
+                return SpanVerticalAlignmentProp.fromProto(mImpl.getAlignment());
+            } else {
+                return null;
+            }
+        }
 
-    ArcAnchorTypeProp(
-        AlignmentProto.ArcAnchorTypeProp impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
 
-    /** Gets the value. Intended for testing purposes only. */
-    @ArcAnchorType
-    public int getValue() {
-      return mImpl.getValue().getNumber();
+        @NonNull
+        static SpanImage fromProto(@NonNull LayoutElementProto.SpanImage proto) {
+            return new SpanImage(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.SpanImage toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.Span toSpanProto() {
+            return LayoutElementProto.Span.newBuilder().setImage(mImpl).build();
+        }
+
+        /** Builder for {@link SpanImage}. */
+        public static final class Builder implements Span.Builder {
+            private final LayoutElementProto.SpanImage.Builder mImpl =
+                    LayoutElementProto.SpanImage.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(502289772);
+
+            public Builder() {}
+
+            /**
+             * Sets the resource_id of the image to render. This must exist in the supplied resource
+             * bundle.
+             */
+            @NonNull
+            public Builder setResourceId(@NonNull StringProp resourceId) {
+                mImpl.setResourceId(resourceId.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(resourceId.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the resource_id of the image to render. This must exist in the supplied resource
+             * bundle.
+             */
+            @NonNull
+            public Builder setResourceId(@NonNull String resourceId) {
+                mImpl.setResourceId(TypesProto.StringProp.newBuilder().setValue(resourceId));
+                return this;
+            }
+
+            /** Sets the width of this image. If not defined, the image will not be rendered. */
+            @NonNull
+            public Builder setWidth(@NonNull DpProp width) {
+                mImpl.setWidth(width.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the height of this image. If not defined, the image will not be rendered. */
+            @NonNull
+            public Builder setHeight(@NonNull DpProp height) {
+                mImpl.setHeight(height.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull SpanModifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets alignment of this image within the line height of the surrounding {@link
+             * Spannable}. If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
+             */
+            @NonNull
+            public Builder setAlignment(@NonNull SpanVerticalAlignmentProp alignment) {
+                mImpl.setAlignment(alignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(alignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets alignment of this image within the line height of the surrounding {@link
+             * Spannable}. If undefined, defaults to SPAN_VERTICAL_ALIGN_BOTTOM.
+             */
+            @NonNull
+            public Builder setAlignment(@SpanVerticalAlignment int alignment) {
+                mImpl.setAlignment(
+                        LayoutElementProto.SpanVerticalAlignmentProp.newBuilder()
+                                .setValue(
+                                        LayoutElementProto.SpanVerticalAlignment.forNumber(
+                                                alignment)));
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public SpanImage build() {
+                return new SpanImage(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * Interface defining a single {@link Span}. Each {@link Span} forms part of a larger {@link
+     * Spannable} widget. At the moment, the only widgets which can be added to {@link Spannable}
+     * containers are {@link SpanText} and {@link SpanImage} elements.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
+    public interface Span {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        LayoutElementProto.Span toSpanProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link Span} objects. */
+        @SuppressLint("StaticFinalBuilder")
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            Span build();
+        }
     }
 
     @NonNull
-    static ArcAnchorTypeProp fromProto(@NonNull AlignmentProto.ArcAnchorTypeProp proto) {
-      return new ArcAnchorTypeProp(proto, null);
+    static Span spanFromProto(@NonNull LayoutElementProto.Span proto) {
+        if (proto.hasText()) {
+            return SpanText.fromProto(proto.getText());
+        }
+        if (proto.hasImage()) {
+            return SpanImage.fromProto(proto.getImage());
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of Span");
+    }
+
+    /**
+     * A container of {@link Span} elements. Currently, this supports {@link SpanImage} and {@link
+     * SpanText} elements, where each individual {@link Span} can have different styling applied to
+     * it but the resulting text will flow naturally. This allows sections of a paragraph of text to
+     * have different styling applied to it, for example, making one or two words bold or italic.
+     */
+    public static final class Spannable implements LayoutElement {
+        private final LayoutElementProto.Spannable mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Spannable(LayoutElementProto.Spannable impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the {@link Span} elements that form this {@link Spannable}. Intended for testing
+         * purposes only.
+         */
+        @NonNull
+        public List<Span> getSpans() {
+            List<Span> list = new ArrayList<>();
+            for (LayoutElementProto.Span item : mImpl.getSpansList()) {
+                list.add(LayoutElementBuilders.spanFromProto(item));
+            }
+            return Collections.unmodifiableList(list);
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the maximum number of lines that can be represented by the {@link Spannable}
+         * element. If not defined, the {@link Spannable} element will be treated as a single-line
+         * element. Intended for testing purposes only.
+         */
+        @Nullable
+        public Int32Prop getMaxLines() {
+            if (mImpl.hasMaxLines()) {
+                return Int32Prop.fromProto(mImpl.getMaxLines());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets alignment of the {@link Spannable} content within its bounds. Note that a {@link
+         * Spannable} element will size itself to wrap its contents, so this option is meaningless
+         * for single-line content (for that, use alignment of the outer container). For multi-line
+         * content, however, this will set the alignment of lines relative to the {@link Spannable}
+         * element bounds. If not defined, defaults to TEXT_ALIGN_CENTER. Intended for testing
+         * purposes only.
+         */
+        @Nullable
+        public HorizontalAlignmentProp getMultilineAlignment() {
+            if (mImpl.hasMultilineAlignment()) {
+                return HorizontalAlignmentProp.fromProto(mImpl.getMultilineAlignment());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets how to handle content which overflows the bound of the {@link Spannable} element. A
+         * {@link Spannable} element will grow as large as possible inside its parent container
+         * (while still respecting max_lines); if it cannot grow large enough to render all of its
+         * content, the content which cannot fit inside its container will be truncated. If not
+         * defined, defaults to TEXT_OVERFLOW_TRUNCATE. Intended for testing purposes only.
+         */
+        @Nullable
+        public TextOverflowProp getOverflow() {
+            if (mImpl.hasOverflow()) {
+                return TextOverflowProp.fromProto(mImpl.getOverflow());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the explicit height between lines of text. This is equivalent to the vertical
+         * distance between subsequent baselines. If not specified, defaults the font's recommended
+         * interline spacing. Intended for testing purposes only.
+         */
+        @Nullable
+        public SpProp getLineHeight() {
+            if (mImpl.hasLineHeight()) {
+                return SpProp.fromProto(mImpl.getLineHeight());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Spannable fromProto(@NonNull LayoutElementProto.Spannable proto) {
+            return new Spannable(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Spannable toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setSpannable(mImpl).build();
+        }
+
+        /** Builder for {@link Spannable}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Spannable.Builder mImpl =
+                    LayoutElementProto.Spannable.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1690284372);
+
+            public Builder() {}
+
+            /** Adds one item to the {@link Span} elements that form this {@link Spannable}. */
+            @NonNull
+            public Builder addSpan(@NonNull Span span) {
+                mImpl.addSpans(span.toSpanProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(span.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the maximum number of lines that can be represented by the {@link Spannable}
+             * element. If not defined, the {@link Spannable} element will be treated as a
+             * single-line element.
+             */
+            @NonNull
+            public Builder setMaxLines(@NonNull Int32Prop maxLines) {
+                mImpl.setMaxLines(maxLines.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(maxLines.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets the maximum number of lines that can be represented by the {@link Spannable}
+             * element. If not defined, the {@link Spannable} element will be treated as a
+             * single-line element.
+             */
+            @NonNull
+            public Builder setMaxLines(@IntRange(from = 1) int maxLines) {
+                mImpl.setMaxLines(TypesProto.Int32Prop.newBuilder().setValue(maxLines));
+                return this;
+            }
+
+            /**
+             * Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
+             * Spannable} element will size itself to wrap its contents, so this option is
+             * meaningless for single-line content (for that, use alignment of the outer container).
+             * For multi-line content, however, this will set the alignment of lines relative to the
+             * {@link Spannable} element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setMultilineAlignment(
+                    @NonNull HorizontalAlignmentProp multilineAlignment) {
+                mImpl.setMultilineAlignment(multilineAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(multilineAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets alignment of the {@link Spannable} content within its bounds. Note that a {@link
+             * Spannable} element will size itself to wrap its contents, so this option is
+             * meaningless for single-line content (for that, use alignment of the outer container).
+             * For multi-line content, however, this will set the alignment of lines relative to the
+             * {@link Spannable} element bounds. If not defined, defaults to TEXT_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setMultilineAlignment(@HorizontalAlignment int multilineAlignment) {
+                mImpl.setMultilineAlignment(
+                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.HorizontalAlignment.forNumber(
+                                                multilineAlignment)));
+                return this;
+            }
+
+            /**
+             * Sets how to handle content which overflows the bound of the {@link Spannable}
+             * element. A {@link Spannable} element will grow as large as possible inside its parent
+             * container (while still respecting max_lines); if it cannot grow large enough to
+             * render all of its content, the content which cannot fit inside its container will be
+             * truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
+             */
+            @NonNull
+            public Builder setOverflow(@NonNull TextOverflowProp overflow) {
+                mImpl.setOverflow(overflow.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(overflow.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets how to handle content which overflows the bound of the {@link Spannable}
+             * element. A {@link Spannable} element will grow as large as possible inside its parent
+             * container (while still respecting max_lines); if it cannot grow large enough to
+             * render all of its content, the content which cannot fit inside its container will be
+             * truncated. If not defined, defaults to TEXT_OVERFLOW_TRUNCATE.
+             */
+            @NonNull
+            public Builder setOverflow(@TextOverflow int overflow) {
+                mImpl.setOverflow(
+                        LayoutElementProto.TextOverflowProp.newBuilder()
+                                .setValue(LayoutElementProto.TextOverflow.forNumber(overflow)));
+                return this;
+            }
+
+            /**
+             * Sets the explicit height between lines of text. This is equivalent to the vertical
+             * distance between subsequent baselines. If not specified, defaults the font's
+             * recommended interline spacing.
+             */
+            @NonNull
+            public Builder setLineHeight(@NonNull SpProp lineHeight) {
+                mImpl.setLineHeight(lineHeight.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        7, checkNotNull(lineHeight.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Spannable build() {
+                return new Spannable(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A column of elements. Each child element will be laid out vertically, one after another (i.e.
+     * stacking down). This element will size itself to the smallest size required to hold all of
+     * its children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting
+     * column will be 30x60).
+     *
+     * <p>If specified, horizontal_alignment can be used to control the gravity inside the
+     * container, affecting the horizontal placement of children whose width are smaller than the
+     * resulting column width.
+     */
+    public static final class Column implements LayoutElement {
+        private final LayoutElementProto.Column mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Column(LayoutElementProto.Column impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the list of child elements to place inside this {@link Column}. Intended for testing
+         * purposes only.
+         */
+        @NonNull
+        public List<LayoutElement> getContents() {
+            List<LayoutElement> list = new ArrayList<>();
+            for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
+                list.add(LayoutElementBuilders.layoutElementFromProto(item));
+            }
+            return Collections.unmodifiableList(list);
+        }
+
+        /**
+         * Gets the horizontal alignment of elements inside this column, if they are narrower than
+         * the resulting width of the column. If not defined, defaults to HORIZONTAL_ALIGN_CENTER.
+         * Intended for testing purposes only.
+         */
+        @Nullable
+        public HorizontalAlignmentProp getHorizontalAlignment() {
+            if (mImpl.hasHorizontalAlignment()) {
+                return HorizontalAlignmentProp.fromProto(mImpl.getHorizontalAlignment());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the width of this column. If not defined, this will size itself to fit all of its
+         * children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getWidth() {
+            if (mImpl.hasWidth()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the height of this column. If not defined, this will size itself to fit all of its
+         * children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getHeight() {
+            if (mImpl.hasHeight()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Column fromProto(@NonNull LayoutElementProto.Column proto) {
+            return new Column(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Column toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setColumn(mImpl).build();
+        }
+
+        /** Builder for {@link Column}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Column.Builder mImpl =
+                    LayoutElementProto.Column.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1411218529);
+
+            public Builder() {}
+
+            /** Adds one item to the list of child elements to place inside this {@link Column}. */
+            @NonNull
+            public Builder addContent(@NonNull LayoutElement content) {
+                mImpl.addContents(content.toLayoutElementProto());
+                mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
+                return this;
+            }
+
+            /**
+             * Sets the horizontal alignment of elements inside this column, if they are narrower
+             * than the resulting width of the column. If not defined, defaults to
+             * HORIZONTAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setHorizontalAlignment(
+                    @NonNull HorizontalAlignmentProp horizontalAlignment) {
+                mImpl.setHorizontalAlignment(horizontalAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2,
+                        checkNotNull(horizontalAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the horizontal alignment of elements inside this column, if they are narrower
+             * than the resulting width of the column. If not defined, defaults to
+             * HORIZONTAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setHorizontalAlignment(@HorizontalAlignment int horizontalAlignment) {
+                mImpl.setHorizontalAlignment(
+                        AlignmentProto.HorizontalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.HorizontalAlignment.forNumber(
+                                                horizontalAlignment)));
+                return this;
+            }
+
+            /**
+             * Sets the width of this column. If not defined, this will size itself to fit all of
+             * its children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setWidth(@NonNull ContainerDimension width) {
+                mImpl.setWidth(width.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the height of this column. If not defined, this will size itself to fit all of
+             * its children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setHeight(@NonNull ContainerDimension height) {
+                mImpl.setHeight(height.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Column build() {
+                return new Column(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * A row of elements. Each child will be laid out horizontally, one after another (i.e. stacking
+     * to the right). This element will size itself to the smallest size required to hold all of its
+     * children (e.g. if it contains three elements sized 10x10, 20x20 and 30x30, the resulting row
+     * will be 60x30).
+     *
+     * <p>If specified, vertical_alignment can be used to control the gravity inside the container,
+     * affecting the vertical placement of children whose width are smaller than the resulting row
+     * height.
+     */
+    public static final class Row implements LayoutElement {
+        private final LayoutElementProto.Row mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Row(LayoutElementProto.Row impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the list of child elements to place inside this {@link Row}. Intended for testing
+         * purposes only.
+         */
+        @NonNull
+        public List<LayoutElement> getContents() {
+            List<LayoutElement> list = new ArrayList<>();
+            for (LayoutElementProto.LayoutElement item : mImpl.getContentsList()) {
+                list.add(LayoutElementBuilders.layoutElementFromProto(item));
+            }
+            return Collections.unmodifiableList(list);
+        }
+
+        /**
+         * Gets the vertical alignment of elements inside this row, if they are narrower than the
+         * resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public VerticalAlignmentProp getVerticalAlignment() {
+            if (mImpl.hasVerticalAlignment()) {
+                return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlignment());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the width of this row. If not defined, this will size itself to fit all of its
+         * children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getWidth() {
+            if (mImpl.hasWidth()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the height of this row. If not defined, this will size itself to fit all of its
+         * children (i.e. a WrappedDimension). Intended for testing purposes only.
+         */
+        @Nullable
+        public ContainerDimension getHeight() {
+            if (mImpl.hasHeight()) {
+                return DimensionBuilders.containerDimensionFromProto(mImpl.getHeight());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Row fromProto(@NonNull LayoutElementProto.Row proto) {
+            return new Row(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Row toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setRow(mImpl).build();
+        }
+
+        /** Builder for {@link Row}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Row.Builder mImpl =
+                    LayoutElementProto.Row.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1537205448);
+
+            public Builder() {}
+
+            /** Adds one item to the list of child elements to place inside this {@link Row}. */
+            @NonNull
+            public Builder addContent(@NonNull LayoutElement content) {
+                mImpl.addContents(content.toLayoutElementProto());
+                mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
+                return this;
+            }
+
+            /**
+             * Sets the vertical alignment of elements inside this row, if they are narrower than
+             * the resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlignment(@NonNull VerticalAlignmentProp verticalAlignment) {
+                mImpl.setVerticalAlignment(verticalAlignment.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(verticalAlignment.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the vertical alignment of elements inside this row, if they are narrower than
+             * the resulting height of the row. If not defined, defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlignment(@VerticalAlignment int verticalAlignment) {
+                mImpl.setVerticalAlignment(
+                        AlignmentProto.VerticalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.VerticalAlignment.forNumber(
+                                                verticalAlignment)));
+                return this;
+            }
+
+            /**
+             * Sets the width of this row. If not defined, this will size itself to fit all of its
+             * children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setWidth(@NonNull ContainerDimension width) {
+                mImpl.setWidth(width.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the height of this row. If not defined, this will size itself to fit all of its
+             * children (i.e. a WrappedDimension).
+             */
+            @NonNull
+            public Builder setHeight(@NonNull ContainerDimension height) {
+                mImpl.setHeight(height.toContainerDimensionProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(height.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Row build() {
+                return new Row(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * An arc container. This container will fill itself to a circle, which fits inside its parent
+     * container, and all of its children will be placed on that circle. The fields anchor_angle and
+     * anchor_type can be used to specify where to draw children within this circle.
+     */
+    public static final class Arc implements LayoutElement {
+        private final LayoutElementProto.Arc mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Arc(LayoutElementProto.Arc impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets contents of this container. Intended for testing purposes only. */
+        @NonNull
+        public List<ArcLayoutElement> getContents() {
+            List<ArcLayoutElement> list = new ArrayList<>();
+            for (LayoutElementProto.ArcLayoutElement item : mImpl.getContentsList()) {
+                list.add(LayoutElementBuilders.arcLayoutElementFromProto(item));
+            }
+            return Collections.unmodifiableList(list);
+        }
+
+        /**
+         * Gets the angle for the anchor, used with anchor_type to determine where to draw children.
+         * Note that 0 degrees is the 12 o clock position on a device, and the angle sweeps
+         * clockwise. If not defined, defaults to 0 degrees.
+         *
+         * <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees will
+         * sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values >360
+         * will be be placed at X mod 360 degrees. Intended for testing purposes only.
+         */
+        @Nullable
+        public DegreesProp getAnchorAngle() {
+            if (mImpl.hasAnchorAngle()) {
+                return DegreesProp.fromProto(mImpl.getAnchorAngle());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets how to align the contents of this container relative to anchor_angle. If not
+         * defined, defaults to ARC_ANCHOR_CENTER. Intended for testing purposes only.
+         */
+        @Nullable
+        public ArcAnchorTypeProp getAnchorType() {
+            if (mImpl.hasAnchorType()) {
+                return ArcAnchorTypeProp.fromProto(mImpl.getAnchorType());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
+         * larger than the thickness of the element being drawn, this controls whether the element
+         * should be drawn towards the inner or outer edge of the arc, or drawn in the center. If
+         * not defined, defaults to VERTICAL_ALIGN_CENTER. Intended for testing purposes only.
+         */
+        @Nullable
+        public VerticalAlignmentProp getVerticalAlign() {
+            if (mImpl.hasVerticalAlign()) {
+                return VerticalAlignmentProp.fromProto(mImpl.getVerticalAlign());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public Modifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return Modifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Arc fromProto(@NonNull LayoutElementProto.Arc proto) {
+            return new Arc(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.Arc toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.LayoutElement toLayoutElementProto() {
+            return LayoutElementProto.LayoutElement.newBuilder().setArc(mImpl).build();
+        }
+
+        /** Builder for {@link Arc}. */
+        public static final class Builder implements LayoutElement.Builder {
+            private final LayoutElementProto.Arc.Builder mImpl =
+                    LayoutElementProto.Arc.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(299028337);
+
+            public Builder() {}
+
+            /** Adds one item to contents of this container. */
+            @NonNull
+            public Builder addContent(@NonNull ArcLayoutElement content) {
+                mImpl.addContents(content.toArcLayoutElementProto());
+                mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
+                return this;
+            }
+
+            /**
+             * Sets the angle for the anchor, used with anchor_type to determine where to draw
+             * children. Note that 0 degrees is the 12 o clock position on a device, and the angle
+             * sweeps clockwise. If not defined, defaults to 0 degrees.
+             *
+             * <p>Values do not have to be clamped to the range 0-360; values less than 0 degrees
+             * will sweep anti-clockwise (i.e. -90 degrees is equivalent to 270 degrees), and values
+             * >360 will be be placed at X mod 360 degrees.
+             */
+            @NonNull
+            public Builder setAnchorAngle(@NonNull DegreesProp anchorAngle) {
+                mImpl.setAnchorAngle(anchorAngle.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(anchorAngle.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets how to align the contents of this container relative to anchor_angle. If not
+             * defined, defaults to ARC_ANCHOR_CENTER.
+             */
+            @NonNull
+            public Builder setAnchorType(@NonNull ArcAnchorTypeProp anchorType) {
+                mImpl.setAnchorType(anchorType.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(anchorType.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets how to align the contents of this container relative to anchor_angle. If not
+             * defined, defaults to ARC_ANCHOR_CENTER.
+             */
+            @NonNull
+            public Builder setAnchorType(@ArcAnchorType int anchorType) {
+                mImpl.setAnchorType(
+                        AlignmentProto.ArcAnchorTypeProp.newBuilder()
+                                .setValue(AlignmentProto.ArcAnchorType.forNumber(anchorType)));
+                return this;
+            }
+
+            /**
+             * Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
+             * larger than the thickness of the element being drawn, this controls whether the
+             * element should be drawn towards the inner or outer edge of the arc, or drawn in the
+             * center. If not defined, defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlign(@NonNull VerticalAlignmentProp verticalAlign) {
+                mImpl.setVerticalAlign(verticalAlign.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(verticalAlign.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+            /**
+             * Sets vertical alignment of elements within the arc. If the {@link Arc}'s thickness is
+             * larger than the thickness of the element being drawn, this controls whether the
+             * element should be drawn towards the inner or outer edge of the arc, or drawn in the
+             * center. If not defined, defaults to VERTICAL_ALIGN_CENTER.
+             */
+            @NonNull
+            public Builder setVerticalAlign(@VerticalAlignment int verticalAlign) {
+                mImpl.setVerticalAlign(
+                        AlignmentProto.VerticalAlignmentProp.newBuilder()
+                                .setValue(
+                                        AlignmentProto.VerticalAlignment.forNumber(verticalAlign)));
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull Modifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public Arc build() {
+                return new Arc(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A text element that can be used in an {@link Arc}. */
+    public static final class ArcText implements ArcLayoutElement {
+        private final LayoutElementProto.ArcText mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ArcText(LayoutElementProto.ArcText impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the text to render. Intended for testing purposes only. */
+        @Nullable
+        public StringProp getText() {
+            if (mImpl.hasText()) {
+                return StringProp.fromProto(mImpl.getText());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the style of font to use (size, bold etc). If not specified, defaults to the
+         * platform's default body font. Intended for testing purposes only.
+         */
+        @Nullable
+        public FontStyle getFontStyle() {
+            if (mImpl.hasFontStyle()) {
+                return FontStyle.fromProto(mImpl.getFontStyle());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public ArcModifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return ArcModifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ArcText fromProto(@NonNull LayoutElementProto.ArcText proto) {
+            return new ArcText(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ArcText toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
+            return LayoutElementProto.ArcLayoutElement.newBuilder().setText(mImpl).build();
+        }
+
+        /** Builder for {@link ArcText}. */
+        public static final class Builder implements ArcLayoutElement.Builder {
+            private final LayoutElementProto.ArcText.Builder mImpl =
+                    LayoutElementProto.ArcText.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(434391973);
+
+            public Builder() {}
+
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull StringProp text) {
+                mImpl.setText(text.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(text.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the text to render. */
+            @NonNull
+            public Builder setText(@NonNull String text) {
+                mImpl.setText(TypesProto.StringProp.newBuilder().setValue(text));
+                return this;
+            }
+
+            /**
+             * Sets the style of font to use (size, bold etc). If not specified, defaults to the
+             * platform's default body font.
+             */
+            @NonNull
+            public Builder setFontStyle(@NonNull FontStyle fontStyle) {
+                mImpl.setFontStyle(fontStyle.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(fontStyle.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull ArcModifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ArcText build() {
+                return new ArcText(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A line that can be used in an {@link Arc} and renders as a round progress bar. */
+    public static final class ArcLine implements ArcLayoutElement {
+        private final LayoutElementProto.ArcLine mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ArcLine(LayoutElementProto.ArcLine impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the length of this line, in degrees. If not defined, defaults to 0. Intended for
+         * testing purposes only.
+         */
+        @Nullable
+        public DegreesProp getLength() {
+            if (mImpl.hasLength()) {
+                return DegreesProp.fromProto(mImpl.getLength());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the thickness of this line. If not defined, defaults to 0. Intended for testing
+         * purposes only.
+         */
+        @Nullable
+        public DpProp getThickness() {
+            if (mImpl.hasThickness()) {
+                return DpProp.fromProto(mImpl.getThickness());
+            } else {
+                return null;
+            }
+        }
+
+        /** Gets the color of this line. Intended for testing purposes only. */
+        @Nullable
+        public ColorProp getColor() {
+            if (mImpl.hasColor()) {
+                return ColorProp.fromProto(mImpl.getColor());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public ArcModifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return ArcModifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ArcLine fromProto(@NonNull LayoutElementProto.ArcLine proto) {
+            return new ArcLine(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ArcLine toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
+            return LayoutElementProto.ArcLayoutElement.newBuilder().setLine(mImpl).build();
+        }
+
+        /** Builder for {@link ArcLine}. */
+        public static final class Builder implements ArcLayoutElement.Builder {
+            private final LayoutElementProto.ArcLine.Builder mImpl =
+                    LayoutElementProto.ArcLine.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1371793535);
+
+            public Builder() {}
+
+            /** Sets the length of this line, in degrees. If not defined, defaults to 0. */
+            @NonNull
+            public Builder setLength(@NonNull DegreesProp length) {
+                mImpl.setLength(length.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the thickness of this line. If not defined, defaults to 0. */
+            @NonNull
+            public Builder setThickness(@NonNull DpProp thickness) {
+                mImpl.setThickness(thickness.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the color of this line. */
+            @NonNull
+            public Builder setColor(@NonNull ColorProp color) {
+                mImpl.setColor(color.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull ArcModifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ArcLine build() {
+                return new ArcLine(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A simple spacer used to provide padding between adjacent elements in an {@link Arc}. */
+    public static final class ArcSpacer implements ArcLayoutElement {
+        private final LayoutElementProto.ArcSpacer mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ArcSpacer(LayoutElementProto.ArcSpacer impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the length of this spacer, in degrees. If not defined, defaults to 0. Intended for
+         * testing purposes only.
+         */
+        @Nullable
+        public DegreesProp getLength() {
+            if (mImpl.hasLength()) {
+                return DegreesProp.fromProto(mImpl.getLength());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the thickness of this spacer, in DP. If not defined, defaults to 0. Intended for
+         * testing purposes only.
+         */
+        @Nullable
+        public DpProp getThickness() {
+            if (mImpl.hasThickness()) {
+                return DpProp.fromProto(mImpl.getThickness());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. Intended
+         * for testing purposes only.
+         */
+        @Nullable
+        public ArcModifiers getModifiers() {
+            if (mImpl.hasModifiers()) {
+                return ArcModifiers.fromProto(mImpl.getModifiers());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ArcSpacer fromProto(@NonNull LayoutElementProto.ArcSpacer proto) {
+            return new ArcSpacer(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ArcSpacer toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
+            return LayoutElementProto.ArcLayoutElement.newBuilder().setSpacer(mImpl).build();
+        }
+
+        /** Builder for {@link ArcSpacer}. */
+        public static final class Builder implements ArcLayoutElement.Builder {
+            private final LayoutElementProto.ArcSpacer.Builder mImpl =
+                    LayoutElementProto.ArcSpacer.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-179760535);
+
+            public Builder() {}
+
+            /** Sets the length of this spacer, in degrees. If not defined, defaults to 0. */
+            @NonNull
+            public Builder setLength(@NonNull DegreesProp length) {
+                mImpl.setLength(length.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(length.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the thickness of this spacer, in DP. If not defined, defaults to 0. */
+            @NonNull
+            public Builder setThickness(@NonNull DpProp thickness) {
+                mImpl.setThickness(thickness.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(thickness.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets {@link androidx.wear.tiles.ModifiersBuilders.Modifiers} for this element. */
+            @NonNull
+            public Builder setModifiers(@NonNull ArcModifiers modifiers) {
+                mImpl.setModifiers(modifiers.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(modifiers.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ArcSpacer build() {
+                return new ArcSpacer(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A container that allows a standard {@link LayoutElement} to be added to an {@link Arc}. */
+    public static final class ArcAdapter implements ArcLayoutElement {
+        private final LayoutElementProto.ArcAdapter mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ArcAdapter(LayoutElementProto.ArcAdapter impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the element to adapt to an {@link Arc}. Intended for testing purposes only. */
+        @Nullable
+        public LayoutElement getContent() {
+            if (mImpl.hasContent()) {
+                return LayoutElementBuilders.layoutElementFromProto(mImpl.getContent());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets whether this adapter's contents should be rotated, according to its position in the
+         * arc or not. As an example, assume that an {@link Image} has been added to the arc, and
+         * ends up at the 3 o clock position. If rotate_contents = true, the image will be placed at
+         * the 3 o clock position, and will be rotated clockwise through 90 degrees. If
+         * rotate_contents = false, the image will be placed at the 3 o clock position, but itself
+         * will not be rotated. If not defined, defaults to false. Intended for testing purposes
+         * only.
+         */
+        @Nullable
+        public BoolProp getRotateContents() {
+            if (mImpl.hasRotateContents()) {
+                return BoolProp.fromProto(mImpl.getRotateContents());
+            } else {
+                return null;
+            }
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ArcAdapter fromProto(@NonNull LayoutElementProto.ArcAdapter proto) {
+            return new ArcAdapter(proto, null);
+        }
+
+        @NonNull
+        LayoutElementProto.ArcAdapter toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.ArcLayoutElement toArcLayoutElementProto() {
+            return LayoutElementProto.ArcLayoutElement.newBuilder().setAdapter(mImpl).build();
+        }
+
+        /** Builder for {@link ArcAdapter}. */
+        public static final class Builder implements ArcLayoutElement.Builder {
+            private final LayoutElementProto.ArcAdapter.Builder mImpl =
+                    LayoutElementProto.ArcAdapter.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1696473935);
+
+            public Builder() {}
+
+            /** Sets the element to adapt to an {@link Arc}. */
+            @NonNull
+            public Builder setContent(@NonNull LayoutElement content) {
+                mImpl.setContent(content.toLayoutElementProto());
+                mFingerprint.addChildNode(checkNotNull(content.getFingerprint()));
+                return this;
+            }
+
+            /**
+             * Sets whether this adapter's contents should be rotated, according to its position in
+             * the arc or not. As an example, assume that an {@link Image} has been added to the
+             * arc, and ends up at the 3 o clock position. If rotate_contents = true, the image will
+             * be placed at the 3 o clock position, and will be rotated clockwise through 90
+             * degrees. If rotate_contents = false, the image will be placed at the 3 o clock
+             * position, but itself will not be rotated. If not defined, defaults to false.
+             */
+            @NonNull
+            public Builder setRotateContents(@NonNull BoolProp rotateContents) {
+                mImpl.setRotateContents(rotateContents.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(rotateContents.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets whether this adapter's contents should be rotated, according to its position in
+             * the arc or not. As an example, assume that an {@link Image} has been added to the
+             * arc, and ends up at the 3 o clock position. If rotate_contents = true, the image will
+             * be placed at the 3 o clock position, and will be rotated clockwise through 90
+             * degrees. If rotate_contents = false, the image will be placed at the 3 o clock
+             * position, but itself will not be rotated. If not defined, defaults to false.
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setRotateContents(boolean rotateContents) {
+                mImpl.setRotateContents(TypesProto.BoolProp.newBuilder().setValue(rotateContents));
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public ArcAdapter build() {
+                return new ArcAdapter(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /**
+     * Interface defining the root of all layout elements. This exists to act as a holder for all of
+     * the actual layout elements above.
+     */
+    public interface LayoutElement {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        LayoutElementProto.LayoutElement toLayoutElementProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link LayoutElement} objects. */
+        @SuppressLint("StaticFinalBuilder")
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            LayoutElement build();
+        }
     }
 
     @NonNull
-    AlignmentProto.ArcAnchorTypeProp toProto() {
-      return mImpl;
+    static LayoutElement layoutElementFromProto(@NonNull LayoutElementProto.LayoutElement proto) {
+        if (proto.hasColumn()) {
+            return Column.fromProto(proto.getColumn());
+        }
+        if (proto.hasRow()) {
+            return Row.fromProto(proto.getRow());
+        }
+        if (proto.hasBox()) {
+            return Box.fromProto(proto.getBox());
+        }
+        if (proto.hasSpacer()) {
+            return Spacer.fromProto(proto.getSpacer());
+        }
+        if (proto.hasText()) {
+            return Text.fromProto(proto.getText());
+        }
+        if (proto.hasImage()) {
+            return Image.fromProto(proto.getImage());
+        }
+        if (proto.hasArc()) {
+            return Arc.fromProto(proto.getArc());
+        }
+        if (proto.hasSpannable()) {
+            return Spannable.fromProto(proto.getSpannable());
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of LayoutElement");
     }
 
-    /** Builder for {@link ArcAnchorTypeProp} */
-    public static final class Builder {
-      private final AlignmentProto.ArcAnchorTypeProp.Builder mImpl =
-          AlignmentProto.ArcAnchorTypeProp.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1193249074);
+    /**
+     * Interface defining the root of all elements that can be used in an {@link Arc}. This exists
+     * to act as a holder for all of the actual arc layout elements above.
+     */
+    public interface ArcLayoutElement {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        LayoutElementProto.ArcLayoutElement toArcLayoutElementProto();
 
-      public Builder() {}
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
 
-      /** Sets the value. */
-      @NonNull
-      public Builder setValue(@ArcAnchorType int value) {
-        mImpl.setValue(AlignmentProto.ArcAnchorType.forNumber(value));
-        mFingerprint.recordPropertyUpdate(1, value);
-        return this;
-      }
+        /** Builder to create {@link ArcLayoutElement} objects. */
+        @SuppressLint("StaticFinalBuilder")
+        interface Builder {
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ArcAnchorTypeProp build() {
-        return new ArcAnchorTypeProp(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** Font styles, currently set up to match Wear's font styling. */
-  public static class FontStyles {
-    private static final int LARGE_SCREEN_WIDTH_DP = 210;
-
-    private FontStyles() {
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            ArcLayoutElement build();
+        }
     }
 
-    private static boolean isLargeScreen(@NonNull DeviceParameters deviceParameters) {
-      return deviceParameters.getScreenWidthDp() >= LARGE_SCREEN_WIDTH_DP;
-    }
-
-    /** Font style for large display text. */
     @NonNull
-    public static FontStyle.Builder display1(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 54 : 50));
+    static ArcLayoutElement arcLayoutElementFromProto(
+            @NonNull LayoutElementProto.ArcLayoutElement proto) {
+        if (proto.hasText()) {
+            return ArcText.fromProto(proto.getText());
+        }
+        if (proto.hasLine()) {
+            return ArcLine.fromProto(proto.getLine());
+        }
+        if (proto.hasSpacer()) {
+            return ArcSpacer.fromProto(proto.getSpacer());
+        }
+        if (proto.hasAdapter()) {
+            return ArcAdapter.fromProto(proto.getAdapter());
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of ArcLayoutElement");
     }
 
-    /** Font style for medium display text. */
-    @NonNull
-    public static FontStyle.Builder display2(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 44 : 40));
+    /** A complete layout. */
+    public static final class Layout {
+        private final LayoutElementProto.Layout mImpl;
+
+        private Layout(LayoutElementProto.Layout impl) {
+            this.mImpl = impl;
+        }
+
+        /** Gets the root element in the layout. Intended for testing purposes only. */
+        @Nullable
+        public LayoutElement getRoot() {
+            if (mImpl.hasRoot()) {
+                return LayoutElementBuilders.layoutElementFromProto(mImpl.getRoot());
+            } else {
+                return null;
+            }
+        }
+
+        /** Creates a {@link Layout} object containing the given layout element. */
+        @NonNull
+        public static Layout fromLayoutElement(@NonNull LayoutElement layoutElement) {
+            return new Builder().setRoot(layoutElement).build();
+        }
+
+        /** Converts to byte array representation. */
+        @NonNull
+        @ProtoLayoutExperimental
+        public byte[] toByteArray() {
+            return mImpl.toByteArray();
+        }
+
+        /** Converts from byte array representation. */
+        @SuppressWarnings("ProtoParseWithRegistry")
+        @Nullable
+        @ProtoLayoutExperimental
+        public static Layout fromByteArray(@NonNull byte[] byteArray) {
+            try {
+                return fromProto(LayoutElementProto.Layout.parseFrom(byteArray));
+            } catch (InvalidProtocolBufferException e) {
+                return null;
+            }
+        }
+
+        @NonNull
+        static Layout fromProto(@NonNull LayoutElementProto.Layout proto) {
+            return new Layout(proto);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public LayoutElementProto.Layout toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Layout} */
+        public static final class Builder {
+            private final LayoutElementProto.Layout.Builder mImpl =
+                    LayoutElementProto.Layout.newBuilder();
+
+            public Builder() {}
+
+            /** Sets the root element in the layout. */
+            @NonNull
+            public Builder setRoot(@NonNull LayoutElement root) {
+                mImpl.setRoot(root.toLayoutElementProto());
+                @Nullable Fingerprint fingerprint = root.getFingerprint();
+                if (fingerprint != null) {
+                    mImpl.setFingerprint(
+                            TreeFingerprint.newBuilder().setRoot(fingerprintToProto(fingerprint)));
+                }
+                return this;
+            }
+
+            private static FingerprintProto.NodeFingerprint fingerprintToProto(
+                    Fingerprint fingerprint) {
+                FingerprintProto.NodeFingerprint.Builder builder =
+                        FingerprintProto.NodeFingerprint.newBuilder();
+                if (fingerprint.selfTypeValue() != 0) {
+                    builder.setSelfTypeValue(fingerprint.selfTypeValue());
+                }
+                if (fingerprint.selfPropsValue() != 0) {
+                    builder.setSelfPropsValue(fingerprint.selfPropsValue());
+                }
+                if (fingerprint.childNodesValue() != 0) {
+                    builder.setChildNodesValue(fingerprint.childNodesValue());
+                }
+                for (Fingerprint childNode : fingerprint.childNodes()) {
+                    builder.addChildNodes(fingerprintToProto(childNode));
+                }
+                return builder.build();
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Layout build() {
+                return Layout.fromProto(mImpl.build());
+            }
+        }
     }
 
-    /** Font style for small display text. */
-    @NonNull
-    public static FontStyle.Builder display3(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 34 : 30));
+    /** The horizontal alignment of an element within its container. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        HORIZONTAL_ALIGN_UNDEFINED,
+        HORIZONTAL_ALIGN_LEFT,
+        HORIZONTAL_ALIGN_CENTER,
+        HORIZONTAL_ALIGN_RIGHT,
+        HORIZONTAL_ALIGN_START,
+        HORIZONTAL_ALIGN_END
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface HorizontalAlignment {}
+
+    /** Horizontal alignment is undefined. */
+    public static final int HORIZONTAL_ALIGN_UNDEFINED = 0;
+
+    /** Horizontally align to the left. */
+    public static final int HORIZONTAL_ALIGN_LEFT = 1;
+
+    /** Horizontally align to center. */
+    public static final int HORIZONTAL_ALIGN_CENTER = 2;
+
+    /** Horizontally align to the right. */
+    public static final int HORIZONTAL_ALIGN_RIGHT = 3;
+
+    /** Horizontally align to the content start (left in LTR layouts, right in RTL layouts). */
+    public static final int HORIZONTAL_ALIGN_START = 4;
+
+    /** Horizontally align to the content end (right in LTR layouts, left in RTL layouts). */
+    public static final int HORIZONTAL_ALIGN_END = 5;
+
+    /** The vertical alignment of an element within its container. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        VERTICAL_ALIGN_UNDEFINED,
+        VERTICAL_ALIGN_TOP,
+        VERTICAL_ALIGN_CENTER,
+        VERTICAL_ALIGN_BOTTOM
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface VerticalAlignment {}
+
+    /** Vertical alignment is undefined. */
+    public static final int VERTICAL_ALIGN_UNDEFINED = 0;
+
+    /** Vertically align to the top. */
+    public static final int VERTICAL_ALIGN_TOP = 1;
+
+    /** Vertically align to center. */
+    public static final int VERTICAL_ALIGN_CENTER = 2;
+
+    /** Vertically align to the bottom. */
+    public static final int VERTICAL_ALIGN_BOTTOM = 3;
+
+    /** Alignment of a text element. */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({TEXT_ALIGN_UNDEFINED, TEXT_ALIGN_START, TEXT_ALIGN_CENTER, TEXT_ALIGN_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface TextAlignment {}
+
+    /** Alignment is undefined. */
+    public static final int TEXT_ALIGN_UNDEFINED = 0;
+
+    /**
+     * Align to the "start" of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element
+     * (left in LTR layouts, right in RTL layouts).
+     */
+    public static final int TEXT_ALIGN_START = 1;
+
+    /**
+     * Align to the center of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element.
+     */
+    public static final int TEXT_ALIGN_CENTER = 2;
+
+    /**
+     * Align to the "end" of the {@link androidx.wear.tiles.LayoutElementBuilders.Text} element
+     * (right in LTR layouts, left in RTL layouts).
+     */
+    public static final int TEXT_ALIGN_END = 3;
+
+    /**
+     * The anchor position of an {@link androidx.wear.tiles.LayoutElementBuilders.Arc}'s elements.
+     * This is used to specify how elements added to an {@link
+     * androidx.wear.tiles.LayoutElementBuilders.Arc} should be laid out with respect to
+     * anchor_angle.
+     *
+     * <p>As an example, assume that the following diagrams are wrapped to an arc, and each
+     * represents an {@link androidx.wear.tiles.LayoutElementBuilders.Arc} element containing a
+     * single {@link androidx.wear.tiles.LayoutElementBuilders.Text} element. The {@link
+     * androidx.wear.tiles.LayoutElementBuilders.Text} element's anchor_angle is "0" for all cases.
+     *
+     * <pre>{@code
+     * ARC_ANCHOR_START:
+     * -180                                0                                    180
+     *                                     Hello World!
+     *
+     *
+     * ARC_ANCHOR_CENTER:
+     * -180                                0                                    180
+     *                                Hello World!
+     *
+     * ARC_ANCHOR_END:
+     * -180                                0                                    180
+     *                          Hello World!
+     *
+     * }</pre>
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({ARC_ANCHOR_UNDEFINED, ARC_ANCHOR_START, ARC_ANCHOR_CENTER, ARC_ANCHOR_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ArcAnchorType {}
+
+    /** Anchor position is undefined. */
+    public static final int ARC_ANCHOR_UNDEFINED = 0;
+
+    /**
+     * Anchor at the start of the elements. This will cause elements added to an arc to begin at the
+     * given anchor_angle, and sweep around to the right.
+     */
+    public static final int ARC_ANCHOR_START = 1;
+
+    /**
+     * Anchor at the center of the elements. This will cause the center of the whole set of elements
+     * added to an arc to be pinned at the given anchor_angle.
+     */
+    public static final int ARC_ANCHOR_CENTER = 2;
+
+    /**
+     * Anchor at the end of the elements. This will cause the set of elements inside the arc to end
+     * at the specified anchor_angle, i.e. all elements should be to the left of anchor_angle.
+     */
+    public static final int ARC_ANCHOR_END = 3;
+
+    /** An extensible {@code HorizontalAlignment} property. */
+    public static final class HorizontalAlignmentProp {
+        private final AlignmentProto.HorizontalAlignmentProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        HorizontalAlignmentProp(
+                AlignmentProto.HorizontalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @HorizontalAlignment
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static HorizontalAlignmentProp fromProto(
+                @NonNull AlignmentProto.HorizontalAlignmentProp proto) {
+            return new HorizontalAlignmentProp(proto, null);
+        }
+
+        @NonNull
+        AlignmentProto.HorizontalAlignmentProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link HorizontalAlignmentProp} */
+        public static final class Builder {
+            private final AlignmentProto.HorizontalAlignmentProp.Builder mImpl =
+                    AlignmentProto.HorizontalAlignmentProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-384830516);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@HorizontalAlignment int value) {
+                mImpl.setValue(AlignmentProto.HorizontalAlignment.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public HorizontalAlignmentProp build() {
+                return new HorizontalAlignmentProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    /** Font style for large title text. */
-    @NonNull
-    public static FontStyle.Builder title1(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 26 : 24));
+    /** An extensible {@code VerticalAlignment} property. */
+    public static final class VerticalAlignmentProp {
+        private final AlignmentProto.VerticalAlignmentProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        VerticalAlignmentProp(
+                AlignmentProto.VerticalAlignmentProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @VerticalAlignment
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static VerticalAlignmentProp fromProto(
+                @NonNull AlignmentProto.VerticalAlignmentProp proto) {
+            return new VerticalAlignmentProp(proto, null);
+        }
+
+        @NonNull
+        AlignmentProto.VerticalAlignmentProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link VerticalAlignmentProp} */
+        public static final class Builder {
+            private final AlignmentProto.VerticalAlignmentProp.Builder mImpl =
+                    AlignmentProto.VerticalAlignmentProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1443510393);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@VerticalAlignment int value) {
+                mImpl.setValue(AlignmentProto.VerticalAlignment.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public VerticalAlignmentProp build() {
+                return new VerticalAlignmentProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    /** Font style for medium title text. */
-    @NonNull
-    public static FontStyle.Builder title2(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 22 : 20));
+    /** An extensible {@code TextAlignment} property. */
+    public static final class TextAlignmentProp {
+        private final AlignmentProto.TextAlignmentProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        TextAlignmentProp(
+                AlignmentProto.TextAlignmentProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @TextAlignment
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static TextAlignmentProp fromProto(@NonNull AlignmentProto.TextAlignmentProp proto) {
+            return new TextAlignmentProp(proto, null);
+        }
+
+        @NonNull
+        AlignmentProto.TextAlignmentProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link TextAlignmentProp} */
+        public static final class Builder {
+            private final AlignmentProto.TextAlignmentProp.Builder mImpl =
+                    AlignmentProto.TextAlignmentProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(797507251);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@TextAlignment int value) {
+                mImpl.setValue(AlignmentProto.TextAlignment.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public TextAlignmentProp build() {
+                return new TextAlignmentProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    /** Font style for small title text. */
-    @NonNull
-    public static FontStyle.Builder title3(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
+    /** An extensible {@code ArcAnchorType} property. */
+    public static final class ArcAnchorTypeProp {
+        private final AlignmentProto.ArcAnchorTypeProp mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ArcAnchorTypeProp(
+                AlignmentProto.ArcAnchorTypeProp impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the value. Intended for testing purposes only. */
+        @ArcAnchorType
+        public int getValue() {
+            return mImpl.getValue().getNumber();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ArcAnchorTypeProp fromProto(@NonNull AlignmentProto.ArcAnchorTypeProp proto) {
+            return new ArcAnchorTypeProp(proto, null);
+        }
+
+        @NonNull
+        AlignmentProto.ArcAnchorTypeProp toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link ArcAnchorTypeProp} */
+        public static final class Builder {
+            private final AlignmentProto.ArcAnchorTypeProp.Builder mImpl =
+                    AlignmentProto.ArcAnchorTypeProp.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1193249074);
+
+            public Builder() {}
+
+            /** Sets the value. */
+            @NonNull
+            public Builder setValue(@ArcAnchorType int value) {
+                mImpl.setValue(AlignmentProto.ArcAnchorType.forNumber(value));
+                mFingerprint.recordPropertyUpdate(1, value);
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ArcAnchorTypeProp build() {
+                return new ArcAnchorTypeProp(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
-    /** Font style for large body text. */
-    @NonNull
-    public static FontStyle.Builder body1(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
-    }
+    /** Font styles, currently set up to match Wear's font styling. */
+    public static class FontStyles {
+        private static final int LARGE_SCREEN_WIDTH_DP = 210;
 
-    /** Font style for medium body text. */
-    @NonNull
-    public static FontStyle.Builder body2(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
-    }
+        private FontStyles() {}
 
-    /** Font style for button text. */
-    @NonNull
-    public static FontStyle.Builder button(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setWeight(FONT_WEIGHT_BOLD)
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
-    }
+        private static boolean isLargeScreen(@NonNull DeviceParameters deviceParameters) {
+            return deviceParameters.getScreenWidthDp() >= LARGE_SCREEN_WIDTH_DP;
+        }
 
-    /** Font style for large caption text. */
-    @NonNull
-    public static FontStyle.Builder caption1(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
-    }
+        /** Font style for large display text. */
+        @NonNull
+        public static FontStyle.Builder display1(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 54 : 50));
+        }
 
-    /** Font style for medium caption text. */
-    @NonNull
-    public static FontStyle.Builder caption2(@NonNull DeviceParameters deviceParameters) {
-      return new FontStyle.Builder()
-              .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 14 : 12));
-    }
-  }
+        /** Font style for medium display text. */
+        @NonNull
+        public static FontStyle.Builder display2(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 44 : 40));
+        }
 
+        /** Font style for small display text. */
+        @NonNull
+        public static FontStyle.Builder display3(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 34 : 30));
+        }
+
+        /** Font style for large title text. */
+        @NonNull
+        public static FontStyle.Builder title1(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 26 : 24));
+        }
+
+        /** Font style for medium title text. */
+        @NonNull
+        public static FontStyle.Builder title2(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 22 : 20));
+        }
+
+        /** Font style for small title text. */
+        @NonNull
+        public static FontStyle.Builder title3(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
+        }
+
+        /** Font style for large body text. */
+        @NonNull
+        public static FontStyle.Builder body1(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 18 : 16));
+        }
+
+        /** Font style for medium body text. */
+        @NonNull
+        public static FontStyle.Builder body2(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+        }
+
+        /** Font style for button text. */
+        @NonNull
+        public static FontStyle.Builder button(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setWeight(FONT_WEIGHT_BOLD)
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+        }
+
+        /** Font style for large caption text. */
+        @NonNull
+        public static FontStyle.Builder caption1(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 16 : 14));
+        }
+
+        /** Font style for medium caption text. */
+        @NonNull
+        public static FontStyle.Builder caption2(@NonNull DeviceParameters deviceParameters) {
+            return new FontStyle.Builder()
+                    .setSize(DimensionBuilders.sp(isLargeScreen(deviceParameters) ? 14 : 12));
+        }
+    }
 }
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
index 65548c3..4a65eeb 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/ModifiersBuilders.java
@@ -16,12 +16,10 @@
 
 package androidx.wear.protolayout;
 
-import static androidx.annotation.Dimension.DP;
 import static androidx.wear.protolayout.expression.Preconditions.checkNotNull;
 
 import android.annotation.SuppressLint;
-import androidx.annotation.ColorInt;
-import androidx.annotation.Dimension;
+
 import androidx.annotation.FloatRange;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
@@ -29,2455 +27,2360 @@
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
 import androidx.wear.protolayout.ActionBuilders.Action;
-import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec;
-import androidx.wear.protolayout.expression.DynamicBuilders;
-import androidx.wear.protolayout.expression.DynamicBuilders.DynamicColor;
-import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
-import androidx.wear.protolayout.expression.Fingerprint;
-import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
-import androidx.wear.protolayout.proto.ColorProto;
-import androidx.wear.protolayout.proto.DimensionProto;
-import androidx.wear.protolayout.proto.ModifiersProto;
-import androidx.wear.protolayout.proto.TypesProto;
-import androidx.wear.protolayout.ActionBuilders.Action;
 import androidx.wear.protolayout.ColorBuilders.ColorProp;
 import androidx.wear.protolayout.DimensionBuilders.DpProp;
 import androidx.wear.protolayout.TypeBuilders.BoolProp;
+import androidx.wear.protolayout.expression.AnimationParameterBuilders.AnimationSpec;
+import androidx.wear.protolayout.expression.Fingerprint;
+import androidx.wear.protolayout.expression.ProtoLayoutExperimental;
+import androidx.wear.protolayout.proto.ModifiersProto;
+import androidx.wear.protolayout.proto.TypesProto;
 import androidx.wear.protolayout.protobuf.ByteString;
+
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
 import java.util.Arrays;
 
 /** Builders for modifiers for composable layout elements. */
 public final class ModifiersBuilders {
-  private ModifiersBuilders() {}
+    private ModifiersBuilders() {}
 
-  /** Prebuilt default objects for animated visibility transition animations. */
-  @ProtoLayoutExperimental
-  public static final class DefaultContentTransitions {
-    /**
-     * Fade in transition animation that fades in element when entering the layout, from fully
-     * invisible to fully visible.
-     */
-    private static final FadeInTransition FADE_IN_TRANSITION =
-        new FadeInTransition.Builder().build();
+    /** Prebuilt default objects for animated visibility transition animations. */
+    @ProtoLayoutExperimental
+    public static final class DefaultContentTransitions {
+        /**
+         * Fade in transition animation that fades in element when entering the layout, from fully
+         * invisible to fully visible.
+         */
+        private static final FadeInTransition FADE_IN_TRANSITION =
+                new FadeInTransition.Builder().build();
+
+        /**
+         * Fade in enter animation that fades in element when entering the layout, from fully
+         * invisible to fully visible.
+         */
+        private static final EnterTransition FADE_IN_ENTER_TRANSITION =
+                new EnterTransition.Builder().setFadeIn(FADE_IN_TRANSITION).build();
+
+        /**
+         * Slide in transition animation that slides in element when entering the layout into its
+         * position from the parent edge in the given direction.
+         *
+         * @param direction The direction for sliding in transition.
+         */
+        private static SlideInTransition slideInTransition(@SlideDirection int direction) {
+            return new SlideInTransition.Builder().setDirection(direction).build();
+        }
+
+        /**
+         * Enter content transition animation that fades in element when entering the layout, from
+         * fully invisible to fully visible.
+         */
+        @NonNull
+        public static EnterTransition fadeIn() {
+            return FADE_IN_ENTER_TRANSITION;
+        }
+
+        /**
+         * Enter content transition animation that slides in element when entering the layout into
+         * its position from the parent edge in the given direction.
+         */
+        @NonNull
+        public static EnterTransition slideIn(@SlideDirection int slideDirection) {
+            return new EnterTransition.Builder()
+                    .setSlideIn(slideInTransition(slideDirection))
+                    .build();
+        }
+
+        /**
+         * Enter content transition animation that fades in element when entering the layout, from
+         * fully invisible to fully visible and slides it in into its position from the parent edge
+         * in the given direction.
+         *
+         * @param slideDirection The direction for sliding in part of transition.
+         */
+        @NonNull
+        public static EnterTransition fadeInSlideIn(@SlideDirection int slideDirection) {
+            return new EnterTransition.Builder()
+                    .setFadeIn(FADE_IN_TRANSITION)
+                    .setSlideIn(slideInTransition(slideDirection))
+                    .build();
+        }
+
+        /**
+         * Fade out transition animation that fades out element when exiting the layout, from fully
+         * visible to fully invisible.
+         */
+        private static final FadeOutTransition FADE_OUT_TRANSITION =
+                new FadeOutTransition.Builder().build();
+
+        /**
+         * Fade out exit animation that fades out element when exiting the layout, from fully
+         * visible to fully invisible.
+         */
+        private static final ExitTransition FADE_OUT_EXIT_TRANSITION =
+                new ExitTransition.Builder().setFadeOut(FADE_OUT_TRANSITION).build();
+
+        /**
+         * Slide out transition animation that slides out element when exiting the layout from its
+         * position to the parent edge in the given direction.
+         *
+         * @param direction The direction for sliding out transition.
+         */
+        private static SlideOutTransition slideOutTransition(@SlideDirection int direction) {
+            return new SlideOutTransition.Builder().setDirection(direction).build();
+        }
+
+        /**
+         * Exit content transition animation that fades out element when exiting the layout, from
+         * fully visible to fully invisible.
+         */
+        @NonNull
+        public static ExitTransition fadeOut() {
+            return FADE_OUT_EXIT_TRANSITION;
+        }
+
+        /**
+         * Exit content transition animation that slides out element when exiting the layout from
+         * its position to the parent edge in the given direction.
+         */
+        @NonNull
+        public static ExitTransition slideOut(@SlideDirection int slideDirection) {
+            return new ExitTransition.Builder()
+                    .setSlideOut(slideOutTransition(slideDirection))
+                    .build();
+        }
+
+        /**
+         * Exit content transition animation that fades out element when exiting the layout, from
+         * fully visible to fully invisible and slides it out from its position to the parent edge
+         * in the given direction.
+         *
+         * @param slideDirection The direction for sliding in part of transition.
+         */
+        @NonNull
+        public static ExitTransition fadeOutSlideOut(@SlideDirection int slideDirection) {
+            return new ExitTransition.Builder()
+                    .setFadeOut(FADE_OUT_TRANSITION)
+                    .setSlideOut(slideOutTransition(slideDirection))
+                    .build();
+        }
+
+        private DefaultContentTransitions() {}
+    }
 
     /**
-     * Fade in enter animation that fades in element when entering the layout, from fully invisible
-     * to fully visible.
-     */
-    private static final EnterTransition FADE_IN_ENTER_TRANSITION =
-        new EnterTransition.Builder().setFadeIn(FADE_IN_TRANSITION).build();
-
-    /**
-     * Slide in transition animation that slides in element when entering the layout into its
-     * position from the parent edge in the given direction.
+     * The snap options to use when sliding using parent boundaries.
      *
-     * @param direction The direction for sliding in transition.
+     * @since 1.2
      */
-    private static SlideInTransition slideInTransition(@SlideDirection int direction) {
-      return new SlideInTransition.Builder().setDirection(direction).build();
-    }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        SLIDE_PARENT_SNAP_UNDEFINED,
+        SLIDE_PARENT_SNAP_TO_INSIDE,
+        SLIDE_PARENT_SNAP_TO_OUTSIDE
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @ProtoLayoutExperimental
+    public @interface SlideParentSnapOption {}
 
     /**
-     * Enter content transition animation that fades in element when entering the layout, from fully
-     * invisible to fully visible.
-     */
-    @NonNull
-    public static EnterTransition fadeIn() {
-      return FADE_IN_ENTER_TRANSITION;
-    }
-
-    /**
-     * Enter content transition animation that slides in element when entering the layout into its
-     * position from the parent edge in the given direction.
-     */
-    @NonNull
-    public static EnterTransition slideIn(@SlideDirection int slideDirection) {
-      return new EnterTransition.Builder().setSlideIn(slideInTransition(slideDirection)).build();
-    }
-
-    /**
-     * Enter content transition animation that fades in element when entering the layout, from fully
-     * invisible to fully visible and slides it in into its position from the parent edge in the
-     * given direction.
+     * The undefined snapping option.
      *
-     * @param slideDirection The direction for sliding in part of transition.
+     * @since 1.2
      */
-    @NonNull
-    public static EnterTransition fadeInSlideIn(@SlideDirection int slideDirection) {
-      return new EnterTransition.Builder()
-          .setFadeIn(FADE_IN_TRANSITION)
-          .setSlideIn(slideInTransition(slideDirection))
-          .build();
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_UNDEFINED = 0;
 
     /**
-     * Fade out transition animation that fades out element when exiting the layout, from fully
-     * visible to fully invisible.
-     */
-    private static final FadeOutTransition FADE_OUT_TRANSITION =
-        new FadeOutTransition.Builder().build();
-
-    /**
-     * Fade out exit animation that fades out element when exiting the layout, from fully visible to
-     * fully invisible.
-     */
-    private static final ExitTransition FADE_OUT_EXIT_TRANSITION =
-        new ExitTransition.Builder().setFadeOut(FADE_OUT_TRANSITION).build();
-
-    /**
-     * Slide out transition animation that slides out element when exiting the layout from its
-     * position to the parent edge in the given direction.
+     * The option that snaps insides of the element and its parent at start/end.
      *
-     * @param direction The direction for sliding out transition.
+     * @since 1.2
      */
-    private static SlideOutTransition slideOutTransition(@SlideDirection int direction) {
-      return new SlideOutTransition.Builder().setDirection(direction).build();
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_TO_INSIDE = 1;
 
     /**
-     * Exit content transition animation that fades out element when exiting the layout, from fully
-     * visible to fully invisible.
-     */
-    @NonNull
-    public static ExitTransition fadeOut() {
-      return FADE_OUT_EXIT_TRANSITION;
-    }
-
-    /**
-     * Exit content transition animation that slides out element when exiting the layout from its
-     * position to the parent edge in the given direction.
-     */
-    @NonNull
-    public static ExitTransition slideOut(@SlideDirection int slideDirection) {
-      return new ExitTransition.Builder().setSlideOut(slideOutTransition(slideDirection)).build();
-    }
-
-    /**
-     * Exit content transition animation that fades out element when exiting the layout, from fully
-     * visible to fully invisible and slides it out from its position to the parent edge in the
-     * given direction.
+     * The option that snaps outsides of the element and its parent at start/end.
      *
-     * @param slideDirection The direction for sliding in part of transition.
+     * @since 1.2
      */
-    @NonNull
-    public static ExitTransition fadeOutSlideOut(@SlideDirection int slideDirection) {
-      return new ExitTransition.Builder()
-          .setFadeOut(FADE_OUT_TRANSITION)
-          .setSlideOut(slideOutTransition(slideDirection))
-          .build();
-    }
-
-    private DefaultContentTransitions() {}
-  }
-
-  /**
-   * The snap options to use when sliding using parent boundaries.
-   *
-   * @since 1.2
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({SLIDE_PARENT_SNAP_UNDEFINED, SLIDE_PARENT_SNAP_TO_INSIDE, SLIDE_PARENT_SNAP_TO_OUTSIDE})
-  @Retention(RetentionPolicy.SOURCE)
-  @ProtoLayoutExperimental
-  public @interface SlideParentSnapOption {}
-
-  /**
-   * The undefined snapping option.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_UNDEFINED = 0;
-
-  /**
-   * The option that snaps insides of the element and its parent at start/end.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_TO_INSIDE = 1;
-
-  /**
-   * The option that snaps outsides of the element and its parent at start/end.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_TO_OUTSIDE = 2;
-
-  /**
-   * The slide direction used for slide animations on any element, from the specified point to its
-   * destination in the layout for in animation or reverse for out animation.
-   *
-   * @since 1.2
-   */
-  @RestrictTo(RestrictTo.Scope.LIBRARY)
-  @IntDef({
-    SLIDE_DIRECTION_UNDEFINED,
-    SLIDE_DIRECTION_LEFT_TO_RIGHT,
-    SLIDE_DIRECTION_RIGHT_TO_LEFT,
-    SLIDE_DIRECTION_TOP_TO_BOTTOM,
-    SLIDE_DIRECTION_BOTTOM_TO_TOP
-  })
-  @Retention(RetentionPolicy.SOURCE)
-  @ProtoLayoutExperimental
-  public @interface SlideDirection {}
-
-  /**
-   * The undefined sliding orientation.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_UNDEFINED = 0;
-
-  /**
-   * The sliding orientation that moves an element horizontally from left to the right.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_LEFT_TO_RIGHT = 1;
-
-  /**
-   * The sliding orientation that moves an element horizontally from right to the left.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_RIGHT_TO_LEFT = 2;
-
-  /**
-   * The sliding orientation that moves an element vertically from top to the bottom.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_TOP_TO_BOTTOM = 3;
-
-  /**
-   * The sliding orientation that moves an element vertically from bottom to the top.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_BOTTOM_TO_TOP = 4;
-
-  /**
-   * A modifier for an element which can have associated Actions for click events. When an element
-   * with a ClickableModifier is clicked it will fire the associated action.
-   */
-  public static final class Clickable {
-    private final ModifiersProto.Clickable mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Clickable(ModifiersProto.Clickable impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the ID associated with this action. Intended for testing purposes only. */
-    @NonNull
-    public String getId() {
-      return mImpl.getId();
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_PARENT_SNAP_TO_OUTSIDE = 2;
 
     /**
-     * Gets the action to perform when the element this modifier is attached to is clicked. Intended
-     * for testing purposes only.
-     */
-    @Nullable
-    public Action getOnClick() {
-      if (mImpl.hasOnClick()) {
-        return ActionBuilders.actionFromProto(mImpl.getOnClick());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The slide direction used for slide animations on any element, from the specified point to its
+     * destination in the layout for in animation or reverse for out animation.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Clickable fromProto(@NonNull ModifiersProto.Clickable proto) {
-      return new Clickable(proto, null);
-    }
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({
+        SLIDE_DIRECTION_UNDEFINED,
+        SLIDE_DIRECTION_LEFT_TO_RIGHT,
+        SLIDE_DIRECTION_RIGHT_TO_LEFT,
+        SLIDE_DIRECTION_TOP_TO_BOTTOM,
+        SLIDE_DIRECTION_BOTTOM_TO_TOP
+    })
+    @Retention(RetentionPolicy.SOURCE)
+    @ProtoLayoutExperimental
+    public @interface SlideDirection {}
 
     /**
-     * Returns the internal proto instance.
+     * The undefined sliding orientation.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.Clickable toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Clickable} */
-    public static final class Builder {
-      private final ModifiersProto.Clickable.Builder mImpl = ModifiersProto.Clickable.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(595587995);
-
-      public Builder() {}
-
-      /** Sets the ID associated with this action. */
-      @NonNull
-      public Builder setId(@NonNull String id) {
-        mImpl.setId(id);
-        mFingerprint.recordPropertyUpdate(1, id.hashCode());
-        return this;
-      }
-
-      /** Sets the action to perform when the element this modifier is attached to is clicked. */
-      @NonNull
-      public Builder setOnClick(@NonNull Action onClick) {
-        mImpl.setOnClick(onClick.toActionProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(onClick.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Clickable build() {
-        return new Clickable(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * A modifier for an element which has accessibility semantics associated with it. This should
-   * generally be used sparingly, and in most cases should only be applied to the top-level layout
-   * element or to Clickables.
-   */
-  public static final class Semantics {
-    private final ModifiersProto.Semantics mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Semantics(ModifiersProto.Semantics impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_UNDEFINED = 0;
 
     /**
-     * Gets the content description associated with this element. This will be dictated when the
-     * element is focused by the screen reader. Intended for testing purposes only.
-     */
-    @NonNull
-    public String getContentDescription() {
-      if (mImpl.hasContentDescription()) {
-        return mImpl.getContentDescription().getValue();
-      }
-      return mImpl.getObsoleteContentDescription();
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The sliding orientation that moves an element horizontally from left to the right.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Semantics fromProto(@NonNull ModifiersProto.Semantics proto) {
-      return new Semantics(proto, null);
-    }
-
-    @NonNull
-    ModifiersProto.Semantics toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Semantics} */
-    public static final class Builder {
-      private final ModifiersProto.Semantics.Builder mImpl = ModifiersProto.Semantics.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1479823155);
-
-      public Builder() {}
-
-      /**
-       * Sets the content description associated with this element. This will be dictated when the
-       * element is focused by the screen reader.
-       */
-      @NonNull
-      public Builder setContentDescription(@NonNull String contentDescription) {
-        mImpl.setObsoleteContentDescription(contentDescription);
-        mImpl.mergeContentDescription(
-            TypesProto.StringProp.newBuilder().setValue(contentDescription).build());
-        mFingerprint.recordPropertyUpdate(4, contentDescription.hashCode());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Semantics build() {
-        return new Semantics(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A modifier to apply padding around an element. */
-  public static final class Padding {
-    private final ModifiersProto.Padding mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Padding(ModifiersProto.Padding impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_LEFT_TO_RIGHT = 1;
 
     /**
-     * Gets the padding on the end of the content, depending on the layout direction, in DP and the
-     * value of "rtl_aware". Intended for testing purposes only.
-     */
-    @Nullable
-    public DpProp getEnd() {
-      if (mImpl.hasEnd()) {
-        return DpProp.fromProto(mImpl.getEnd());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the padding on the start of the content, depending on the layout direction, in DP and
-     * the value of "rtl_aware". Intended for testing purposes only.
-     */
-    @Nullable
-    public DpProp getStart() {
-      if (mImpl.hasStart()) {
-        return DpProp.fromProto(mImpl.getStart());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the padding at the top, in DP. Intended for testing purposes only. */
-    @Nullable
-    public DpProp getTop() {
-      if (mImpl.hasTop()) {
-        return DpProp.fromProto(mImpl.getTop());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the padding at the bottom, in DP. Intended for testing purposes only. */
-    @Nullable
-    public DpProp getBottom() {
-      if (mImpl.hasBottom()) {
-        return DpProp.fromProto(mImpl.getBottom());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets whether the start/end padding is aware of RTL support. If true, the values for start/end
-     * will follow the layout direction (i.e. start will refer to the right hand side of the
-     * container if the device is using an RTL locale). If false, start/end will always map to
-     * left/right, accordingly. Intended for testing purposes only.
-     */
-    @Nullable
-    public BoolProp getRtlAware() {
-      if (mImpl.hasRtlAware()) {
-        return BoolProp.fromProto(mImpl.getRtlAware());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The sliding orientation that moves an element horizontally from right to the left.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Padding fromProto(@NonNull ModifiersProto.Padding proto) {
-      return new Padding(proto, null);
-    }
-
-    @NonNull
-    ModifiersProto.Padding toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Padding} */
-    public static final class Builder {
-      private final ModifiersProto.Padding.Builder mImpl = ModifiersProto.Padding.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1120275440);
-
-      public Builder() {}
-
-      /**
-       * Sets the padding on the end of the content, depending on the layout direction, in DP and
-       * the value of "rtl_aware".
-       */
-      @NonNull
-      public Builder setEnd(@NonNull DpProp end) {
-        mImpl.setEnd(end.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(end.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the padding on the start of the content, depending on the layout direction, in DP and
-       * the value of "rtl_aware".
-       */
-      @NonNull
-      public Builder setStart(@NonNull DpProp start) {
-        mImpl.setStart(start.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(start.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the padding at the top, in DP.
-       */
-      @NonNull
-      public Builder setTop(@NonNull DpProp top) {
-        mImpl.setTop(top.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(top.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the padding at the bottom, in DP.
-       */
-      @NonNull
-      public Builder setBottom(@NonNull DpProp bottom) {
-        mImpl.setBottom(bottom.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(bottom.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets whether the start/end padding is aware of RTL support. If true, the values for
-       * start/end will follow the layout direction (i.e. start will refer to the right hand side of
-       * the container if the device is using an RTL locale). If false, start/end will always map to
-       * left/right, accordingly.
-       */
-      @NonNull
-      public Builder setRtlAware(@NonNull BoolProp rtlAware) {
-        mImpl.setRtlAware(rtlAware.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(rtlAware.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets whether the start/end padding is aware of RTL support. If true, the values for
-       * start/end will follow the layout direction (i.e. start will refer to the right hand side of
-       * the container if the device is using an RTL locale). If false, start/end will always map to
-       * left/right, accordingly.
-       */
-      @SuppressLint("MissingGetterMatchingBuilder")
-      @NonNull
-      public Builder setRtlAware(boolean rtlAware) {
-        mImpl.setRtlAware(TypesProto.BoolProp.newBuilder().setValue(rtlAware));
-        mFingerprint.recordPropertyUpdate(5, Boolean.hashCode(rtlAware));
-        return this;
-      }
-
-      /**
-       * Sets the padding for all sides of the content, in DP.
-       */
-      @NonNull
-      @SuppressLint("MissingGetterMatchingBuilder")
-      public Builder setAll(@NonNull DpProp value) {
-        return setStart(value).setEnd(value).setTop(value).setBottom(value);
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Padding build() {
-        return new Padding(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** A modifier to apply a border around an element. */
-  public static final class Border {
-    private final ModifiersProto.Border mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Border(ModifiersProto.Border impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the width of the border, in DP. Intended for testing purposes only. */
-    @Nullable
-    public DpProp getWidth() {
-      if (mImpl.hasWidth()) {
-        return DpProp.fromProto(mImpl.getWidth());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the color of the border. Intended for testing purposes only. */
-    @Nullable
-    public ColorProp getColor() {
-      if (mImpl.hasColor()) {
-        return ColorProp.fromProto(mImpl.getColor());
-      } else {
-        return null;
-      }
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_RIGHT_TO_LEFT = 2;
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The sliding orientation that moves an element vertically from top to the bottom.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static Border fromProto(@NonNull ModifiersProto.Border proto) {
-      return new Border(proto, null);
-    }
-
-    @NonNull
-    ModifiersProto.Border toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Border} */
-    public static final class Builder {
-      private final ModifiersProto.Border.Builder mImpl = ModifiersProto.Border.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(2085330827);
-
-      public Builder() {}
-
-      /**
-       * Sets the width of the border, in DP.
-       */
-      @NonNull
-      public Builder setWidth(@NonNull DpProp width) {
-        mImpl.setWidth(width.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the color of the border.
-       */
-      @NonNull
-      public Builder setColor(@NonNull ColorProp color) {
-        mImpl.setColor(color.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Border build() {
-        return new Border(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /** The corner of a {@link androidx.wear.tiles.LayoutElementBuilders.Box} element. */
-  public static final class Corner {
-    private final ModifiersProto.Corner mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Corner(ModifiersProto.Corner impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /** Gets the radius of the corner in DP. Intended for testing purposes only. */
-    @Nullable
-    public DpProp getRadius() {
-      if (mImpl.hasRadius()) {
-        return DpProp.fromProto(mImpl.getRadius());
-      } else {
-        return null;
-      }
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_TOP_TO_BOTTOM = 3;
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The sliding orientation that moves an element vertically from bottom to the top.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    @ProtoLayoutExperimental public static final int SLIDE_DIRECTION_BOTTOM_TO_TOP = 4;
 
-    @NonNull
-    static Corner fromProto(@NonNull ModifiersProto.Corner proto) {
-      return new Corner(proto, null);
-    }
+    /**
+     * A modifier for an element which can have associated Actions for click events. When an element
+     * with a ClickableModifier is clicked it will fire the associated action.
+     */
+    public static final class Clickable {
+        private final ModifiersProto.Clickable mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    ModifiersProto.Corner toProto() {
-      return mImpl;
-    }
+        Clickable(ModifiersProto.Clickable impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-    /** Builder for {@link Corner} */
-    public static final class Builder {
-      private final ModifiersProto.Corner.Builder mImpl = ModifiersProto.Corner.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-623478338);
+        /** Gets the ID associated with this action. Intended for testing purposes only. */
+        @NonNull
+        public String getId() {
+            return mImpl.getId();
+        }
 
-      public Builder() {}
+        /**
+         * Gets the action to perform when the element this modifier is attached to is clicked.
+         * Intended for testing purposes only.
+         */
+        @Nullable
+        public Action getOnClick() {
+            if (mImpl.hasOnClick()) {
+                return ActionBuilders.actionFromProto(mImpl.getOnClick());
+            } else {
+                return null;
+            }
+        }
 
-      /**
-       * Sets the radius of the corner in DP.
-       */
-      @NonNull
-      public Builder setRadius(@NonNull DpProp radius) {
-        mImpl.setRadius(radius.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(radius.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Corner build() {
-        return new Corner(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        @NonNull
+        static Clickable fromProto(@NonNull ModifiersProto.Clickable proto) {
+            return new Clickable(proto, null);
+        }
 
-  /** A modifier to apply a background to an element. */
-  public static final class Background {
-    private final ModifiersProto.Background mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.Clickable toProto() {
+            return mImpl;
+        }
 
-    Background(ModifiersProto.Background impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+        /** Builder for {@link Clickable} */
+        public static final class Builder {
+            private final ModifiersProto.Clickable.Builder mImpl =
+                    ModifiersProto.Clickable.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(595587995);
+
+            public Builder() {}
+
+            /** Sets the ID associated with this action. */
+            @NonNull
+            public Builder setId(@NonNull String id) {
+                mImpl.setId(id);
+                mFingerprint.recordPropertyUpdate(1, id.hashCode());
+                return this;
+            }
+
+            /**
+             * Sets the action to perform when the element this modifier is attached to is clicked.
+             */
+            @NonNull
+            public Builder setOnClick(@NonNull Action onClick) {
+                mImpl.setOnClick(onClick.toActionProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(onClick.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Clickable build() {
+                return new Clickable(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets the background color for this element. If not defined, defaults to being transparent.
-     * Intended for testing purposes only.
+     * A modifier for an element which has accessibility semantics associated with it. This should
+     * generally be used sparingly, and in most cases should only be applied to the top-level layout
+     * element or to Clickables.
      */
-    @Nullable
-    public ColorProp getColor() {
-      if (mImpl.hasColor()) {
-        return ColorProp.fromProto(mImpl.getColor());
-      } else {
-        return null;
-      }
+    public static final class Semantics {
+        private final ModifiersProto.Semantics mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Semantics(ModifiersProto.Semantics impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the content description associated with this element. This will be dictated when the
+         * element is focused by the screen reader. Intended for testing purposes only.
+         */
+        @NonNull
+        public String getContentDescription() {
+            if (mImpl.hasContentDescription()) {
+                return mImpl.getContentDescription().getValue();
+            }
+            return mImpl.getObsoleteContentDescription();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Semantics fromProto(@NonNull ModifiersProto.Semantics proto) {
+            return new Semantics(proto, null);
+        }
+
+        @NonNull
+        ModifiersProto.Semantics toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Semantics} */
+        public static final class Builder {
+            private final ModifiersProto.Semantics.Builder mImpl =
+                    ModifiersProto.Semantics.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1479823155);
+
+            public Builder() {}
+
+            /**
+             * Sets the content description associated with this element. This will be dictated when
+             * the element is focused by the screen reader.
+             */
+            @NonNull
+            public Builder setContentDescription(@NonNull String contentDescription) {
+                mImpl.setObsoleteContentDescription(contentDescription);
+                mImpl.mergeContentDescription(
+                        TypesProto.StringProp.newBuilder().setValue(contentDescription).build());
+                mFingerprint.recordPropertyUpdate(4, contentDescription.hashCode());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Semantics build() {
+                return new Semantics(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A modifier to apply padding around an element. */
+    public static final class Padding {
+        private final ModifiersProto.Padding mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Padding(ModifiersProto.Padding impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the padding on the end of the content, depending on the layout direction, in DP and
+         * the value of "rtl_aware". Intended for testing purposes only.
+         */
+        @Nullable
+        public DpProp getEnd() {
+            if (mImpl.hasEnd()) {
+                return DpProp.fromProto(mImpl.getEnd());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the padding on the start of the content, depending on the layout direction, in DP
+         * and the value of "rtl_aware". Intended for testing purposes only.
+         */
+        @Nullable
+        public DpProp getStart() {
+            if (mImpl.hasStart()) {
+                return DpProp.fromProto(mImpl.getStart());
+            } else {
+                return null;
+            }
+        }
+
+        /** Gets the padding at the top, in DP. Intended for testing purposes only. */
+        @Nullable
+        public DpProp getTop() {
+            if (mImpl.hasTop()) {
+                return DpProp.fromProto(mImpl.getTop());
+            } else {
+                return null;
+            }
+        }
+
+        /** Gets the padding at the bottom, in DP. Intended for testing purposes only. */
+        @Nullable
+        public DpProp getBottom() {
+            if (mImpl.hasBottom()) {
+                return DpProp.fromProto(mImpl.getBottom());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets whether the start/end padding is aware of RTL support. If true, the values for
+         * start/end will follow the layout direction (i.e. start will refer to the right hand side
+         * of the container if the device is using an RTL locale). If false, start/end will always
+         * map to left/right, accordingly. Intended for testing purposes only.
+         */
+        @Nullable
+        public BoolProp getRtlAware() {
+            if (mImpl.hasRtlAware()) {
+                return BoolProp.fromProto(mImpl.getRtlAware());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Padding fromProto(@NonNull ModifiersProto.Padding proto) {
+            return new Padding(proto, null);
+        }
+
+        @NonNull
+        ModifiersProto.Padding toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Padding} */
+        public static final class Builder {
+            private final ModifiersProto.Padding.Builder mImpl =
+                    ModifiersProto.Padding.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1120275440);
+
+            public Builder() {}
+
+            /**
+             * Sets the padding on the end of the content, depending on the layout direction, in DP
+             * and the value of "rtl_aware".
+             */
+            @NonNull
+            public Builder setEnd(@NonNull DpProp end) {
+                mImpl.setEnd(end.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(end.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the padding on the start of the content, depending on the layout direction, in
+             * DP and the value of "rtl_aware".
+             */
+            @NonNull
+            public Builder setStart(@NonNull DpProp start) {
+                mImpl.setStart(start.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(start.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the padding at the top, in DP. */
+            @NonNull
+            public Builder setTop(@NonNull DpProp top) {
+                mImpl.setTop(top.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(top.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the padding at the bottom, in DP. */
+            @NonNull
+            public Builder setBottom(@NonNull DpProp bottom) {
+                mImpl.setBottom(bottom.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(bottom.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets whether the start/end padding is aware of RTL support. If true, the values for
+             * start/end will follow the layout direction (i.e. start will refer to the right hand
+             * side of the container if the device is using an RTL locale). If false, start/end will
+             * always map to left/right, accordingly.
+             */
+            @NonNull
+            public Builder setRtlAware(@NonNull BoolProp rtlAware) {
+                mImpl.setRtlAware(rtlAware.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(rtlAware.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets whether the start/end padding is aware of RTL support. If true, the values for
+             * start/end will follow the layout direction (i.e. start will refer to the right hand
+             * side of the container if the device is using an RTL locale). If false, start/end will
+             * always map to left/right, accordingly.
+             */
+            @SuppressLint("MissingGetterMatchingBuilder")
+            @NonNull
+            public Builder setRtlAware(boolean rtlAware) {
+                mImpl.setRtlAware(TypesProto.BoolProp.newBuilder().setValue(rtlAware));
+                mFingerprint.recordPropertyUpdate(5, Boolean.hashCode(rtlAware));
+                return this;
+            }
+
+            /** Sets the padding for all sides of the content, in DP. */
+            @NonNull
+            @SuppressLint("MissingGetterMatchingBuilder")
+            public Builder setAll(@NonNull DpProp value) {
+                return setStart(value).setEnd(value).setTop(value).setBottom(value);
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Padding build() {
+                return new Padding(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A modifier to apply a border around an element. */
+    public static final class Border {
+        private final ModifiersProto.Border mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Border(ModifiersProto.Border impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the width of the border, in DP. Intended for testing purposes only. */
+        @Nullable
+        public DpProp getWidth() {
+            if (mImpl.hasWidth()) {
+                return DpProp.fromProto(mImpl.getWidth());
+            } else {
+                return null;
+            }
+        }
+
+        /** Gets the color of the border. Intended for testing purposes only. */
+        @Nullable
+        public ColorProp getColor() {
+            if (mImpl.hasColor()) {
+                return ColorProp.fromProto(mImpl.getColor());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Border fromProto(@NonNull ModifiersProto.Border proto) {
+            return new Border(proto, null);
+        }
+
+        @NonNull
+        ModifiersProto.Border toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Border} */
+        public static final class Builder {
+            private final ModifiersProto.Border.Builder mImpl = ModifiersProto.Border.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(2085330827);
+
+            public Builder() {}
+
+            /** Sets the width of the border, in DP. */
+            @NonNull
+            public Builder setWidth(@NonNull DpProp width) {
+                mImpl.setWidth(width.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(width.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the color of the border.
+             *
+             * <p>
+             * This field is made bindable and will use the dynamic value (if set) from version 1.2
+             * Older renderers will still consider this field as non-bindable and will use the
+             * static value.
+             */
+            @NonNull
+            public Builder setColor(@NonNull ColorProp color) {
+                mImpl.setColor(color.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Border build() {
+                return new Border(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** The corner of a {@link androidx.wear.tiles.LayoutElementBuilders.Box} element. */
+    public static final class Corner {
+        private final ModifiersProto.Corner mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Corner(ModifiersProto.Corner impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /** Gets the radius of the corner in DP. Intended for testing purposes only. */
+        @Nullable
+        public DpProp getRadius() {
+            if (mImpl.hasRadius()) {
+                return DpProp.fromProto(mImpl.getRadius());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Corner fromProto(@NonNull ModifiersProto.Corner proto) {
+            return new Corner(proto, null);
+        }
+
+        @NonNull
+        ModifiersProto.Corner toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Corner} */
+        public static final class Builder {
+            private final ModifiersProto.Corner.Builder mImpl = ModifiersProto.Corner.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-623478338);
+
+            public Builder() {}
+
+            /** Sets the radius of the corner in DP. */
+            @NonNull
+            public Builder setRadius(@NonNull DpProp radius) {
+                mImpl.setRadius(radius.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(radius.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Corner build() {
+                return new Corner(mImpl.build(), mFingerprint);
+            }
+        }
+    }
+
+    /** A modifier to apply a background to an element. */
+    public static final class Background {
+        private final ModifiersProto.Background mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        Background(ModifiersProto.Background impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the background color for this element. If not defined, defaults to being
+         * transparent. Intended for testing purposes only.
+         */
+        @Nullable
+        public ColorProp getColor() {
+            if (mImpl.hasColor()) {
+                return ColorProp.fromProto(mImpl.getColor());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the corner properties of this element. This only affects the drawing of this element
+         * if it has a background color or border. If not defined, defaults to having a square
+         * corner. Intended for testing purposes only.
+         */
+        @Nullable
+        public Corner getCorner() {
+            if (mImpl.hasCorner()) {
+                return Corner.fromProto(mImpl.getCorner());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static Background fromProto(@NonNull ModifiersProto.Background proto) {
+            return new Background(proto, null);
+        }
+
+        @NonNull
+        ModifiersProto.Background toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Background} */
+        public static final class Builder {
+            private final ModifiersProto.Background.Builder mImpl =
+                    ModifiersProto.Background.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(374507572);
+
+            public Builder() {}
+
+            /**
+             * Sets the background color for this element. If not defined, defaults to being
+             * transparent.
+             * <p>
+             * This field is made bindable and supports dynamic colors from version 1.2
+             * Older renderers will still consider this field as non-bindable and will use the
+             * static value.
+             */
+            @NonNull
+            public Builder setColor(@NonNull ColorProp color) {
+                mImpl.setColor(color.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the corner properties of this element. This only affects the drawing of this
+             * element if it has a background color or border. If not defined, defaults to having a
+             * square corner.
+             */
+            @NonNull
+            public Builder setCorner(@NonNull Corner corner) {
+                mImpl.setCorner(corner.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(corner.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Background build() {
+                return new Background(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets the corner properties of this element. This only affects the drawing of this element if
-     * it has a background color or border. If not defined, defaults to having a square corner.
-     * Intended for testing purposes only.
+     * Metadata about an element. For use by libraries building higher-level components only. This
+     * can be used to track component metadata.
      */
-    @Nullable
-    public Corner getCorner() {
-      if (mImpl.hasCorner()) {
-        return Corner.fromProto(mImpl.getCorner());
-      } else {
-        return null;
-      }
+    public static final class ElementMetadata {
+        private final ModifiersProto.ElementMetadata mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ElementMetadata(ModifiersProto.ElementMetadata impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets property describing the element with which it is associated. For use by libraries
+         * building higher-level components only. This can be used to track component metadata.
+         */
+        @NonNull
+        public byte[] getTagData() {
+            return mImpl.getTagData().toByteArray();
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static ElementMetadata fromProto(@NonNull ModifiersProto.ElementMetadata proto) {
+            return new ElementMetadata(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.ElementMetadata toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link ElementMetadata} */
+        public static final class Builder {
+            private final ModifiersProto.ElementMetadata.Builder mImpl =
+                    ModifiersProto.ElementMetadata.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-589294723);
+
+            public Builder() {}
+
+            /**
+             * Sets property describing the element with which it is associated. For use by
+             * libraries building higher-level components only. This can be used to track component
+             * metadata.
+             */
+            @NonNull
+            public Builder setTagData(@NonNull byte[] tagData) {
+                mImpl.setTagData(ByteString.copyFrom(tagData));
+                mFingerprint.recordPropertyUpdate(1, Arrays.hashCode(tagData));
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ElementMetadata build() {
+                return new ElementMetadata(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
+     * {@link Modifiers} for an element. These may change the way they are drawn (e.g. {@link
+     * Padding} or {@link Background}), or change their behaviour (e.g. {@link Clickable}, or {@link
+     * Semantics}).
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    public static final class Modifiers {
+        private final ModifiersProto.Modifiers mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    static Background fromProto(@NonNull ModifiersProto.Background proto) {
-      return new Background(proto, null);
-    }
+        Modifiers(ModifiersProto.Modifiers impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-    @NonNull
-    ModifiersProto.Background toProto() {
-      return mImpl;
-    }
+        /**
+         * Gets the clickable property of the modified element. It allows its wrapped element to
+         * have actions associated with it, which will be executed when the element is tapped.
+         * Intended for testing purposes only.
+         */
+        @Nullable
+        public Clickable getClickable() {
+            if (mImpl.hasClickable()) {
+                return Clickable.fromProto(mImpl.getClickable());
+            } else {
+                return null;
+            }
+        }
 
-    /** Builder for {@link Background} */
-    public static final class Builder {
-      private final ModifiersProto.Background.Builder mImpl =
-          ModifiersProto.Background.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(374507572);
+        /**
+         * Gets the semantics of the modified element. This can be used to add metadata to the
+         * modified element (eg. screen reader content descriptions). Intended for testing purposes
+         * only.
+         */
+        @Nullable
+        public Semantics getSemantics() {
+            if (mImpl.hasSemantics()) {
+                return Semantics.fromProto(mImpl.getSemantics());
+            } else {
+                return null;
+            }
+        }
 
-      public Builder() {}
+        /** Gets the padding of the modified element. Intended for testing purposes only. */
+        @Nullable
+        public Padding getPadding() {
+            if (mImpl.hasPadding()) {
+                return Padding.fromProto(mImpl.getPadding());
+            } else {
+                return null;
+            }
+        }
 
-      /**
-       * Sets the background color for this element. If not defined, defaults to being transparent.
-       */
-      @NonNull
-      public Builder setColor(@NonNull ColorProp color) {
-        mImpl.setColor(color.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(color.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
+        /** Gets the border of the modified element. Intended for testing purposes only. */
+        @Nullable
+        public Border getBorder() {
+            if (mImpl.hasBorder()) {
+                return Border.fromProto(mImpl.getBorder());
+            } else {
+                return null;
+            }
+        }
 
-      /**
-       * Sets the corner properties of this element. This only affects the drawing of this element
-       * if it has a background color or border. If not defined, defaults to having a square corner.
-       */
-      @NonNull
-      public Builder setCorner(@NonNull Corner corner) {
-        mImpl.setCorner(corner.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(corner.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
+        /**
+         * Gets the background (with optional corner radius) of the modified element. Intended for
+         * testing purposes only.
+         */
+        @Nullable
+        public Background getBackground() {
+            if (mImpl.hasBackground()) {
+                return Background.fromProto(mImpl.getBackground());
+            } else {
+                return null;
+            }
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Background build() {
-        return new Background(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        /**
+         * Gets metadata about an element. For use by libraries building higher-level components
+         * only. This can be used to track component metadata.
+         */
+        @Nullable
+        public ElementMetadata getMetadata() {
+            if (mImpl.hasMetadata()) {
+                return ElementMetadata.fromProto(mImpl.getMetadata());
+            } else {
+                return null;
+            }
+        }
 
-  /**
-   * Metadata about an element. For use by libraries building higher-level components only. This can
-   * be used to track component metadata.
-   */
-  public static final class ElementMetadata {
-    private final ModifiersProto.ElementMetadata mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        /**
+         * Gets the content transition of an element. Any update to the element or its children will
+         * trigger this animation for this element and everything underneath it.
+         *
+         * @since 1.2
+         */
+        @ProtoLayoutExperimental
+        @Nullable
+        public AnimatedVisibility getContentUpdateAnimation() {
+            if (mImpl.hasContentUpdateAnimation()) {
+                return AnimatedVisibility.fromProto(mImpl.getContentUpdateAnimation());
+            } else {
+                return null;
+            }
+        }
 
-    ElementMetadata(
-        ModifiersProto.ElementMetadata impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /**
+         * Creates a new wrapper instance from the proto. Intended for testing purposes only. An
+         * object created using this method can't be added to any other wrapper.
+         */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static Modifiers fromProto(@NonNull ModifiersProto.Modifiers proto) {
+            return new Modifiers(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.Modifiers toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link Modifiers} */
+        public static final class Builder {
+            private final ModifiersProto.Modifiers.Builder mImpl =
+                    ModifiersProto.Modifiers.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-170942531);
+
+            public Builder() {}
+
+            /**
+             * Sets the clickable property of the modified element. It allows its wrapped element to
+             * have actions associated with it, which will be executed when the element is tapped.
+             */
+            @NonNull
+            public Builder setClickable(@NonNull Clickable clickable) {
+                mImpl.setClickable(clickable.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the semantics of the modified element. This can be used to add metadata to the
+             * modified element (eg. screen reader content descriptions).
+             */
+            @NonNull
+            public Builder setSemantics(@NonNull Semantics semantics) {
+                mImpl.setSemantics(semantics.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(semantics.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the padding of the modified element. */
+            @NonNull
+            public Builder setPadding(@NonNull Padding padding) {
+                mImpl.setPadding(padding.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(padding.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the border of the modified element. */
+            @NonNull
+            public Builder setBorder(@NonNull Border border) {
+                mImpl.setBorder(border.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        4, checkNotNull(border.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Sets the background (with optional corner radius) of the modified element. */
+            @NonNull
+            public Builder setBackground(@NonNull Background background) {
+                mImpl.setBackground(background.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        5, checkNotNull(background.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets metadata about an element. For use by libraries building higher-level components
+             * only. This can be used to track component metadata.
+             */
+            @NonNull
+            public Builder setMetadata(@NonNull ElementMetadata metadata) {
+                mImpl.setMetadata(metadata.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        6, checkNotNull(metadata.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the content transition of an element. Any update to the element or its children
+             * will trigger this animation for this element and everything underneath it.
+             *
+             * @since 1.2
+             */
+            @ProtoLayoutExperimental
+            @NonNull
+            public Builder setContentUpdateAnimation(
+                    @NonNull AnimatedVisibility contentUpdateAnimation) {
+                mImpl.setContentUpdateAnimation(contentUpdateAnimation.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        7,
+                        checkNotNull(contentUpdateAnimation.getFingerprint())
+                                .aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public Modifiers build() {
+                return new Modifiers(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets property describing the element with which it is associated. For use by libraries
-     * building higher-level components only. This can be used to track component metadata.
-     */
-    @NonNull
-    public byte[] getTagData() {
-      return mImpl.getTagData().toByteArray();
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static ElementMetadata fromProto(@NonNull ModifiersProto.ElementMetadata proto) {
-      return new ElementMetadata(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.ElementMetadata toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link ElementMetadata} */
-    public static final class Builder {
-      private final ModifiersProto.ElementMetadata.Builder mImpl =
-          ModifiersProto.ElementMetadata.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-589294723);
-
-      public Builder() {}
-
-      /**
-       * Sets property describing the element with which it is associated. For use by libraries
-       * building higher-level components only. This can be used to track component metadata.
-       */
-      @NonNull
-      public Builder setTagData(@NonNull byte[] tagData) {
-        mImpl.setTagData(ByteString.copyFrom(tagData));
-        mFingerprint.recordPropertyUpdate(1, Arrays.hashCode(tagData));
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ElementMetadata build() {
-        return new ElementMetadata(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * {@link Modifiers} for an element. These may change the way they are drawn (e.g. {@link Padding}
-   * or {@link Background}), or change their behaviour (e.g. {@link Clickable}, or {@link
-   * Semantics}).
-   */
-  public static final class Modifiers {
-    private final ModifiersProto.Modifiers mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    Modifiers(ModifiersProto.Modifiers impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the clickable property of the modified element. It allows its wrapped element to have
-     * actions associated with it, which will be executed when the element is tapped. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Clickable getClickable() {
-      if (mImpl.hasClickable()) {
-        return Clickable.fromProto(mImpl.getClickable());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the semantics of the modified element. This can be used to add metadata to the modified
-     * element (eg. screen reader content descriptions). Intended for testing purposes only.
-     */
-    @Nullable
-    public Semantics getSemantics() {
-      if (mImpl.hasSemantics()) {
-        return Semantics.fromProto(mImpl.getSemantics());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the padding of the modified element. Intended for testing purposes only. */
-    @Nullable
-    public Padding getPadding() {
-      if (mImpl.hasPadding()) {
-        return Padding.fromProto(mImpl.getPadding());
-      } else {
-        return null;
-      }
-    }
-
-    /** Gets the border of the modified element. Intended for testing purposes only. */
-    @Nullable
-    public Border getBorder() {
-      if (mImpl.hasBorder()) {
-        return Border.fromProto(mImpl.getBorder());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the background (with optional corner radius) of the modified element. Intended for
-     * testing purposes only.
-     */
-    @Nullable
-    public Background getBackground() {
-      if (mImpl.hasBackground()) {
-        return Background.fromProto(mImpl.getBackground());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets metadata about an element. For use by libraries building higher-level components only.
-     * This can be used to track component metadata.
-     */
-    @Nullable
-    public ElementMetadata getMetadata() {
-      if (mImpl.hasMetadata()) {
-        return ElementMetadata.fromProto(mImpl.getMetadata());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the content transition of an element. Any update to the element or its children will
-     * trigger this animation for this element and everything underneath it.
+     * The content transition of an element. Any update to the element or its children will trigger
+     * this animation for this element and everything underneath it.
      *
      * @since 1.2
      */
     @ProtoLayoutExperimental
-    @Nullable
-    public AnimatedVisibility getContentUpdateAnimation() {
-      if (mImpl.hasContentUpdateAnimation()) {
-        return AnimatedVisibility.fromProto(mImpl.getContentUpdateAnimation());
-      } else {
-        return null;
-      }
+    public static final class AnimatedVisibility {
+        private final ModifiersProto.AnimatedVisibility mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        AnimatedVisibility(
+                ModifiersProto.AnimatedVisibility impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the content transition that is triggered when element enters the layout.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public EnterTransition getEnterTransition() {
+            if (mImpl.hasEnterTransition()) {
+                return EnterTransition.fromProto(mImpl.getEnterTransition());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the content transition that is triggered when element exits the layout. Note that
+         * indefinite exit animations are ignored.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public ExitTransition getExitTransition() {
+            if (mImpl.hasExitTransition()) {
+                return ExitTransition.fromProto(mImpl.getExitTransition());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static AnimatedVisibility fromProto(
+                @NonNull ModifiersProto.AnimatedVisibility proto,
+                @Nullable Fingerprint fingerprint) {
+            return new AnimatedVisibility(proto, fingerprint);
+        }
+
+        @NonNull
+        static AnimatedVisibility fromProto(@NonNull ModifiersProto.AnimatedVisibility proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.AnimatedVisibility toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "AnimatedVisibility{"
+                    + "enterTransition="
+                    + getEnterTransition()
+                    + ", exitTransition="
+                    + getExitTransition()
+                    + "}";
+        }
+
+        /** Builder for {@link AnimatedVisibility} */
+        public static final class Builder {
+            private final ModifiersProto.AnimatedVisibility.Builder mImpl =
+                    ModifiersProto.AnimatedVisibility.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1372451979);
+
+            public Builder() {}
+
+            /**
+             * Sets the content transition that is triggered when element enters the layout.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setEnterTransition(@NonNull EnterTransition enterTransition) {
+                mImpl.setEnterTransition(enterTransition.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(enterTransition.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the content transition that is triggered when element exits the layout. Note
+             * that indefinite exit animations are ignored.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setExitTransition(@NonNull ExitTransition exitTransition) {
+                mImpl.setExitTransition(exitTransition.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(exitTransition.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public AnimatedVisibility build() {
+                return new AnimatedVisibility(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    /**
-     * Creates a new wrapper instance from the proto. Intended for testing purposes only. An object
-     * created using this method can't be added to any other wrapper.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static Modifiers fromProto(@NonNull ModifiersProto.Modifiers proto) {
-      return new Modifiers(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.Modifiers toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link Modifiers} */
-    public static final class Builder {
-      private final ModifiersProto.Modifiers.Builder mImpl = ModifiersProto.Modifiers.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-170942531);
-
-      public Builder() {}
-
-      /**
-       * Sets the clickable property of the modified element. It allows its wrapped element to have
-       * actions associated with it, which will be executed when the element is tapped.
-       */
-      @NonNull
-      public Builder setClickable(@NonNull Clickable clickable) {
-        mImpl.setClickable(clickable.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the semantics of the modified element. This can be used to add metadata to the
-       * modified element (eg. screen reader content descriptions).
-       */
-      @NonNull
-      public Builder setSemantics(@NonNull Semantics semantics) {
-        mImpl.setSemantics(semantics.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(semantics.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets the padding of the modified element. */
-      @NonNull
-      public Builder setPadding(@NonNull Padding padding) {
-        mImpl.setPadding(padding.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(padding.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets the border of the modified element. */
-      @NonNull
-      public Builder setBorder(@NonNull Border border) {
-        mImpl.setBorder(border.toProto());
-        mFingerprint.recordPropertyUpdate(
-            4, checkNotNull(border.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Sets the background (with optional corner radius) of the modified element. */
-      @NonNull
-      public Builder setBackground(@NonNull Background background) {
-        mImpl.setBackground(background.toProto());
-        mFingerprint.recordPropertyUpdate(
-            5, checkNotNull(background.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets metadata about an element. For use by libraries building higher-level components only.
-       * This can be used to track component metadata.
-       */
-      @NonNull
-      public Builder setMetadata(@NonNull ElementMetadata metadata) {
-        mImpl.setMetadata(metadata.toProto());
-        mFingerprint.recordPropertyUpdate(
-            6, checkNotNull(metadata.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the content transition of an element. Any update to the element or its children will
-       * trigger this animation for this element and everything underneath it.
-       *
-       * @since 1.2
-       */
-      @ProtoLayoutExperimental
-      @NonNull
-      public Builder setContentUpdateAnimation(@NonNull AnimatedVisibility contentUpdateAnimation) {
-        mImpl.setContentUpdateAnimation(contentUpdateAnimation.toProto());
-        mFingerprint.recordPropertyUpdate(
-            7, checkNotNull(contentUpdateAnimation.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public Modifiers build() {
-        return new Modifiers(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The content transition of an element. Any update to the element or its children will trigger
-   * this animation for this element and everything underneath it.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class AnimatedVisibility {
-    private final ModifiersProto.AnimatedVisibility mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    AnimatedVisibility(ModifiersProto.AnimatedVisibility impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the content transition that is triggered when element enters the layout.
+     * The content transition that is triggered when element enters the layout.
      *
      * @since 1.2
      */
-    @Nullable
-    public EnterTransition getEnterTransition() {
-      if (mImpl.hasEnterTransition()) {
-        return EnterTransition.fromProto(mImpl.getEnterTransition());
-      } else {
-        return null;
-      }
+    @ProtoLayoutExperimental
+    public static final class EnterTransition {
+        private final ModifiersProto.EnterTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        EnterTransition(ModifiersProto.EnterTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the fading in animation for content transition of an element and its children
+         * happening when entering the layout.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public FadeInTransition getFadeIn() {
+            if (mImpl.hasFadeIn()) {
+                return FadeInTransition.fromProto(mImpl.getFadeIn());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the sliding in animation for content transition of an element and its children
+         * happening when entering the layout.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public SlideInTransition getSlideIn() {
+            if (mImpl.hasSlideIn()) {
+                return SlideInTransition.fromProto(mImpl.getSlideIn());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static EnterTransition fromProto(
+                @NonNull ModifiersProto.EnterTransition proto, @Nullable Fingerprint fingerprint) {
+            return new EnterTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static EnterTransition fromProto(@NonNull ModifiersProto.EnterTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.EnterTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "EnterTransition{" + "fadeIn=" + getFadeIn() + ", slideIn=" + getSlideIn() + "}";
+        }
+
+        /** Builder for {@link EnterTransition} */
+        public static final class Builder {
+            private final ModifiersProto.EnterTransition.Builder mImpl =
+                    ModifiersProto.EnterTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1732205279);
+
+            public Builder() {}
+
+            /**
+             * Sets the fading in animation for content transition of an element and its children
+             * happening when entering the layout.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setFadeIn(@NonNull FadeInTransition fadeIn) {
+                mImpl.setFadeIn(fadeIn.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(fadeIn.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the sliding in animation for content transition of an element and its children
+             * happening when entering the layout.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setSlideIn(@NonNull SlideInTransition slideIn) {
+                mImpl.setSlideIn(slideIn.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(slideIn.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public EnterTransition build() {
+                return new EnterTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets the content transition that is triggered when element exits the layout. Note that
-     * indefinite exit animations are ignored.
+     * The fading animation for content transition of an element and its children, from the
+     * specified starting alpha to fully visible.
      *
      * @since 1.2
      */
-    @Nullable
-    public ExitTransition getExitTransition() {
-      if (mImpl.hasExitTransition()) {
-        return ExitTransition.fromProto(mImpl.getExitTransition());
-      } else {
-        return null;
-      }
+    @ProtoLayoutExperimental
+    public static final class FadeInTransition {
+        private final ModifiersProto.FadeInTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        FadeInTransition(ModifiersProto.FadeInTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the starting alpha of the fade in transition. It should be between 0 and 1. If not
+         * set, defaults to fully transparent, i.e. 0.
+         *
+         * @since 1.2
+         */
+        @FloatRange(from = 0.0, to = 1.0)
+        public float getInitialAlpha() {
+            return mImpl.getInitialAlpha();
+        }
+
+        /**
+         * Gets the animation parameters for duration, delay, etc.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public AnimationSpec getAnimationSpec() {
+            if (mImpl.hasAnimationSpec()) {
+                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static FadeInTransition fromProto(
+                @NonNull ModifiersProto.FadeInTransition proto, @Nullable Fingerprint fingerprint) {
+            return new FadeInTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static FadeInTransition fromProto(@NonNull ModifiersProto.FadeInTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.FadeInTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "FadeInTransition{"
+                    + "initialAlpha="
+                    + getInitialAlpha()
+                    + ", animationSpec="
+                    + getAnimationSpec()
+                    + "}";
+        }
+
+        /** Builder for {@link FadeInTransition} */
+        public static final class Builder {
+            private final ModifiersProto.FadeInTransition.Builder mImpl =
+                    ModifiersProto.FadeInTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(1430024488);
+
+            public Builder() {}
+
+            /**
+             * Sets the starting alpha of the fade in transition. It should be between 0 and 1. If
+             * not set, defaults to fully transparent, i.e. 0.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setInitialAlpha(@FloatRange(from = 0.0, to = 1.0) float initialAlpha) {
+                mImpl.setInitialAlpha(initialAlpha);
+                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(initialAlpha));
+                return this;
+            }
+
+            /**
+             * Sets the animation parameters for duration, delay, etc.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
+                mImpl.setAnimationSpec(animationSpec.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public FadeInTransition build() {
+                return new FadeInTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static AnimatedVisibility fromProto(
-        @NonNull ModifiersProto.AnimatedVisibility proto, @Nullable Fingerprint fingerprint) {
-      return new AnimatedVisibility(proto, fingerprint);
-    }
-
-    @NonNull
-    static AnimatedVisibility fromProto(@NonNull ModifiersProto.AnimatedVisibility proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.AnimatedVisibility toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "AnimatedVisibility{"
-              + "enterTransition="
-              + getEnterTransition()
-              + ", exitTransition="
-              + getExitTransition()
-              + "}";
-    }
-
-    /** Builder for {@link AnimatedVisibility} */
-    public static final class Builder {
-      private final ModifiersProto.AnimatedVisibility.Builder mImpl =
-          ModifiersProto.AnimatedVisibility.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1372451979);
-
-      public Builder() {}
-
-      /**
-       * Sets the content transition that is triggered when element enters the layout.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setEnterTransition(@NonNull EnterTransition enterTransition) {
-        mImpl.setEnterTransition(enterTransition.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(enterTransition.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the content transition that is triggered when element exits the layout. Note that
-       * indefinite exit animations are ignored.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setExitTransition(@NonNull ExitTransition exitTransition) {
-        mImpl.setExitTransition(exitTransition.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(exitTransition.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public AnimatedVisibility build() {
-        return new AnimatedVisibility(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The content transition that is triggered when element enters the layout.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class EnterTransition {
-    private final ModifiersProto.EnterTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    EnterTransition(ModifiersProto.EnterTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the fading in animation for content transition of an element and its children happening
-     * when entering the layout.
+     * The sliding in animation for content transition of an element and its children.
      *
      * @since 1.2
      */
-    @Nullable
-    public FadeInTransition getFadeIn() {
-      if (mImpl.hasFadeIn()) {
-        return FadeInTransition.fromProto(mImpl.getFadeIn());
-      } else {
-        return null;
-      }
+    @ProtoLayoutExperimental
+    public static final class SlideInTransition {
+        private final ModifiersProto.SlideInTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SlideInTransition(
+                ModifiersProto.SlideInTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the slide direction used for slide animations on any element, from the specified
+         * point to its destination in the layout. If not set, defaults to horizontal from left to
+         * the right.
+         *
+         * @since 1.2
+         */
+        @SlideDirection
+        public int getDirection() {
+            return mImpl.getDirection().getNumber();
+        }
+
+        /**
+         * Gets the initial offset for animation. By default the transition starts from the left
+         * parent boundary for horizontal orientation and from the top for vertical orientation.
+         * Note that sliding from the screen boundaries can only be achieved if all parent's sizes
+         * are big enough to accommodate it.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public SlideBound getInitialSlideBound() {
+            if (mImpl.hasInitialSlideBound()) {
+                return ModifiersBuilders.slideBoundFromProto(mImpl.getInitialSlideBound());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the animation parameters for duration, delay, etc.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public AnimationSpec getAnimationSpec() {
+            if (mImpl.hasAnimationSpec()) {
+                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static SlideInTransition fromProto(
+                @NonNull ModifiersProto.SlideInTransition proto,
+                @Nullable Fingerprint fingerprint) {
+            return new SlideInTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static SlideInTransition fromProto(@NonNull ModifiersProto.SlideInTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.SlideInTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "SlideInTransition{"
+                    + "direction="
+                    + getDirection()
+                    + ", initialSlideBound="
+                    + getInitialSlideBound()
+                    + ", animationSpec="
+                    + getAnimationSpec()
+                    + "}";
+        }
+
+        /** Builder for {@link SlideInTransition} */
+        public static final class Builder {
+            private final ModifiersProto.SlideInTransition.Builder mImpl =
+                    ModifiersProto.SlideInTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-991346238);
+
+            public Builder() {}
+
+            /**
+             * Sets the slide direction used for slide animations on any element, from the specified
+             * point to its destination in the layout. If not set, defaults to horizontal from left
+             * to the right.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setDirection(@SlideDirection int direction) {
+                mImpl.setDirection(ModifiersProto.SlideDirection.forNumber(direction));
+                mFingerprint.recordPropertyUpdate(1, direction);
+                return this;
+            }
+
+            /**
+             * Sets the initial offset for animation. By default the transition starts from the left
+             * parent boundary for horizontal orientation and from the top for vertical orientation.
+             * Note that sliding from the screen boundaries can only be achieved if all parent's
+             * sizes are big enough to accommodate it.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setInitialSlideBound(@NonNull SlideBound initialSlideBound) {
+                mImpl.setInitialSlideBound(initialSlideBound.toSlideBoundProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(initialSlideBound.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the animation parameters for duration, delay, etc.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
+                mImpl.setAnimationSpec(animationSpec.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public SlideInTransition build() {
+                return new SlideInTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets the sliding in animation for content transition of an element and its children happening
-     * when entering the layout.
+     * The content transition that is triggered when element exits the layout.
      *
      * @since 1.2
      */
-    @Nullable
-    public SlideInTransition getSlideIn() {
-      if (mImpl.hasSlideIn()) {
-        return SlideInTransition.fromProto(mImpl.getSlideIn());
-      } else {
-        return null;
-      }
+    @ProtoLayoutExperimental
+    public static final class ExitTransition {
+        private final ModifiersProto.ExitTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        ExitTransition(ModifiersProto.ExitTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the fading out animation for content transition of an element and its children
+         * happening when exiting the layout.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public FadeOutTransition getFadeOut() {
+            if (mImpl.hasFadeOut()) {
+                return FadeOutTransition.fromProto(mImpl.getFadeOut());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the sliding out animation for content transition of an element and its children
+         * happening when exiting the layout.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public SlideOutTransition getSlideOut() {
+            if (mImpl.hasSlideOut()) {
+                return SlideOutTransition.fromProto(mImpl.getSlideOut());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static ExitTransition fromProto(
+                @NonNull ModifiersProto.ExitTransition proto, @Nullable Fingerprint fingerprint) {
+            return new ExitTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static ExitTransition fromProto(@NonNull ModifiersProto.ExitTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.ExitTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "ExitTransition{"
+                    + "fadeOut="
+                    + getFadeOut()
+                    + ", slideOut="
+                    + getSlideOut()
+                    + "}";
+        }
+
+        /** Builder for {@link ExitTransition} */
+        public static final class Builder {
+            private final ModifiersProto.ExitTransition.Builder mImpl =
+                    ModifiersProto.ExitTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-99296494);
+
+            public Builder() {}
+
+            /**
+             * Sets the fading out animation for content transition of an element and its children
+             * happening when exiting the layout.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setFadeOut(@NonNull FadeOutTransition fadeOut) {
+                mImpl.setFadeOut(fadeOut.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(fadeOut.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the sliding out animation for content transition of an element and its children
+             * happening when exiting the layout.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setSlideOut(@NonNull SlideOutTransition slideOut) {
+                mImpl.setSlideOut(slideOut.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(slideOut.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ExitTransition build() {
+                return new ExitTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static EnterTransition fromProto(
-        @NonNull ModifiersProto.EnterTransition proto, @Nullable Fingerprint fingerprint) {
-      return new EnterTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static EnterTransition fromProto(@NonNull ModifiersProto.EnterTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.EnterTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "EnterTransition{" + "fadeIn=" + getFadeIn() + ", slideIn=" + getSlideIn() + "}";
-    }
-
-    /** Builder for {@link EnterTransition} */
-    public static final class Builder {
-      private final ModifiersProto.EnterTransition.Builder mImpl =
-          ModifiersProto.EnterTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1732205279);
-
-      public Builder() {}
-
-      /**
-       * Sets the fading in animation for content transition of an element and its children
-       * happening when entering the layout.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setFadeIn(@NonNull FadeInTransition fadeIn) {
-        mImpl.setFadeIn(fadeIn.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(fadeIn.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the sliding in animation for content transition of an element and its children
-       * happening when entering the layout.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setSlideIn(@NonNull SlideInTransition slideIn) {
-        mImpl.setSlideIn(slideIn.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(slideIn.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public EnterTransition build() {
-        return new EnterTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The fading animation for content transition of an element and its children, from the specified
-   * starting alpha to fully visible.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class FadeInTransition {
-    private final ModifiersProto.FadeInTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    FadeInTransition(ModifiersProto.FadeInTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the starting alpha of the fade in transition. It should be between 0 and 1. If not set,
-     * defaults to fully transparent, i.e. 0.
+     * The fading animation for content transition of an element and its children, from fully
+     * visible to the specified target alpha.
      *
      * @since 1.2
      */
-    @FloatRange(from = 0.0, to = 1.0)
-    public float getInitialAlpha() {
-      return mImpl.getInitialAlpha();
+    @ProtoLayoutExperimental
+    public static final class FadeOutTransition {
+        private final ModifiersProto.FadeOutTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        FadeOutTransition(
+                ModifiersProto.FadeOutTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the target alpha of the fade out transition. It should be between 0 and 1. If not
+         * set, defaults to fully invisible, i.e. 0.
+         *
+         * @since 1.2
+         */
+        @FloatRange(from = 0.0, to = 1.0)
+        public float getTargetAlpha() {
+            return mImpl.getTargetAlpha();
+        }
+
+        /**
+         * Gets the animation parameters for duration, delay, etc.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public AnimationSpec getAnimationSpec() {
+            if (mImpl.hasAnimationSpec()) {
+                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static FadeOutTransition fromProto(
+                @NonNull ModifiersProto.FadeOutTransition proto,
+                @Nullable Fingerprint fingerprint) {
+            return new FadeOutTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static FadeOutTransition fromProto(@NonNull ModifiersProto.FadeOutTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.FadeOutTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "FadeOutTransition{"
+                    + "targetAlpha="
+                    + getTargetAlpha()
+                    + ", animationSpec="
+                    + getAnimationSpec()
+                    + "}";
+        }
+
+        /** Builder for {@link FadeOutTransition} */
+        public static final class Builder {
+            private final ModifiersProto.FadeOutTransition.Builder mImpl =
+                    ModifiersProto.FadeOutTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-545572295);
+
+            public Builder() {}
+
+            /**
+             * Sets the target alpha of the fade out transition. It should be between 0 and 1. If
+             * not set, defaults to fully invisible, i.e. 0.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setTargetAlpha(@FloatRange(from = 0.0, to = 1.0) float targetAlpha) {
+                mImpl.setTargetAlpha(targetAlpha);
+                mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(targetAlpha));
+                return this;
+            }
+
+            /**
+             * Sets the animation parameters for duration, delay, etc.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
+                mImpl.setAnimationSpec(animationSpec.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public FadeOutTransition build() {
+                return new FadeOutTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets the animation parameters for duration, delay, etc.
+     * The sliding out animation for content transition of an element and its children.
      *
      * @since 1.2
      */
-    @Nullable
-    public AnimationSpec getAnimationSpec() {
-      if (mImpl.hasAnimationSpec()) {
-        return AnimationSpec.fromProto(mImpl.getAnimationSpec());
-      } else {
-        return null;
-      }
+    @ProtoLayoutExperimental
+    public static final class SlideOutTransition {
+        private final ModifiersProto.SlideOutTransition mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SlideOutTransition(
+                ModifiersProto.SlideOutTransition impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the slide direction used for slide animations on any element, from its destination
+         * in the layout to the specified point. If not set, defaults to horizontal from right to
+         * the left.
+         *
+         * @since 1.2
+         */
+        @SlideDirection
+        public int getDirection() {
+            return mImpl.getDirection().getNumber();
+        }
+
+        /**
+         * Gets the target offset for animation. By default the transition will end at the left
+         * parent boundary for horizontal orientation and at the top for vertical orientation. Note
+         * that sliding from the screen boundaries can only be achieved if all parent's sizes are
+         * big enough to accommodate it.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public SlideBound getTargetSlideBound() {
+            if (mImpl.hasTargetSlideBound()) {
+                return ModifiersBuilders.slideBoundFromProto(mImpl.getTargetSlideBound());
+            } else {
+                return null;
+            }
+        }
+
+        /**
+         * Gets the animation parameters for duration, delay, etc.
+         *
+         * @since 1.2
+         */
+        @Nullable
+        public AnimationSpec getAnimationSpec() {
+            if (mImpl.hasAnimationSpec()) {
+                return AnimationSpec.fromProto(mImpl.getAnimationSpec());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static SlideOutTransition fromProto(
+                @NonNull ModifiersProto.SlideOutTransition proto,
+                @Nullable Fingerprint fingerprint) {
+            return new SlideOutTransition(proto, fingerprint);
+        }
+
+        @NonNull
+        static SlideOutTransition fromProto(@NonNull ModifiersProto.SlideOutTransition proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.SlideOutTransition toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "SlideOutTransition{"
+                    + "direction="
+                    + getDirection()
+                    + ", targetSlideBound="
+                    + getTargetSlideBound()
+                    + ", animationSpec="
+                    + getAnimationSpec()
+                    + "}";
+        }
+
+        /** Builder for {@link SlideOutTransition} */
+        public static final class Builder {
+            private final ModifiersProto.SlideOutTransition.Builder mImpl =
+                    ModifiersProto.SlideOutTransition.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(3732844);
+
+            public Builder() {}
+
+            /**
+             * Sets the slide direction used for slide animations on any element, from its
+             * destination in the layout to the specified point. If not set, defaults to horizontal
+             * from right to the left.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setDirection(@SlideDirection int direction) {
+                mImpl.setDirection(ModifiersProto.SlideDirection.forNumber(direction));
+                mFingerprint.recordPropertyUpdate(1, direction);
+                return this;
+            }
+
+            /**
+             * Sets the target offset for animation. By default the transition will end at the left
+             * parent boundary for horizontal orientation and at the top for vertical orientation.
+             * Note that sliding from the screen boundaries can only be achieved if all parent's
+             * sizes are big enough to accommodate it.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setTargetSlideBound(@NonNull SlideBound targetSlideBound) {
+                mImpl.setTargetSlideBound(targetSlideBound.toSlideBoundProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(targetSlideBound.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets the animation parameters for duration, delay, etc.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
+                mImpl.setAnimationSpec(animationSpec.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public SlideOutTransition build() {
+                return new SlideOutTransition(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static FadeInTransition fromProto(
-        @NonNull ModifiersProto.FadeInTransition proto, @Nullable Fingerprint fingerprint) {
-      return new FadeInTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static FadeInTransition fromProto(@NonNull ModifiersProto.FadeInTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.FadeInTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "FadeInTransition{"
-          + "initialAlpha="
-          + getInitialAlpha()
-          + ", animationSpec="
-          + getAnimationSpec()
-          + "}";
-    }
-
-    /** Builder for {@link FadeInTransition} */
-    public static final class Builder {
-      private final ModifiersProto.FadeInTransition.Builder mImpl =
-          ModifiersProto.FadeInTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(1430024488);
-
-      public Builder() {}
-
-      /**
-       * Sets the starting alpha of the fade in transition. It should be between 0 and 1. If not
-       * set, defaults to fully transparent, i.e. 0.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setInitialAlpha(@FloatRange(from = 0.0, to = 1.0) float initialAlpha) {
-        mImpl.setInitialAlpha(initialAlpha);
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(initialAlpha));
-        return this;
-      }
-
-      /**
-       * Sets the animation parameters for duration, delay, etc.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
-        mImpl.setAnimationSpec(animationSpec.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public FadeInTransition build() {
-        return new FadeInTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The sliding in animation for content transition of an element and its children.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class SlideInTransition {
-    private final ModifiersProto.SlideInTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SlideInTransition(ModifiersProto.SlideInTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the slide direction used for slide animations on any element, from the specified point
-     * to its destination in the layout. If not set, defaults to horizontal from left to the right.
+     * Interface defining the boundary that a Slide animation will use for start/end.
      *
      * @since 1.2
      */
-    @SlideDirection
-    public int getDirection() {
-      return mImpl.getDirection().getNumber();
+    @ProtoLayoutExperimental
+    public interface SlideBound {
+        /** Get the protocol buffer representation of this object. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        ModifiersProto.SlideBound toSlideBoundProto();
+
+        /** Get the fingerprint for this object or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        Fingerprint getFingerprint();
+
+        /** Builder to create {@link SlideBound} objects. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        interface Builder {
+
+            /** Builds an instance with values accumulated in this Builder. */
+            @NonNull
+            SlideBound build();
+        }
     }
 
-    /**
-     * Gets the initial offset for animation. By default the transition starts from the left parent
-     * boundary for horizontal orientation and from the top for vertical orientation. Note that
-     * sliding from the screen boundaries can only be achieved if all parent's sizes are big enough
-     * to accommodate it.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public SlideBound getInitialSlideBound() {
-      if (mImpl.hasInitialSlideBound()) {
-        return ModifiersBuilders.slideBoundFromProto(mImpl.getInitialSlideBound());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the animation parameters for duration, delay, etc.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public AnimationSpec getAnimationSpec() {
-      if (mImpl.hasAnimationSpec()) {
-        return AnimationSpec.fromProto(mImpl.getAnimationSpec());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static SlideInTransition fromProto(
-        @NonNull ModifiersProto.SlideInTransition proto, @Nullable Fingerprint fingerprint) {
-      return new SlideInTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static SlideInTransition fromProto(@NonNull ModifiersProto.SlideInTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.SlideInTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "SlideInTransition{"
-          + "direction="
-          + getDirection()
-          + ", initialSlideBound="
-          + getInitialSlideBound()
-          + ", animationSpec="
-          + getAnimationSpec()
-          + "}";
-    }
-
-    /** Builder for {@link SlideInTransition} */
-    public static final class Builder {
-      private final ModifiersProto.SlideInTransition.Builder mImpl =
-          ModifiersProto.SlideInTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-991346238);
-
-      public Builder() {}
-
-      /**
-       * Sets the slide direction used for slide animations on any element, from the specified point
-       * to its destination in the layout. If not set, defaults to horizontal from left to the
-       * right.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setDirection(@SlideDirection int direction) {
-        mImpl.setDirection(ModifiersProto.SlideDirection.forNumber(direction));
-        mFingerprint.recordPropertyUpdate(1, direction);
-        return this;
-      }
-
-      /**
-       * Sets the initial offset for animation. By default the transition starts from the left
-       * parent boundary for horizontal orientation and from the top for vertical orientation. Note
-       * that sliding from the screen boundaries can only be achieved if all parent's sizes are big
-       * enough to accommodate it.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setInitialSlideBound(@NonNull SlideBound initialSlideBound) {
-        mImpl.setInitialSlideBound(initialSlideBound.toSlideBoundProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(initialSlideBound.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the animation parameters for duration, delay, etc.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
-        mImpl.setAnimationSpec(animationSpec.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public SlideInTransition build() {
-        return new SlideInTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The content transition that is triggered when element exits the layout.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class ExitTransition {
-    private final ModifiersProto.ExitTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ExitTransition(ModifiersProto.ExitTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the fading out animation for content transition of an element and its children happening
-     * when exiting the layout.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public FadeOutTransition getFadeOut() {
-      if (mImpl.hasFadeOut()) {
-        return FadeOutTransition.fromProto(mImpl.getFadeOut());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the sliding out animation for content transition of an element and its children
-     * happening when exiting the layout.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public SlideOutTransition getSlideOut() {
-      if (mImpl.hasSlideOut()) {
-        return SlideOutTransition.fromProto(mImpl.getSlideOut());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static ExitTransition fromProto(
-        @NonNull ModifiersProto.ExitTransition proto, @Nullable Fingerprint fingerprint) {
-      return new ExitTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static ExitTransition fromProto(@NonNull ModifiersProto.ExitTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.ExitTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "ExitTransition{" + "fadeOut=" + getFadeOut() + ", slideOut=" + getSlideOut() + "}";
-    }
-
-    /** Builder for {@link ExitTransition} */
-    public static final class Builder {
-      private final ModifiersProto.ExitTransition.Builder mImpl =
-          ModifiersProto.ExitTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-99296494);
-
-      public Builder() {}
-
-      /**
-       * Sets the fading out animation for content transition of an element and its children
-       * happening when exiting the layout.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setFadeOut(@NonNull FadeOutTransition fadeOut) {
-        mImpl.setFadeOut(fadeOut.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(fadeOut.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the sliding out animation for content transition of an element and its children
-       * happening when exiting the layout.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setSlideOut(@NonNull SlideOutTransition slideOut) {
-        mImpl.setSlideOut(slideOut.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(slideOut.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ExitTransition build() {
-        return new ExitTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The fading animation for content transition of an element and its children, from fully visible
-   * to the specified target alpha.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class FadeOutTransition {
-    private final ModifiersProto.FadeOutTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    FadeOutTransition(ModifiersProto.FadeOutTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the target alpha of the fade out transition. It should be between 0 and 1. If not set,
-     * defaults to fully invisible, i.e. 0.
-     *
-     * @since 1.2
-     */
-    @FloatRange(from = 0.0, to = 1.0)
-    public float getTargetAlpha() {
-      return mImpl.getTargetAlpha();
-    }
-
-    /**
-     * Gets the animation parameters for duration, delay, etc.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public AnimationSpec getAnimationSpec() {
-      if (mImpl.hasAnimationSpec()) {
-        return AnimationSpec.fromProto(mImpl.getAnimationSpec());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static FadeOutTransition fromProto(
-        @NonNull ModifiersProto.FadeOutTransition proto, @Nullable Fingerprint fingerprint) {
-      return new FadeOutTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static FadeOutTransition fromProto(@NonNull ModifiersProto.FadeOutTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.FadeOutTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "FadeOutTransition{"
-          + "targetAlpha="
-          + getTargetAlpha()
-          + ", animationSpec="
-          + getAnimationSpec()
-          + "}";
-    }
-
-    /** Builder for {@link FadeOutTransition} */
-    public static final class Builder {
-      private final ModifiersProto.FadeOutTransition.Builder mImpl =
-          ModifiersProto.FadeOutTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-545572295);
-
-      public Builder() {}
-
-      /**
-       * Sets the target alpha of the fade out transition. It should be between 0 and 1. If not set,
-       * defaults to fully invisible, i.e. 0.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setTargetAlpha(@FloatRange(from = 0.0, to = 1.0) float targetAlpha) {
-        mImpl.setTargetAlpha(targetAlpha);
-        mFingerprint.recordPropertyUpdate(1, Float.floatToIntBits(targetAlpha));
-        return this;
-      }
-
-      /**
-       * Sets the animation parameters for duration, delay, etc.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
-        mImpl.setAnimationSpec(animationSpec.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public FadeOutTransition build() {
-        return new FadeOutTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * The sliding out animation for content transition of an element and its children.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class SlideOutTransition {
-    private final ModifiersProto.SlideOutTransition mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SlideOutTransition(ModifiersProto.SlideOutTransition impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the slide direction used for slide animations on any element, from its destination in
-     * the layout to the specified point. If not set, defaults to horizontal from right to the left.
-     *
-     * @since 1.2
-     */
-    @SlideDirection
-    public int getDirection() {
-      return mImpl.getDirection().getNumber();
-    }
-
-    /**
-     * Gets the target offset for animation. By default the transition will end at the left parent
-     * boundary for horizontal orientation and at the top for vertical orientation. Note that
-     * sliding from the screen boundaries can only be achieved if all parent's sizes are big enough
-     * to accommodate it.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public SlideBound getTargetSlideBound() {
-      if (mImpl.hasTargetSlideBound()) {
-        return ModifiersBuilders.slideBoundFromProto(mImpl.getTargetSlideBound());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets the animation parameters for duration, delay, etc.
-     *
-     * @since 1.2
-     */
-    @Nullable
-    public AnimationSpec getAnimationSpec() {
-      if (mImpl.hasAnimationSpec()) {
-        return AnimationSpec.fromProto(mImpl.getAnimationSpec());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static SlideOutTransition fromProto(
-        @NonNull ModifiersProto.SlideOutTransition proto, @Nullable Fingerprint fingerprint) {
-      return new SlideOutTransition(proto, fingerprint);
-    }
-
-    @NonNull
-    static SlideOutTransition fromProto(@NonNull ModifiersProto.SlideOutTransition proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.SlideOutTransition toProto() {
-      return mImpl;
-    }
-
-    @Override
-    @NonNull
-    public String toString() {
-      return "SlideOutTransition{"
-          + "direction="
-          + getDirection()
-          + ", targetSlideBound="
-          + getTargetSlideBound()
-          + ", animationSpec="
-          + getAnimationSpec()
-          + "}";
-    }
-
-    /** Builder for {@link SlideOutTransition} */
-    public static final class Builder {
-      private final ModifiersProto.SlideOutTransition.Builder mImpl =
-          ModifiersProto.SlideOutTransition.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(3732844);
-
-      public Builder() {}
-
-      /**
-       * Sets the slide direction used for slide animations on any element, from its destination in
-       * the layout to the specified point. If not set, defaults to horizontal from right to the
-       * left.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setDirection(@SlideDirection int direction) {
-        mImpl.setDirection(ModifiersProto.SlideDirection.forNumber(direction));
-        mFingerprint.recordPropertyUpdate(1, direction);
-        return this;
-      }
-
-      /**
-       * Sets the target offset for animation. By default the transition will end at the left parent
-       * boundary for horizontal orientation and at the top for vertical orientation. Note that
-       * sliding from the screen boundaries can only be achieved if all parent's sizes are big
-       * enough to accommodate it.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setTargetSlideBound(@NonNull SlideBound targetSlideBound) {
-        mImpl.setTargetSlideBound(targetSlideBound.toSlideBoundProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(targetSlideBound.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /**
-       * Sets the animation parameters for duration, delay, etc.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setAnimationSpec(@NonNull AnimationSpec animationSpec) {
-        mImpl.setAnimationSpec(animationSpec.toProto());
-        mFingerprint.recordPropertyUpdate(
-            3, checkNotNull(animationSpec.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public SlideOutTransition build() {
-        return new SlideOutTransition(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * Interface defining the boundary that a Slide animation will use for start/end.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public interface SlideBound {
-    /**
-     * Get the protocol buffer representation of this object.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    ModifiersProto.SlideBound toSlideBoundProto();
-
-    /**
-     * Get the fingerprint for this object or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    Fingerprint getFingerprint();
-
-    /**
-     * Builder to create {@link SlideBound} objects.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    interface Builder {
-
-      /** Builds an instance with values accumulated in this Builder. */
-      @NonNull
-      SlideBound build();
-    }
-  }
-
-  /**
-   * Creates a new wrapper instance from the proto.
-   *
-   */
-  @RestrictTo(Scope.LIBRARY_GROUP)
-  @NonNull
-  @ProtoLayoutExperimental
-  public static SlideBound slideBoundFromProto(
-      @NonNull ModifiersProto.SlideBound proto, @Nullable Fingerprint fingerprint) {
-    if (proto.hasParentBound()) {
-      return SlideParentBound.fromProto(proto.getParentBound(), fingerprint);
-    }
-    throw new IllegalStateException("Proto was not a recognised instance of SlideBound");
-  }
-
-  @NonNull
-  @ProtoLayoutExperimental
-  static SlideBound slideBoundFromProto(@NonNull ModifiersProto.SlideBound proto) {
-    return slideBoundFromProto(proto, null);
-  }
-
-  /**
-   * The slide animation will animate from/to the parent elements boundaries.
-   *
-   * @since 1.2
-   */
-  @ProtoLayoutExperimental
-  public static final class SlideParentBound implements SlideBound {
-    private final ModifiersProto.SlideParentBound mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    SlideParentBound(ModifiersProto.SlideParentBound impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
-    }
-
-    /**
-     * Gets the snap options to use when sliding using parent boundaries. Defaults to
-     * SLIDE_PARENT_SNAP_TO_INSIDE if not specified.
-     *
-     * @since 1.2
-     */
-    @SlideParentSnapOption
-    public int getSnapTo() {
-      return mImpl.getSnapTo().getNumber();
-    }
-
-    @Override
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-    /**
-     * Creates a new wrapper instance from the proto.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public static SlideParentBound fromProto(
-        @NonNull ModifiersProto.SlideParentBound proto, @Nullable Fingerprint fingerprint) {
-      return new SlideParentBound(proto, fingerprint);
-    }
-
-    @NonNull
-    static SlideParentBound fromProto(@NonNull ModifiersProto.SlideParentBound proto) {
-      return fromProto(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    ModifiersProto.SlideParentBound toProto() {
-      return mImpl;
-    }
-
-    @Override
+    /** Creates a new wrapper instance from the proto. */
     @RestrictTo(Scope.LIBRARY_GROUP)
     @NonNull
     @ProtoLayoutExperimental
-    public ModifiersProto.SlideBound toSlideBoundProto() {
-      return ModifiersProto.SlideBound.newBuilder().setParentBound(mImpl).build();
+    public static SlideBound slideBoundFromProto(
+            @NonNull ModifiersProto.SlideBound proto, @Nullable Fingerprint fingerprint) {
+        if (proto.hasParentBound()) {
+            return SlideParentBound.fromProto(proto.getParentBound(), fingerprint);
+        }
+        throw new IllegalStateException("Proto was not a recognised instance of SlideBound");
     }
 
-    @Override
     @NonNull
-    public String toString() {
-      return "SlideParentBound{" + "snapTo=" + getSnapTo() + "}";
-    }
-
-    /** Builder for {@link SlideParentBound}. */
-    public static final class Builder implements SlideBound.Builder {
-      private final ModifiersProto.SlideParentBound.Builder mImpl =
-          ModifiersProto.SlideParentBound.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-516388675);
-
-      public Builder() {}
-
-      /**
-       * Sets the snap options to use when sliding using parent boundaries. Defaults to
-       * SLIDE_PARENT_SNAP_TO_INSIDE if not specified.
-       *
-       * @since 1.2
-       */
-      @NonNull
-      public Builder setSnapTo(@SlideParentSnapOption int snapTo) {
-        mImpl.setSnapTo(ModifiersProto.SlideParentSnapOption.forNumber(snapTo));
-        mFingerprint.recordPropertyUpdate(1, snapTo);
-        return this;
-      }
-
-      @Override
-      @NonNull
-      public SlideParentBound build() {
-        return new SlideParentBound(mImpl.build(), mFingerprint);
-      }
-    }
-  }
-
-  /**
-   * {@link Modifiers} that can be used with ArcLayoutElements. These may change the way they are
-   * drawn, or change their behaviour.
-   */
-  public static final class ArcModifiers {
-    private final ModifiersProto.ArcModifiers mImpl;
-    @Nullable private final Fingerprint mFingerprint;
-
-    ArcModifiers(ModifiersProto.ArcModifiers impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+    @ProtoLayoutExperimental
+    static SlideBound slideBoundFromProto(@NonNull ModifiersProto.SlideBound proto) {
+        return slideBoundFromProto(proto, null);
     }
 
     /**
-     * Gets allows its wrapped element to have actions associated with it, which will be executed
-     * when the element is tapped. Intended for testing purposes only.
-     */
-    @Nullable
-    public Clickable getClickable() {
-      if (mImpl.hasClickable()) {
-        return Clickable.fromProto(mImpl.getClickable());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Gets adds metadata for the modified element, for example, screen reader content descriptions.
-     * Intended for testing purposes only.
-     */
-    @Nullable
-    public Semantics getSemantics() {
-      if (mImpl.hasSemantics()) {
-        return Semantics.fromProto(mImpl.getSemantics());
-      } else {
-        return null;
-      }
-    }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
+     * The slide animation will animate from/to the parent elements boundaries.
      *
+     * @since 1.2
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
+    @ProtoLayoutExperimental
+    public static final class SlideParentBound implements SlideBound {
+        private final ModifiersProto.SlideParentBound mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    @NonNull
-    static ArcModifiers fromProto(@NonNull ModifiersProto.ArcModifiers proto) {
-      return new ArcModifiers(proto, null);
+        SlideParentBound(ModifiersProto.SlideParentBound impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets the snap options to use when sliding using parent boundaries. Defaults to
+         * SLIDE_PARENT_SNAP_TO_INSIDE if not specified.
+         *
+         * @since 1.2
+         */
+        @SlideParentSnapOption
+        public int getSnapTo() {
+            return mImpl.getSnapTo().getNumber();
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        /** Creates a new wrapper instance from the proto. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public static SlideParentBound fromProto(
+                @NonNull ModifiersProto.SlideParentBound proto, @Nullable Fingerprint fingerprint) {
+            return new SlideParentBound(proto, fingerprint);
+        }
+
+        @NonNull
+        static SlideParentBound fromProto(@NonNull ModifiersProto.SlideParentBound proto) {
+            return fromProto(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        ModifiersProto.SlideParentBound toProto() {
+            return mImpl;
+        }
+
+        @Override
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        @ProtoLayoutExperimental
+        public ModifiersProto.SlideBound toSlideBoundProto() {
+            return ModifiersProto.SlideBound.newBuilder().setParentBound(mImpl).build();
+        }
+
+        @Override
+        @NonNull
+        public String toString() {
+            return "SlideParentBound{" + "snapTo=" + getSnapTo() + "}";
+        }
+
+        /** Builder for {@link SlideParentBound}. */
+        public static final class Builder implements SlideBound.Builder {
+            private final ModifiersProto.SlideParentBound.Builder mImpl =
+                    ModifiersProto.SlideParentBound.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-516388675);
+
+            public Builder() {}
+
+            /**
+             * Sets the snap options to use when sliding using parent boundaries. Defaults to
+             * SLIDE_PARENT_SNAP_TO_INSIDE if not specified.
+             *
+             * @since 1.2
+             */
+            @NonNull
+            public Builder setSnapTo(@SlideParentSnapOption int snapTo) {
+                mImpl.setSnapTo(ModifiersProto.SlideParentSnapOption.forNumber(snapTo));
+                mFingerprint.recordPropertyUpdate(1, snapTo);
+                return this;
+            }
+
+            @Override
+            @NonNull
+            public SlideParentBound build() {
+                return new SlideParentBound(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Returns the internal proto instance.
-     *
+     * {@link Modifiers} that can be used with ArcLayoutElements. These may change the way they are
+     * drawn, or change their behaviour.
      */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.ArcModifiers toProto() {
-      return mImpl;
-    }
+    public static final class ArcModifiers {
+        private final ModifiersProto.ArcModifiers mImpl;
+        @Nullable private final Fingerprint mFingerprint;
 
-    /** Builder for {@link ArcModifiers} */
-    public static final class Builder {
-      private final ModifiersProto.ArcModifiers.Builder mImpl =
-          ModifiersProto.ArcModifiers.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1648736168);
+        ArcModifiers(ModifiersProto.ArcModifiers impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
 
-      public Builder() {}
+        /**
+         * Gets allows its wrapped element to have actions associated with it, which will be
+         * executed when the element is tapped. Intended for testing purposes only.
+         */
+        @Nullable
+        public Clickable getClickable() {
+            if (mImpl.hasClickable()) {
+                return Clickable.fromProto(mImpl.getClickable());
+            } else {
+                return null;
+            }
+        }
 
-      /**
-       * Sets allows its wrapped element to have actions associated with it, which will be executed
-       * when the element is tapped.
-       */
-      @NonNull
-      public Builder setClickable(@NonNull Clickable clickable) {
-        mImpl.setClickable(clickable.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
+        /**
+         * Gets adds metadata for the modified element, for example, screen reader content
+         * descriptions. Intended for testing purposes only.
+         */
+        @Nullable
+        public Semantics getSemantics() {
+            if (mImpl.hasSemantics()) {
+                return Semantics.fromProto(mImpl.getSemantics());
+            } else {
+                return null;
+            }
+        }
 
-      /**
-       * Sets adds metadata for the modified element, for example, screen reader content
-       * descriptions.
-       */
-      @NonNull
-      public Builder setSemantics(@NonNull Semantics semantics) {
-        mImpl.setSemantics(semantics.toProto());
-        mFingerprint.recordPropertyUpdate(
-            2, checkNotNull(semantics.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
 
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public ArcModifiers build() {
-        return new ArcModifiers(mImpl.build(), mFingerprint);
-      }
-    }
-  }
+        @NonNull
+        static ArcModifiers fromProto(@NonNull ModifiersProto.ArcModifiers proto) {
+            return new ArcModifiers(proto, null);
+        }
 
-  /**
-   * {@link Modifiers} that can be used with {@link androidx.wear.tiles.LayoutElementBuilders.Span}
-   * elements. These may change the way they are drawn, or change their behaviour.
-   */
-  public static final class SpanModifiers {
-    private final ModifiersProto.SpanModifiers mImpl;
-    @Nullable private final Fingerprint mFingerprint;
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.ArcModifiers toProto() {
+            return mImpl;
+        }
 
-    SpanModifiers(ModifiersProto.SpanModifiers impl, @Nullable Fingerprint fingerprint) {
-      this.mImpl = impl;
-      this.mFingerprint = fingerprint;
+        /** Builder for {@link ArcModifiers} */
+        public static final class Builder {
+            private final ModifiersProto.ArcModifiers.Builder mImpl =
+                    ModifiersProto.ArcModifiers.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1648736168);
+
+            public Builder() {}
+
+            /**
+             * Sets allows its wrapped element to have actions associated with it, which will be
+             * executed when the element is tapped.
+             */
+            @NonNull
+            public Builder setClickable(@NonNull Clickable clickable) {
+                mImpl.setClickable(clickable.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /**
+             * Sets adds metadata for the modified element, for example, screen reader content
+             * descriptions.
+             */
+            @NonNull
+            public Builder setSemantics(@NonNull Semantics semantics) {
+                mImpl.setSemantics(semantics.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        2, checkNotNull(semantics.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public ArcModifiers build() {
+                return new ArcModifiers(mImpl.build(), mFingerprint);
+            }
+        }
     }
 
     /**
-     * Gets allows its wrapped element to have actions associated with it, which will be executed
-     * when the element is tapped. Intended for testing purposes only.
+     * {@link Modifiers} that can be used with {@link
+     * androidx.wear.tiles.LayoutElementBuilders.Span} elements. These may change the way they are
+     * drawn, or change their behaviour.
      */
-    @Nullable
-    public Clickable getClickable() {
-      if (mImpl.hasClickable()) {
-        return Clickable.fromProto(mImpl.getClickable());
-      } else {
-        return null;
-      }
+    public static final class SpanModifiers {
+        private final ModifiersProto.SpanModifiers mImpl;
+        @Nullable private final Fingerprint mFingerprint;
+
+        SpanModifiers(ModifiersProto.SpanModifiers impl, @Nullable Fingerprint fingerprint) {
+            this.mImpl = impl;
+            this.mFingerprint = fingerprint;
+        }
+
+        /**
+         * Gets allows its wrapped element to have actions associated with it, which will be
+         * executed when the element is tapped. Intended for testing purposes only.
+         */
+        @Nullable
+        public Clickable getClickable() {
+            if (mImpl.hasClickable()) {
+                return Clickable.fromProto(mImpl.getClickable());
+            } else {
+                return null;
+            }
+        }
+
+        /** Get the fingerprint for this object, or null if unknown. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @Nullable
+        public Fingerprint getFingerprint() {
+            return mFingerprint;
+        }
+
+        @NonNull
+        static SpanModifiers fromProto(@NonNull ModifiersProto.SpanModifiers proto) {
+            return new SpanModifiers(proto, null);
+        }
+
+        /** Returns the internal proto instance. */
+        @RestrictTo(Scope.LIBRARY_GROUP)
+        @NonNull
+        public ModifiersProto.SpanModifiers toProto() {
+            return mImpl;
+        }
+
+        /** Builder for {@link SpanModifiers} */
+        public static final class Builder {
+            private final ModifiersProto.SpanModifiers.Builder mImpl =
+                    ModifiersProto.SpanModifiers.newBuilder();
+            private final Fingerprint mFingerprint = new Fingerprint(-1318656482);
+
+            public Builder() {}
+
+            /**
+             * Sets allows its wrapped element to have actions associated with it, which will be
+             * executed when the element is tapped.
+             */
+            @NonNull
+            public Builder setClickable(@NonNull Clickable clickable) {
+                mImpl.setClickable(clickable.toProto());
+                mFingerprint.recordPropertyUpdate(
+                        1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
+                return this;
+            }
+
+            /** Builds an instance from accumulated values. */
+            @NonNull
+            public SpanModifiers build() {
+                return new SpanModifiers(mImpl.build(), mFingerprint);
+            }
+        }
     }
-
-    /**
-     * Get the fingerprint for this object, or null if unknown.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @Nullable
-    public Fingerprint getFingerprint() {
-      return mFingerprint;
-    }
-
-    @NonNull
-    static SpanModifiers fromProto(@NonNull ModifiersProto.SpanModifiers proto) {
-      return new SpanModifiers(proto, null);
-    }
-
-    /**
-     * Returns the internal proto instance.
-     *
-     */
-    @RestrictTo(Scope.LIBRARY_GROUP)
-    @NonNull
-    public ModifiersProto.SpanModifiers toProto() {
-      return mImpl;
-    }
-
-    /** Builder for {@link SpanModifiers} */
-    public static final class Builder {
-      private final ModifiersProto.SpanModifiers.Builder mImpl =
-          ModifiersProto.SpanModifiers.newBuilder();
-      private final Fingerprint mFingerprint = new Fingerprint(-1318656482);
-
-      public Builder() {}
-
-      /**
-       * Sets allows its wrapped element to have actions associated with it, which will be executed
-       * when the element is tapped.
-       */
-      @NonNull
-      public Builder setClickable(@NonNull Clickable clickable) {
-        mImpl.setClickable(clickable.toProto());
-        mFingerprint.recordPropertyUpdate(
-            1, checkNotNull(clickable.getFingerprint()).aggregateValueAsInt());
-        return this;
-      }
-
-      /** Builds an instance from accumulated values. */
-      @NonNull
-      public SpanModifiers build() {
-        return new SpanModifiers(mImpl.build(), mFingerprint);
-      }
-    }
-  }
 }
diff --git a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
index 96eb1ee..f445db0 100644
--- a/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
+++ b/wear/protolayout/protolayout/src/main/java/androidx/wear/protolayout/TypeBuilders.java
@@ -282,6 +282,12 @@
       return mImpl;
     }
 
+    @Override
+    @NonNull
+    public String toString() {
+      return "BoolProp{" + "value=" + getValue() + "}";
+    }
+
     /** Builder for {@link BoolProp} */
     public static final class Builder {
       private final TypesProto.BoolProp.Builder mImpl = TypesProto.BoolProp.newBuilder();
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ColorBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ColorBuildersTest.java
new file mode 100644
index 0000000..078ae25
--- /dev/null
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ColorBuildersTest.java
@@ -0,0 +1,58 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.graphics.Color;
+
+import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.proto.ColorProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ColorBuildersTest {
+    private static final String STATE_KEY = "state-key";
+    private static final ColorBuilders.ColorProp COLOR =
+            new ColorBuilders.ColorProp.Builder(Color.RED)
+                    .setDynamicValue(DynamicBuilders.DynamicColor.fromState(STATE_KEY))
+                    .build();
+
+    @SuppressWarnings("deprecation")
+    private static final ColorBuilders.ColorProp.Builder COLOR_BUILDER_WITHOUT_STATIC_VALUE =
+            new ColorBuilders.ColorProp.Builder()
+                    .setDynamicValue(DynamicBuilders.DynamicColor.fromState(STATE_KEY));
+
+    @Test
+    public void colorPropSupportsDynamicColor() {
+        ColorProto.ColorProp colorPropProto = COLOR.toProto();
+
+        assertThat(colorPropProto.getArgb()).isEqualTo(COLOR.getArgb());
+        assertThat(colorPropProto.getDynamicValue().getStateSource().getSourceKey())
+                .isEqualTo(STATE_KEY);
+    }
+
+    @Test
+    public void colorProp_withoutStaticValue_throws() {
+        assertThrows(IllegalStateException.class, COLOR_BUILDER_WITHOUT_STATIC_VALUE::build);
+    }
+}
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
index 2f54d7b..8566683 100644
--- a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/DimensionBuildersTest.java
@@ -16,8 +16,13 @@
 
 package androidx.wear.protolayout;
 
+import static androidx.wear.protolayout.DimensionBuilders.dp;
+
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.junit.Assert.assertThrows;
+
+import androidx.wear.protolayout.expression.DynamicBuilders;
 import androidx.wear.protolayout.proto.DimensionProto;
 
 import org.junit.Test;
@@ -26,10 +31,35 @@
 
 @RunWith(RobolectricTestRunner.class)
 public class DimensionBuildersTest {
+    private static final String STATE_KEY = "state-key";
+    private static final DimensionBuilders.DpProp DP_PROP =
+            new DimensionBuilders.DpProp.Builder(3.14f)
+                    .setDynamicValue(DynamicBuilders.DynamicFloat.fromState(STATE_KEY))
+                    .build();
+
+    @SuppressWarnings("deprecation")
+    private static final DimensionBuilders.DpProp.Builder DP_PROP_WITHOUT_STATIC_VALUE =
+            new DimensionBuilders.DpProp.Builder()
+                    .setDynamicValue(DynamicBuilders.DynamicFloat.fromState(STATE_KEY));
+
+    @Test
+    public void dpPropSupportsDynamicValue() {
+        DimensionProto.DpProp dpPropProto = DP_PROP.toProto();
+
+        assertThat(dpPropProto.getValue()).isEqualTo(DP_PROP.getValue());
+        assertThat(dpPropProto.getDynamicValue().getStateSource().getSourceKey())
+                .isEqualTo(STATE_KEY);
+    }
+
+    @Test
+    public void dpProp_withoutStaticValue_throws() {
+        assertThrows(IllegalStateException.class, DP_PROP_WITHOUT_STATIC_VALUE::build);
+    }
 
     @Test
     public void expandedLayoutWeight() {
-        float layoutWeight = 3.14f;
+        TypeBuilders.FloatProp layoutWeight =
+                new TypeBuilders.FloatProp.Builder().setValue(3.14f).build();
         DimensionBuilders.ContainerDimension dimensionProp =
                 new DimensionBuilders.ExpandedDimensionProp.Builder().setLayoutWeight(layoutWeight)
                         .build();
@@ -37,20 +67,30 @@
         DimensionProto.ContainerDimension dimensionProto =
                 dimensionProp.toContainerDimensionProto();
         assertThat(dimensionProto.getExpandedDimension().getLayoutWeight().getValue())
-                .isWithin(.001f).of(layoutWeight);
+                .isWithin(.001f).of(layoutWeight.getValue());
     }
 
 
     @Test
     public void wrappedMinSize() {
-        int minSizeDp = 42;
+        DimensionBuilders.DpProp minSize = dp(42);
         DimensionBuilders.ContainerDimension dimensionProp =
-                new DimensionBuilders.WrappedDimensionProp.Builder().setMinimumSizeDp(minSizeDp)
+                new DimensionBuilders.WrappedDimensionProp.Builder().setMinimumSize(minSize)
                         .build();
 
         DimensionProto.ContainerDimension dimensionProto =
                 dimensionProp.toContainerDimensionProto();
         assertThat(dimensionProto.getWrappedDimension().getMinimumSize().getValue())
-                .isEqualTo(minSizeDp);
+                .isEqualTo(minSize.getValue());
+    }
+
+    @Test
+    public void wrappedMinSize_throwsWhenSetToDynamicValue() {
+        DimensionBuilders.DpProp minSizeDynamic =
+                new DimensionBuilders.DpProp.Builder(42).setDynamicValue(
+                        DynamicBuilders.DynamicFloat.fromState("some-state")).build();
+        assertThrows(IllegalArgumentException.class, () ->
+                new DimensionBuilders.WrappedDimensionProp.Builder().setMinimumSize(
+                        minSizeDynamic));
     }
 }
diff --git a/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java
new file mode 100644
index 0000000..fec2fb2
--- /dev/null
+++ b/wear/protolayout/protolayout/src/test/java/androidx/wear/protolayout/ModifiersBuildersTest.java
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.wear.protolayout;
+
+
+import static com.google.common.truth.Truth.assertThat;
+
+import android.graphics.Color;
+
+import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.proto.ModifiersProto;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+
+@RunWith(RobolectricTestRunner.class)
+public class ModifiersBuildersTest {
+    private static final String STATE_KEY = "state-key";
+    private static final ColorBuilders.ColorProp COLOR =
+            new ColorBuilders.ColorProp.Builder(Color.RED)
+                    .setDynamicValue(DynamicBuilders.DynamicColor.fromState(STATE_KEY))
+                    .build();
+
+    @Test
+    public void borderSupportsDynamicColor() {
+        ModifiersBuilders.Border border =
+                new ModifiersBuilders.Border.Builder().setColor(COLOR).build();
+
+        ModifiersProto.Border borderProto = border.toProto();
+        assertThat(borderProto.getColor().getArgb()).isEqualTo(COLOR.getArgb());
+        assertThat(borderProto.getColor().getDynamicValue().getStateSource().getSourceKey())
+                .isEqualTo(STATE_KEY);
+    }
+
+    @Test
+    public void backgroundSupportsDynamicColor() {
+        ModifiersBuilders.Background background1 =
+                new ModifiersBuilders.Background.Builder().setColor(COLOR).build();
+
+        ModifiersProto.Background background1Proto = background1.toProto();
+        assertThat(background1Proto.getColor().getArgb()).isEqualTo(COLOR.getArgb());
+        assertThat(background1Proto.getColor().getDynamicValue().getStateSource().getSourceKey())
+                .isEqualTo(STATE_KEY);
+    }
+}
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenTest.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenTest.java
index bf29998..5277c40 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenTest.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenTest.java
@@ -26,6 +26,7 @@
 
 import androidx.annotation.Dimension;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.screenshot.AndroidXScreenshotTestRule;
 import androidx.wear.tiles.DeviceParametersBuilders;
@@ -94,6 +95,7 @@
                 .collect(Collectors.toList());
     }
 
+    @SdkSuppress(maxSdkVersion = 32) // b/271486183
     @Test
     public void test() {
         runSingleScreenshotTest(mScreenshotRule, mLayoutElement, mExpected);
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenXLTest.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenXLTest.java
index f42d628..0130f11 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenXLTest.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/MaterialGoldenXLTest.java
@@ -28,6 +28,7 @@
 
 import androidx.annotation.Dimension;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.screenshot.AndroidXScreenshotTestRule;
 import androidx.wear.tiles.DeviceParametersBuilders;
@@ -134,6 +135,7 @@
         getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
     }
 
+    @SdkSuppress(maxSdkVersion = 32) // b/271486183
     @Test
     public void test() {
         runSingleScreenshotTest(mScreenshotRule, mLayoutElement, mExpected);
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenTest.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenTest.java
index 067e846..72498b3 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenTest.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenTest.java
@@ -26,6 +26,7 @@
 
 import androidx.annotation.Dimension;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.screenshot.AndroidXScreenshotTestRule;
 import androidx.wear.tiles.DeviceParametersBuilders;
@@ -94,6 +95,7 @@
                 .collect(Collectors.toList());
     }
 
+    @SdkSuppress(maxSdkVersion = 32) // b/271486183
     @Test
     public void test() {
         runSingleScreenshotTest(mScreenshotRule, mLayoutElement, mExpected);
diff --git a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenXLTest.java b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenXLTest.java
index 41ee4be..ff3b8d9 100644
--- a/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenXLTest.java
+++ b/wear/tiles/tiles-material/src/androidTest/java/androidx/wear/tiles/material/layouts/LayoutsGoldenXLTest.java
@@ -28,6 +28,7 @@
 
 import androidx.annotation.Dimension;
 import androidx.test.filters.LargeTest;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.platform.app.InstrumentationRegistry;
 import androidx.test.screenshot.AndroidXScreenshotTestRule;
 import androidx.wear.tiles.DeviceParametersBuilders;
@@ -143,6 +144,7 @@
         getApplicationContext().getResources().getDisplayMetrics().setTo(OLD_DISPLAY_METRICS);
     }
 
+    @SdkSuppress(maxSdkVersion = 32) // b/271486183
     @Test
     public void test() {
         runSingleScreenshotTest(mScreenshotRule, mLayoutElement, mExpected);
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
index 39310fd..a4cb503 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/ComplicationDataExpressionEvaluator.kt
@@ -255,7 +255,7 @@
         state
             .onEach {
                 if (it.invalid.isNotEmpty()) _data.value = INVALID_DATA
-                else if (it.pending.isEmpty()) _data.value = it.data
+                else if (it.pending.isEmpty() && it.preUpdateCount == 0) _data.value = it.data
             }
             .launchIn(coroutineScope)
     }
@@ -269,16 +269,45 @@
         val pending: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
         val invalid: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
         val complete: Set<ComplicationEvaluationResultReceiver<out Any>> = setOf(),
+        val preUpdateCount: Int = 0,
     ) {
         val all = pending + invalid + complete
 
-        fun withInvalid(receiver: ComplicationEvaluationResultReceiver<out Any>) =
-            State(data, pending - receiver, invalid + receiver, complete - receiver)
+        init {
+            require(preUpdateCount >= 0) {
+                "DynamicTypeValueReceiver invoked onData() more times than onPreUpdate()."
+            }
+        }
 
-        fun withComplete(
+        fun withPreUpdate() =
+            State(
+                data,
+                pending = pending,
+                invalid = invalid,
+                complete = complete,
+                preUpdateCount + 1,
+            )
+
+        fun withInvalid(receiver: ComplicationEvaluationResultReceiver<out Any>) =
+            State(
+                data,
+                pending = pending - receiver,
+                invalid = invalid + receiver,
+                complete = complete - receiver,
+                preUpdateCount - 1,
+            )
+
+        fun withUpdate(
             data: WireComplicationData,
             receiver: ComplicationEvaluationResultReceiver<out Any>,
-        ) = State(data, pending - receiver, invalid - receiver, complete + receiver)
+        ) =
+            State(
+                data,
+                pending = pending - receiver,
+                invalid = invalid - receiver,
+                complete = complete + receiver,
+                preUpdateCount - 1,
+            )
     }
 
     private inner class ComplicationEvaluationResultReceiver<T : Any>(
@@ -300,14 +329,13 @@
             boundDynamicType.close()
         }
 
-        override fun onPreUpdate() {}
+        override fun onPreUpdate() {
+            state.update { it.withPreUpdate() }
+        }
 
         override fun onData(newData: T) {
             state.update {
-                it.withComplete(
-                    setter(WireComplicationData.Builder(it.data), newData).build(),
-                    this
-                )
+                it.withUpdate(setter(WireComplicationData.Builder(it.data), newData).build(), this)
             }
         }
 
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
index 68265ed..fb730b0 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
@@ -29,6 +29,7 @@
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
 import androidx.test.screenshot.AndroidXScreenshotTestRule
 import androidx.test.screenshot.assertAgainstGolden
 import androidx.wear.watchface.CanvasType
@@ -121,6 +122,7 @@
         /* digitalPreviewReferenceTimeMillis = */ 0
     )
 
+@SdkSuppress(maxSdkVersion = 32) // b/271922712
 @RunWith(AndroidJUnit4::class)
 @RequiresApi(Build.VERSION_CODES.O_MR1)
 @MediumTest
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
index 61e0e41..066ed00 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsObserver.kt
@@ -20,7 +20,6 @@
 import android.content.ContentResolver
 import android.content.Context
 import android.content.Intent
-import android.provider.Settings
 import androidx.annotation.RestrictTo
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Deferred
@@ -103,30 +102,22 @@
                 as KeyguardManager
 
         (watchState.isLocked as MutableStateFlow).value = keyguardManager.isDeviceLocked
+    }
 
-        // Before SysUI has connected, we use ActionScreenOn/ActionScreenOff as a trigger to query
-        // AMBIENT_ENABLED_PATH in order to determine if the device os ambient or not.
+    override fun onActionUserPresent() {
+        (watchState.isLocked as MutableStateFlow).value = false
+    }
+
+    override fun onActionAmbientStarted() {
         if (sysUiHasSentWatchUiState) {
             return
         }
 
         val isAmbient = watchState.isAmbient as MutableStateFlow
-
-        // This is a backup signal for when SysUI is unable to deliver the ambient state (e.g. in
-        // direct boot mode). We need to distinguish between ACTION_SCREEN_OFF for entering ambient
-        // and the screen turning off. This is only possible from R.
-        isAmbient.value =
-            if (ambientSettingAvailable) {
-                Settings.Global.getInt(contentResolver, AMBIENT_ENABLED_PATH, 0) == 1
-            } else {
-                // On P and below we just have to assume we're not ambient.
-                false
-            }
+        isAmbient.value = true
     }
 
-    override fun onActionScreenOn() {
-        // Before SysUI has connected, we use ActionScreenOn/ActionScreenOff as a trigger to query
-        // AMBIENT_ENABLED_PATH in order to determine if the device os ambient or not.
+    override fun onActionAmbientStopped() {
         if (sysUiHasSentWatchUiState) {
             return
         }
@@ -134,8 +125,4 @@
         val isAmbient = watchState.isAmbient as MutableStateFlow
         isAmbient.value = false
     }
-
-    override fun onActionUserPresent() {
-        (watchState.isLocked as MutableStateFlow).value = false
-    }
 }
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
index 4742681..644a68a 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/BroadcastsReceiver.kt
@@ -62,11 +62,14 @@
         /** Called when we receive [Intent.ACTION_SCREEN_OFF] */
         @UiThread public fun onActionScreenOff() {}
 
-        /** Called when we receive [Intent.ACTION_SCREEN_ON] */
-        @UiThread public fun onActionScreenOn() {}
-
         /** Called when we receive [Intent.ACTION_USER_PRESENT] */
         @UiThread public fun onActionUserPresent() {}
+
+        /** Called when we receive [ACTION_AMBIENT_STARTED] */
+        @UiThread public fun onActionAmbientStarted() {}
+
+        /** Called when we receive [ACTION_AMBIENT_STOPPED] */
+        @UiThread public fun onActionAmbientStopped() {}
     }
 
     companion object {
@@ -75,6 +78,12 @@
         // available programmatically. The value below is the default but it could be overridden
         // by OEMs.
         internal const val INITIAL_LOW_BATTERY_THRESHOLD = 15f
+
+        internal const val ACTION_AMBIENT_STARTED =
+            "com.google.android.wearable.action.AMBIENT_STARTED"
+
+        internal const val ACTION_AMBIENT_STOPPED =
+            "com.google.android.wearable.action.AMBIENT_STOPPED"
     }
 
     internal val receiver: BroadcastReceiver =
@@ -90,9 +99,10 @@
                     Intent.ACTION_TIME_TICK -> observer.onActionTimeTick()
                     Intent.ACTION_TIMEZONE_CHANGED -> observer.onActionTimeZoneChanged()
                     Intent.ACTION_SCREEN_OFF -> observer.onActionScreenOff()
-                    Intent.ACTION_SCREEN_ON -> observer.onActionScreenOn()
                     Intent.ACTION_USER_PRESENT -> observer.onActionUserPresent()
                     WatchFaceImpl.MOCK_TIME_INTENT -> observer.onMockTime(intent)
+                    ACTION_AMBIENT_STARTED -> observer.onActionAmbientStarted()
+                    ACTION_AMBIENT_STOPPED -> observer.onActionAmbientStopped()
                     else -> System.err.println("<< IGNORING $intent")
                 }
             }
@@ -112,6 +122,8 @@
                 addAction(Intent.ACTION_POWER_DISCONNECTED)
                 addAction(Intent.ACTION_USER_PRESENT)
                 addAction(WatchFaceImpl.MOCK_TIME_INTENT)
+                addAction(ACTION_AMBIENT_STARTED)
+                addAction(ACTION_AMBIENT_STOPPED)
             }
         )
     }
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index 59e942b..140549e 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -5658,9 +5658,7 @@
 
     @Test
     @Config(sdk = [Build.VERSION_CODES.R])
-    public fun onActionScreenOff_onActionScreenOn_ambientEnabled() {
-        Settings.Global.putInt(context.contentResolver, BroadcastsObserver.AMBIENT_ENABLED_PATH, 1)
-
+    public fun onActionAmbientStarted_onActionAmbientStopped_ambientEnabled() {
         testWatchFaceService =
             TestWatchFaceService(
                 WatchFaceType.DIGITAL,
@@ -5715,20 +5713,21 @@
 
         watchFaceImpl = engineWrapper.getWatchFaceImplOrNull()!!
 
-        watchFaceImpl.broadcastsObserver.onActionScreenOff()
+        watchFaceImpl.broadcastsObserver.onActionAmbientStarted()
         assertThat(watchState.isAmbient.value).isTrue()
 
-        watchFaceImpl.broadcastsObserver.onActionScreenOn()
+        watchFaceImpl.broadcastsObserver.onActionAmbientStopped()
         assertThat(watchState.isAmbient.value).isFalse()
 
-        // After SysUI has sent WatchUiState onActionScreenOff/onActionScreenOn should be ignored.
+        // After SysUI has sent WatchUiState onActionAmbientStarted/onActionAmbientStopped should be
+        // ignored.
         engineWrapper.setWatchUiState(WatchUiState(false, 0), fromSysUi = true)
 
-        watchFaceImpl.broadcastsObserver.onActionScreenOff()
+        watchFaceImpl.broadcastsObserver.onActionAmbientStarted()
         assertThat(watchState.isAmbient.value).isFalse()
 
         engineWrapper.setWatchUiState(WatchUiState(true, 0), fromSysUi = true)
-        watchFaceImpl.broadcastsObserver.onActionScreenOn()
+        watchFaceImpl.broadcastsObserver.onActionAmbientStopped()
         assertThat(watchState.isAmbient.value).isTrue()
     }
 
diff --git a/webkit/webkit/api/1.7.0-beta01.txt b/webkit/webkit/api/1.7.0-beta01.txt
new file mode 100644
index 0000000..eac20ef
--- /dev/null
+++ b/webkit/webkit/api/1.7.0-beta01.txt
@@ -0,0 +1,312 @@
+// Signature format: 4.0
+package androidx.webkit {
+
+  public class CookieManagerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
+  }
+
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public class ProcessGlobalConfig {
+    ctor public ProcessGlobalConfig();
+    method public static void apply(androidx.webkit.ProcessGlobalConfig);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDataDirectorySuffix(android.content.Context, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDirectoryBasePath(android.content.Context, String, String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setRequestedWithHeaderOriginAllowList(java.util.Set<java.lang.String!>);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isAlgorithmicDarkeningAllowed(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setAlgorithmicDarkeningAllowed(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings, boolean);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setRequestedWithHeaderOriginAllowList(android.webkit.WebSettings, java.util.Set<java.lang.String!>);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field @Deprecated public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field @Deprecated public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field @Deprecated public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+    field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_VARIATIONS_HEADER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static String getVariationsHeader();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ALLOWLIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingAllowlist(java.util.Set<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    method public static boolean isStartupFeatureSupported(android.content.Context, String);
+    field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_COOKIE_INFO = "GET_COOKIE_INFO";
+    field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field @Deprecated public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX = "STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX";
+    field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/webkit/webkit/api/public_plus_experimental_1.7.0-beta01.txt b/webkit/webkit/api/public_plus_experimental_1.7.0-beta01.txt
new file mode 100644
index 0000000..eac20ef
--- /dev/null
+++ b/webkit/webkit/api/public_plus_experimental_1.7.0-beta01.txt
@@ -0,0 +1,312 @@
+// Signature format: 4.0
+package androidx.webkit {
+
+  public class CookieManagerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
+  }
+
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public class ProcessGlobalConfig {
+    ctor public ProcessGlobalConfig();
+    method public static void apply(androidx.webkit.ProcessGlobalConfig);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDataDirectorySuffix(android.content.Context, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDirectoryBasePath(android.content.Context, String, String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setRequestedWithHeaderOriginAllowList(java.util.Set<java.lang.String!>);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isAlgorithmicDarkeningAllowed(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setAlgorithmicDarkeningAllowed(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings, boolean);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setRequestedWithHeaderOriginAllowList(android.webkit.WebSettings, java.util.Set<java.lang.String!>);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field @Deprecated public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field @Deprecated public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field @Deprecated public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+    field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_VARIATIONS_HEADER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static String getVariationsHeader();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ALLOWLIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingAllowlist(java.util.Set<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    method public static boolean isStartupFeatureSupported(android.content.Context, String);
+    field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_COOKIE_INFO = "GET_COOKIE_INFO";
+    field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field @Deprecated public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX = "STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX";
+    field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/webkit/webkit/api/res-1.7.0-beta01.txt b/webkit/webkit/api/res-1.7.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/webkit/webkit/api/res-1.7.0-beta01.txt
diff --git a/webkit/webkit/api/restricted_1.7.0-beta01.txt b/webkit/webkit/api/restricted_1.7.0-beta01.txt
new file mode 100644
index 0000000..eac20ef
--- /dev/null
+++ b/webkit/webkit/api/restricted_1.7.0-beta01.txt
@@ -0,0 +1,312 @@
+// Signature format: 4.0
+package androidx.webkit {
+
+  public class CookieManagerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_COOKIE_INFO, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.List<java.lang.String!> getCookieInfo(android.webkit.CookieManager, String);
+  }
+
+  public final class DropDataContentProvider extends android.content.ContentProvider {
+    ctor public DropDataContentProvider();
+    method public int delete(android.net.Uri, String?, String![]?);
+    method public String? getType(android.net.Uri);
+    method public android.net.Uri? insert(android.net.Uri, android.content.ContentValues?);
+    method public boolean onCreate();
+    method public android.database.Cursor? query(android.net.Uri, String![]?, String?, String![]?, String?);
+    method public int update(android.net.Uri, android.content.ContentValues?, String?, String![]?);
+  }
+
+  public abstract class JavaScriptReplyProxy {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(String);
+  }
+
+  public class ProcessGlobalConfig {
+    ctor public ProcessGlobalConfig();
+    method public static void apply(androidx.webkit.ProcessGlobalConfig);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDataDirectorySuffix(android.content.Context, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH, enforcement="androidx.webkit.WebViewFeature#isConfigFeatureSupported(String, Context)") public androidx.webkit.ProcessGlobalConfig setDirectoryBasePath(android.content.Context, String, String);
+  }
+
+  public final class ProxyConfig {
+    method public java.util.List<java.lang.String!> getBypassRules();
+    method public java.util.List<androidx.webkit.ProxyConfig.ProxyRule!> getProxyRules();
+    method public boolean isReverseBypassEnabled();
+    field public static final String MATCH_ALL_SCHEMES = "*";
+    field public static final String MATCH_HTTP = "http";
+    field public static final String MATCH_HTTPS = "https";
+  }
+
+  public static final class ProxyConfig.Builder {
+    ctor public ProxyConfig.Builder();
+    ctor public ProxyConfig.Builder(androidx.webkit.ProxyConfig);
+    method public androidx.webkit.ProxyConfig.Builder addBypassRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect(String);
+    method public androidx.webkit.ProxyConfig.Builder addDirect();
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String);
+    method public androidx.webkit.ProxyConfig.Builder addProxyRule(String, String);
+    method public androidx.webkit.ProxyConfig build();
+    method public androidx.webkit.ProxyConfig.Builder bypassSimpleHostnames();
+    method public androidx.webkit.ProxyConfig.Builder removeImplicitRules();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE_REVERSE_BYPASS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public androidx.webkit.ProxyConfig.Builder setReverseBypassEnabled(boolean);
+  }
+
+  public static final class ProxyConfig.ProxyRule {
+    method public String getSchemeFilter();
+    method public String getUrl();
+  }
+
+  public abstract class ProxyController {
+    method public abstract void clearProxyOverride(java.util.concurrent.Executor, Runnable);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.PROXY_OVERRIDE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ProxyController getInstance();
+    method public abstract void setProxyOverride(androidx.webkit.ProxyConfig, java.util.concurrent.Executor, Runnable);
+  }
+
+  public abstract class SafeBrowsingResponseCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void backToSafety(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_PROCEED, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void proceed(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void showInterstitial(boolean);
+  }
+
+  public abstract class ServiceWorkerClientCompat {
+    ctor public ServiceWorkerClientCompat();
+    method @WorkerThread public abstract android.webkit.WebResourceResponse? shouldInterceptRequest(android.webkit.WebResourceRequest);
+  }
+
+  public abstract class ServiceWorkerControllerCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.ServiceWorkerControllerCompat getInstance();
+    method public abstract androidx.webkit.ServiceWorkerWebSettingsCompat getServiceWorkerWebSettings();
+    method public abstract void setServiceWorkerClient(androidx.webkit.ServiceWorkerClientCompat?);
+  }
+
+  public abstract class ServiceWorkerWebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowContentAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getAllowFileAccess();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract boolean getBlockNetworkLoads();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getCacheMode();
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CONTENT_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowContentAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_FILE_ACCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setAllowFileAccess(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_BLOCK_NETWORK_LOADS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setBlockNetworkLoads(boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SERVICE_WORKER_CACHE_MODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setCacheMode(int);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setRequestedWithHeaderOriginAllowList(java.util.Set<java.lang.String!>);
+  }
+
+  public class TracingConfig {
+    method public java.util.List<java.lang.String!> getCustomIncludedCategories();
+    method public int getPredefinedCategories();
+    method public int getTracingMode();
+    field public static final int CATEGORIES_ALL = 1; // 0x1
+    field public static final int CATEGORIES_ANDROID_WEBVIEW = 2; // 0x2
+    field public static final int CATEGORIES_FRAME_VIEWER = 64; // 0x40
+    field public static final int CATEGORIES_INPUT_LATENCY = 8; // 0x8
+    field public static final int CATEGORIES_JAVASCRIPT_AND_RENDERING = 32; // 0x20
+    field public static final int CATEGORIES_NONE = 0; // 0x0
+    field public static final int CATEGORIES_RENDERING = 16; // 0x10
+    field public static final int CATEGORIES_WEB_DEVELOPER = 4; // 0x4
+    field public static final int RECORD_CONTINUOUSLY = 1; // 0x1
+    field public static final int RECORD_UNTIL_FULL = 0; // 0x0
+  }
+
+  public static class TracingConfig.Builder {
+    ctor public TracingConfig.Builder();
+    method public androidx.webkit.TracingConfig.Builder addCategories(int...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.lang.String!...);
+    method public androidx.webkit.TracingConfig.Builder addCategories(java.util.Collection<java.lang.String!>);
+    method public androidx.webkit.TracingConfig build();
+    method public androidx.webkit.TracingConfig.Builder setTracingMode(int);
+  }
+
+  public abstract class TracingController {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.TRACING_CONTROLLER_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.TracingController getInstance();
+    method public abstract boolean isTracing();
+    method public abstract void start(androidx.webkit.TracingConfig);
+    method public abstract boolean stop(java.io.OutputStream?, java.util.concurrent.Executor);
+  }
+
+  public class WebMessageCompat {
+    ctor public WebMessageCompat(String?);
+    ctor public WebMessageCompat(String?, androidx.webkit.WebMessagePortCompat![]?);
+    method public String? getData();
+    method public androidx.webkit.WebMessagePortCompat![]? getPorts();
+  }
+
+  public abstract class WebMessagePortCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_CLOSE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void close();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_POST_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void postMessage(androidx.webkit.WebMessageCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract void setWebMessageCallback(android.os.Handler?, androidx.webkit.WebMessagePortCompat.WebMessageCallbackCompat);
+  }
+
+  public abstract static class WebMessagePortCompat.WebMessageCallbackCompat {
+    ctor public WebMessagePortCompat.WebMessageCallbackCompat();
+    method public void onMessage(androidx.webkit.WebMessagePortCompat, androidx.webkit.WebMessageCompat?);
+  }
+
+  public abstract class WebResourceErrorCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_DESCRIPTION, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract CharSequence getDescription();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_ERROR_GET_CODE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public abstract int getErrorCode();
+  }
+
+  public class WebResourceRequestCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_RESOURCE_REQUEST_IS_REDIRECT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isRedirect(android.webkit.WebResourceRequest);
+  }
+
+  public class WebSettingsCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getDisabledActionModeMenuItems(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDark(android.webkit.WebSettings);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static int getForceDarkStrategy(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getOffscreenPreRaster(android.webkit.WebSettings);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static java.util.Set<java.lang.String!> getRequestedWithHeaderOriginAllowList(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean getSafeBrowsingEnabled(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isAlgorithmicDarkeningAllowed(android.webkit.WebSettings);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ALGORITHMIC_DARKENING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setAlgorithmicDarkeningAllowed(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.DISABLED_ACTION_MODE_MENU_ITEMS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setDisabledActionModeMenuItems(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setEnterpriseAuthenticationAppLinkPolicyEnabled(android.webkit.WebSettings, boolean);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDark(android.webkit.WebSettings, int);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.FORCE_DARK_STRATEGY, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setForceDarkStrategy(android.webkit.WebSettings, int);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.OFF_SCREEN_PRERASTER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setOffscreenPreRaster(android.webkit.WebSettings, boolean);
+    method @RequiresFeature(name="REQUESTED_WITH_HEADER_ALLOW_LIST", enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setRequestedWithHeaderOriginAllowList(android.webkit.WebSettings, java.util.Set<java.lang.String!>);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ENABLE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingEnabled(android.webkit.WebSettings, boolean);
+    field @Deprecated public static final int DARK_STRATEGY_PREFER_WEB_THEME_OVER_USER_AGENT_DARKENING = 2; // 0x2
+    field @Deprecated public static final int DARK_STRATEGY_USER_AGENT_DARKENING_ONLY = 0; // 0x0
+    field @Deprecated public static final int DARK_STRATEGY_WEB_THEME_DARKENING_ONLY = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_AUTO = 1; // 0x1
+    field @Deprecated public static final int FORCE_DARK_OFF = 0; // 0x0
+    field @Deprecated public static final int FORCE_DARK_ON = 2; // 0x2
+  }
+
+  public final class WebViewAssetLoader {
+    method @WorkerThread public android.webkit.WebResourceResponse? shouldInterceptRequest(android.net.Uri);
+    field public static final String DEFAULT_DOMAIN = "appassets.androidplatform.net";
+  }
+
+  public static final class WebViewAssetLoader.AssetsPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.AssetsPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.Builder {
+    ctor public WebViewAssetLoader.Builder();
+    method public androidx.webkit.WebViewAssetLoader.Builder addPathHandler(String, androidx.webkit.WebViewAssetLoader.PathHandler);
+    method public androidx.webkit.WebViewAssetLoader build();
+    method public androidx.webkit.WebViewAssetLoader.Builder setDomain(String);
+    method public androidx.webkit.WebViewAssetLoader.Builder setHttpAllowed(boolean);
+  }
+
+  public static final class WebViewAssetLoader.InternalStoragePathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.InternalStoragePathHandler(android.content.Context, java.io.File);
+    method @WorkerThread public android.webkit.WebResourceResponse handle(String);
+  }
+
+  public static interface WebViewAssetLoader.PathHandler {
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public static final class WebViewAssetLoader.ResourcesPathHandler implements androidx.webkit.WebViewAssetLoader.PathHandler {
+    ctor public WebViewAssetLoader.ResourcesPathHandler(android.content.Context);
+    method @WorkerThread public android.webkit.WebResourceResponse? handle(String);
+  }
+
+  public class WebViewClientCompat extends android.webkit.WebViewClient {
+    ctor public WebViewClientCompat();
+    method @RequiresApi(23) public final void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, android.webkit.WebResourceError);
+    method @RequiresApi(21) @UiThread public void onReceivedError(android.webkit.WebView, android.webkit.WebResourceRequest, androidx.webkit.WebResourceErrorCompat);
+    method @RequiresApi(27) public final void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, android.webkit.SafeBrowsingResponse);
+    method @UiThread public void onSafeBrowsingHit(android.webkit.WebView, android.webkit.WebResourceRequest, int, androidx.webkit.SafeBrowsingResponseCompat);
+  }
+
+  public class WebViewCompat {
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void addWebMessageListener(android.webkit.WebView, String, java.util.Set<java.lang.String!>, androidx.webkit.WebViewCompat.WebMessageListener);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.CREATE_WEB_MESSAGE_CHANNEL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebMessagePortCompat![] createWebMessageChannel(android.webkit.WebView);
+    method public static android.content.pm.PackageInfo? getCurrentWebViewPackage(android.content.Context);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_PRIVACY_POLICY_URL, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.net.Uri getSafeBrowsingPrivacyPolicyUrl();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_VARIATIONS_HEADER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static String getVariationsHeader();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_CHROME_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebChromeClient? getWebChromeClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_CLIENT, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static android.webkit.WebViewClient getWebViewClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.GET_WEB_VIEW_RENDERER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcess? getWebViewRenderProcess(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static androidx.webkit.WebViewRenderProcessClient? getWebViewRenderProcessClient(android.webkit.WebView);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.MULTI_PROCESS, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static boolean isMultiProcessEnabled();
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.VISUAL_STATE_CALLBACK, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postVisualStateCallback(android.webkit.WebView, long, androidx.webkit.WebViewCompat.VisualStateCallback);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.POST_WEB_MESSAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void postWebMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_MESSAGE_LISTENER, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void removeWebMessageListener(android.webkit.WebView, String);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_ALLOWLIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingAllowlist(java.util.Set<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @Deprecated @RequiresFeature(name=androidx.webkit.WebViewFeature.SAFE_BROWSING_WHITELIST, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setSafeBrowsingWhitelist(java.util.List<java.lang.String!>, android.webkit.ValueCallback<java.lang.Boolean!>?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, java.util.concurrent.Executor, androidx.webkit.WebViewRenderProcessClient);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void setWebViewRenderProcessClient(android.webkit.WebView, androidx.webkit.WebViewRenderProcessClient?);
+    method @RequiresFeature(name=androidx.webkit.WebViewFeature.START_SAFE_BROWSING, enforcement="androidx.webkit.WebViewFeature#isFeatureSupported") public static void startSafeBrowsing(android.content.Context, android.webkit.ValueCallback<java.lang.Boolean!>?);
+  }
+
+  public static interface WebViewCompat.VisualStateCallback {
+    method @UiThread public void onComplete(long);
+  }
+
+  public static interface WebViewCompat.WebMessageListener {
+    method @UiThread public void onPostMessage(android.webkit.WebView, androidx.webkit.WebMessageCompat, android.net.Uri, boolean, androidx.webkit.JavaScriptReplyProxy);
+  }
+
+  public class WebViewFeature {
+    method public static boolean isFeatureSupported(String);
+    method public static boolean isStartupFeatureSupported(android.content.Context, String);
+    field public static final String ALGORITHMIC_DARKENING = "ALGORITHMIC_DARKENING";
+    field public static final String CREATE_WEB_MESSAGE_CHANNEL = "CREATE_WEB_MESSAGE_CHANNEL";
+    field public static final String DISABLED_ACTION_MODE_MENU_ITEMS = "DISABLED_ACTION_MODE_MENU_ITEMS";
+    field public static final String ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY = "ENTERPRISE_AUTHENTICATION_APP_LINK_POLICY";
+    field public static final String FORCE_DARK = "FORCE_DARK";
+    field public static final String FORCE_DARK_STRATEGY = "FORCE_DARK_STRATEGY";
+    field public static final String GET_COOKIE_INFO = "GET_COOKIE_INFO";
+    field public static final String GET_VARIATIONS_HEADER = "GET_VARIATIONS_HEADER";
+    field public static final String GET_WEB_CHROME_CLIENT = "GET_WEB_CHROME_CLIENT";
+    field public static final String GET_WEB_VIEW_CLIENT = "GET_WEB_VIEW_CLIENT";
+    field public static final String GET_WEB_VIEW_RENDERER = "GET_WEB_VIEW_RENDERER";
+    field public static final String MULTI_PROCESS = "MULTI_PROCESS";
+    field public static final String OFF_SCREEN_PRERASTER = "OFF_SCREEN_PRERASTER";
+    field public static final String POST_WEB_MESSAGE = "POST_WEB_MESSAGE";
+    field public static final String PROXY_OVERRIDE = "PROXY_OVERRIDE";
+    field public static final String PROXY_OVERRIDE_REVERSE_BYPASS = "PROXY_OVERRIDE_REVERSE_BYPASS";
+    field public static final String RECEIVE_HTTP_ERROR = "RECEIVE_HTTP_ERROR";
+    field public static final String RECEIVE_WEB_RESOURCE_ERROR = "RECEIVE_WEB_RESOURCE_ERROR";
+    field public static final String SAFE_BROWSING_ALLOWLIST = "SAFE_BROWSING_ALLOWLIST";
+    field public static final String SAFE_BROWSING_ENABLE = "SAFE_BROWSING_ENABLE";
+    field public static final String SAFE_BROWSING_HIT = "SAFE_BROWSING_HIT";
+    field public static final String SAFE_BROWSING_PRIVACY_POLICY_URL = "SAFE_BROWSING_PRIVACY_POLICY_URL";
+    field public static final String SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY = "SAFE_BROWSING_RESPONSE_BACK_TO_SAFETY";
+    field public static final String SAFE_BROWSING_RESPONSE_PROCEED = "SAFE_BROWSING_RESPONSE_PROCEED";
+    field public static final String SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL = "SAFE_BROWSING_RESPONSE_SHOW_INTERSTITIAL";
+    field @Deprecated public static final String SAFE_BROWSING_WHITELIST = "SAFE_BROWSING_WHITELIST";
+    field public static final String SERVICE_WORKER_BASIC_USAGE = "SERVICE_WORKER_BASIC_USAGE";
+    field public static final String SERVICE_WORKER_BLOCK_NETWORK_LOADS = "SERVICE_WORKER_BLOCK_NETWORK_LOADS";
+    field public static final String SERVICE_WORKER_CACHE_MODE = "SERVICE_WORKER_CACHE_MODE";
+    field public static final String SERVICE_WORKER_CONTENT_ACCESS = "SERVICE_WORKER_CONTENT_ACCESS";
+    field public static final String SERVICE_WORKER_FILE_ACCESS = "SERVICE_WORKER_FILE_ACCESS";
+    field public static final String SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST = "SERVICE_WORKER_SHOULD_INTERCEPT_REQUEST";
+    field public static final String SHOULD_OVERRIDE_WITH_REDIRECTS = "SHOULD_OVERRIDE_WITH_REDIRECTS";
+    field public static final String STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX = "STARTUP_FEATURE_SET_DATA_DIRECTORY_SUFFIX";
+    field public static final String STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH = "STARTUP_FEATURE_SET_DIRECTORY_BASE_PATH";
+    field public static final String START_SAFE_BROWSING = "START_SAFE_BROWSING";
+    field public static final String TRACING_CONTROLLER_BASIC_USAGE = "TRACING_CONTROLLER_BASIC_USAGE";
+    field public static final String VISUAL_STATE_CALLBACK = "VISUAL_STATE_CALLBACK";
+    field public static final String WEB_MESSAGE_CALLBACK_ON_MESSAGE = "WEB_MESSAGE_CALLBACK_ON_MESSAGE";
+    field public static final String WEB_MESSAGE_LISTENER = "WEB_MESSAGE_LISTENER";
+    field public static final String WEB_MESSAGE_PORT_CLOSE = "WEB_MESSAGE_PORT_CLOSE";
+    field public static final String WEB_MESSAGE_PORT_POST_MESSAGE = "WEB_MESSAGE_PORT_POST_MESSAGE";
+    field public static final String WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK = "WEB_MESSAGE_PORT_SET_MESSAGE_CALLBACK";
+    field public static final String WEB_RESOURCE_ERROR_GET_CODE = "WEB_RESOURCE_ERROR_GET_CODE";
+    field public static final String WEB_RESOURCE_ERROR_GET_DESCRIPTION = "WEB_RESOURCE_ERROR_GET_DESCRIPTION";
+    field public static final String WEB_RESOURCE_REQUEST_IS_REDIRECT = "WEB_RESOURCE_REQUEST_IS_REDIRECT";
+    field public static final String WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE = "WEB_VIEW_RENDERER_CLIENT_BASIC_USAGE";
+    field public static final String WEB_VIEW_RENDERER_TERMINATE = "WEB_VIEW_RENDERER_TERMINATE";
+  }
+
+  public abstract class WebViewRenderProcess {
+    ctor public WebViewRenderProcess();
+    method public abstract boolean terminate();
+  }
+
+  public abstract class WebViewRenderProcessClient {
+    ctor public WebViewRenderProcessClient();
+    method public abstract void onRenderProcessResponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+    method public abstract void onRenderProcessUnresponsive(android.webkit.WebView, androidx.webkit.WebViewRenderProcess?);
+  }
+
+}
+
diff --git a/window/window-testing/api/public_plus_experimental_1.1.0-beta01.txt b/window/window-testing/api/public_plus_experimental_1.1.0-beta01.txt
index 63f8aa1..16174f8 100644
--- a/window/window-testing/api/public_plus_experimental_1.1.0-beta01.txt
+++ b/window/window-testing/api/public_plus_experimental_1.1.0-beta01.txt
@@ -9,6 +9,12 @@
     method public void overrideSplitSupportStatus(androidx.window.embedding.SplitController.SplitSupportStatus status);
   }
 
+  public final class TestActivityStack {
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess, optional boolean isEmpty);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack();
+  }
+
   public final class TestSplitAttributesCalculatorParams {
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied, optional String? splitRuleTag);
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied);
@@ -18,6 +24,13 @@
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics);
   }
 
+  public final class TestSplitInfo {
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack, optional androidx.window.embedding.SplitAttributes splitAttributes);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo();
+  }
+
 }
 
 package androidx.window.testing.layout {
diff --git a/window/window-testing/api/public_plus_experimental_current.txt b/window/window-testing/api/public_plus_experimental_current.txt
index 63f8aa1..16174f8 100644
--- a/window/window-testing/api/public_plus_experimental_current.txt
+++ b/window/window-testing/api/public_plus_experimental_current.txt
@@ -9,6 +9,12 @@
     method public void overrideSplitSupportStatus(androidx.window.embedding.SplitController.SplitSupportStatus status);
   }
 
+  public final class TestActivityStack {
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess, optional boolean isEmpty);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack(optional java.util.List<? extends android.app.Activity> activitiesInProcess);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.ActivityStack createTestActivityStack();
+  }
+
   public final class TestSplitAttributesCalculatorParams {
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied, optional String? splitRuleTag);
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics, optional android.content.res.Configuration parentConfiguration, optional androidx.window.layout.WindowLayoutInfo parentWindowLayoutInfo, optional androidx.window.embedding.SplitAttributes defaultSplitAttributes, optional boolean areDefaultConstraintsSatisfied);
@@ -18,6 +24,13 @@
     method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitAttributesCalculatorParams createTestSplitAttributesCalculatorParams(androidx.window.layout.WindowMetrics parentWindowMetrics);
   }
 
+  public final class TestSplitInfo {
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack, optional androidx.window.embedding.SplitAttributes splitAttributes);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack, optional androidx.window.embedding.ActivityStack secondActivityStack);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo(optional androidx.window.embedding.ActivityStack primaryActivityStack);
+    method @androidx.window.core.ExperimentalWindowApi public static androidx.window.embedding.SplitInfo createTestSplitInfo();
+  }
+
 }
 
 package androidx.window.testing.layout {
diff --git a/window/window-testing/build.gradle b/window/window-testing/build.gradle
index b1b0851..9ccb288 100644
--- a/window/window-testing/build.gradle
+++ b/window/window-testing/build.gradle
@@ -45,6 +45,7 @@
     androidTestImplementation(libs.truth)
 
     testImplementation(libs.robolectric)
+    testImplementation(libs.mockitoCore4)
     testImplementation(libs.mockitoKotlin4)
     testImplementation(libs.testCore)
     testImplementation(libs.testExtJunit)
diff --git a/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt b/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt
new file mode 100644
index 0000000..a8e66a2
--- /dev/null
+++ b/window/window-testing/src/main/java/androidx/window/testing/embedding/ActivityStackTesting.kt
@@ -0,0 +1,44 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("TestActivityStack")
+
+package androidx.window.testing.embedding
+
+import android.app.Activity
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.ActivityStack
+
+/**
+ * Creates an [ActivityStack] instance for testing, which defaults to an [ActivityStack] with
+ * cross-process activities.
+ *
+ * The [activitiesInProcess] can be passed from the activity obtained from
+ * [androidx.test.core.app.ActivityScenario] or even mock Activities.
+ *
+ * @param activitiesInProcess The [Activity] list with the same process of the host task with
+ *     empty list as the default value
+ * @param isEmpty Indicates whether this `ActivityStack` contains any [Activity] regardless of the
+ *     process with `false` as the default value
+ * @return An [ActivityStack] instance for testing
+ */
+@Suppress("FunctionName")
+@ExperimentalWindowApi
+@JvmName("createTestActivityStack")
+@JvmOverloads
+fun TestActivityStack(
+    activitiesInProcess: List<Activity> = emptyList(),
+    isEmpty: Boolean = false,
+): ActivityStack = ActivityStack(activitiesInProcess, isEmpty)
\ No newline at end of file
diff --git a/window/window-testing/src/main/java/androidx/window/testing/embedding/SplitInfoTesting.kt b/window/window-testing/src/main/java/androidx/window/testing/embedding/SplitInfoTesting.kt
new file mode 100644
index 0000000..3ce87ed
--- /dev/null
+++ b/window/window-testing/src/main/java/androidx/window/testing/embedding/SplitInfoTesting.kt
@@ -0,0 +1,47 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+@file:JvmName("TestSplitInfo")
+
+package androidx.window.testing.embedding
+
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.ActivityStack
+import androidx.window.embedding.SplitAttributes
+import androidx.window.embedding.SplitInfo
+
+/**
+ * Creates a [SplitInfo] instance for testing.
+ *
+ * It is suggested to construct [primaryActivityStack] and [secondActivityStack] by
+ * [TestActivityStack], and [splitAttributes] by [SplitAttributes.Builder] APIs
+ *
+ * @param primaryActivityStack The primary [ActivityStack] with an empty [ActivityStack] as the
+ *     default value.
+ * @param secondActivityStack The secondary [ActivityStack] with an empty [ActivityStack] as the
+ *     default value.
+ * @param splitAttributes The current [SplitAttributes] for this test split, which defaults to
+ *     split equally with layout direction [SplitAttributes.LayoutDirection.LOCALE].
+ * @return A [SplitInfo] instance for testing
+ */
+@Suppress("FunctionName")
+@ExperimentalWindowApi
+@JvmName("createTestSplitInfo")
+@JvmOverloads
+fun TestSplitInfo(
+    primaryActivityStack: ActivityStack = TestActivityStack(),
+    secondActivityStack: ActivityStack = TestActivityStack(),
+    splitAttributes: SplitAttributes = SplitAttributes.Builder().build(),
+): SplitInfo = SplitInfo(primaryActivityStack, secondActivityStack, splitAttributes)
\ No newline at end of file
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityEmbeddingTestRuleTest.kt b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityEmbeddingTestRuleTest.kt
index c302053..b1c22cb 100644
--- a/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityEmbeddingTestRuleTest.kt
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityEmbeddingTestRuleTest.kt
@@ -272,7 +272,12 @@
 
     @Test
     fun testOverrideSplitInfo() = testScope.runTest {
-        val expected = listOf(TestSplitInfo(mockActivity, mockActivity))
+        val expected = listOf(
+            TestSplitInfo(
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+            )
+        )
 
         testRule.overrideSplitInfo(mockActivity, expected)
 
@@ -283,12 +288,17 @@
 
     @Test
     fun testOverrideSplitInfo_updatesExistingListeners() = testScope.runTest {
-        val expected1 = listOf(TestSplitInfo(mockActivity, mockActivity))
+        val expected1 = listOf(
+            TestSplitInfo(
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+            )
+        )
         val expected2 = listOf(
             TestSplitInfo(
-                mockActivity,
-                mockActivity,
-                SplitAttributes(splitType = SPLIT_TYPE_HINGE)
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+                TestActivityStack(listOf(mockActivity), isEmpty = false),
+                SplitAttributes(splitType = SPLIT_TYPE_HINGE),
             )
         )
 
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingJavaTest.java b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingJavaTest.java
new file mode 100644
index 0000000..ca3d9c4
--- /dev/null
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingJavaTest.java
@@ -0,0 +1,57 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.testing.embedding;
+
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+import static org.mockito.Mockito.mock;
+
+import android.app.Activity;
+
+import androidx.annotation.OptIn;
+import androidx.window.core.ExperimentalWindowApi;
+import androidx.window.embedding.ActivityStack;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+/** Test class to verify {@link TestActivityStack} */
+@OptIn(markerClass = ExperimentalWindowApi.class)
+public class ActivityStackTestingJavaTest {
+
+    /** Verifies the default value of {@link TestActivityStack} */
+    @Test
+    public void testActivityStackDefaultValue() {
+        final ActivityStack activityStack = TestActivityStack.createTestActivityStack();
+
+        assertEquals(new ActivityStack(Collections.emptyList(), false /* isEmpty */),
+                activityStack);
+    }
+
+    /** Verifies {@link TestActivityStack} */
+    @Test
+    public void testActivityStackWithNonEmptyActivityList() {
+        final Activity activity = mock(Activity.class);
+        final ActivityStack activityStack = TestActivityStack.createTestActivityStack(
+                Collections.singletonList(activity), false /* isEmpty */);
+
+        assertTrue(activityStack.contains(activity));
+        assertFalse(activityStack.isEmpty());
+    }
+}
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingTest.kt b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingTest.kt
new file mode 100644
index 0000000..905bb8f
--- /dev/null
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/ActivityStackTestingTest.kt
@@ -0,0 +1,49 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.testing.embedding
+
+import android.app.Activity
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.ActivityStack
+import org.junit.Assert.assertEquals
+import org.junit.Assert.assertFalse
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.mockito.kotlin.mock
+
+/** Test class to verify [TestActivityStack] */
+@OptIn(ExperimentalWindowApi::class)
+class ActivityStackTestingTest {
+
+    /** Verifies the default value of [TestActivityStack] */
+    @Test
+    fun testActivityStackDefaultValue() {
+        val activityStack = TestActivityStack()
+
+        assertEquals(ActivityStack(emptyList(), isEmpty = false), activityStack)
+    }
+
+    /** Verifies [TestActivityStack] */
+    @Test
+    fun testActivityStackWithNonEmptyActivityList() {
+        val activity = mock<Activity>()
+        val activityStack = TestActivityStack(listOf(activity), isEmpty = false)
+
+        assertTrue(activity in activityStack)
+        assertFalse(activityStack.isEmpty)
+    }
+}
\ No newline at end of file
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitAttributesCalculatorParamsTestingTest.kt b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitAttributesCalculatorParamsTestingTest.kt
index 975c16b..f8e8ce2 100644
--- a/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitAttributesCalculatorParamsTestingTest.kt
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitAttributesCalculatorParamsTestingTest.kt
@@ -40,6 +40,7 @@
 @OptIn(ExperimentalWindowApi::class)
 @RunWith(RobolectricTestRunner::class)
 class SplitAttributesCalculatorParamsTestingTest {
+
     /** Verifies if the default values of [TestSplitAttributesCalculatorParams] are as expected. */
     @Test
     fun testDefaults() {
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingJavaTest.java b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingJavaTest.java
new file mode 100644
index 0000000..8702a7f
--- /dev/null
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingJavaTest.java
@@ -0,0 +1,72 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.testing.embedding;
+
+import static org.junit.Assert.assertEquals;
+import static org.mockito.Mockito.mock;
+
+import android.app.Activity;
+
+import androidx.annotation.OptIn;
+import androidx.window.core.ExperimentalWindowApi;
+import androidx.window.embedding.ActivityStack;
+import androidx.window.embedding.SplitAttributes;
+import androidx.window.embedding.SplitInfo;
+
+import org.junit.Test;
+
+import java.util.Collections;
+
+/** Test class to verify {@link TestSplitInfo} */
+@OptIn(markerClass = ExperimentalWindowApi.class)
+public class SplitInfoTestingJavaTest {
+
+    /** Verifies the default value of {@link TestSplitInfo}. */
+    @Test
+    public void testSplitInfoDefaultValue() {
+        final SplitInfo splitInfo = TestSplitInfo.createTestSplitInfo();
+
+        assertEquals(TestActivityStack.createTestActivityStack(),
+                splitInfo.getPrimaryActivityStack());
+        assertEquals(TestActivityStack.createTestActivityStack(),
+                splitInfo.getSecondaryActivityStack());
+        assertEquals(new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+                .build(), splitInfo.getSplitAttributes());
+    }
+
+    /** Verifies {@link TestSplitInfo} */
+    @Test
+    public void testSplitInfoWithNonEmptyActivityStacks() {
+        final ActivityStack primaryActivityStack = TestActivityStack.createTestActivityStack(
+                Collections.singletonList(mock(Activity.class)), false /* isEmpty */);
+        final ActivityStack secondaryActivityStack = TestActivityStack.createTestActivityStack(
+                Collections.singletonList(mock(Activity.class)), false /* isEmpty */);
+        final SplitAttributes splitAttributes = new SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_HINGE)
+                .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+                .build();
+
+        final SplitInfo splitInfo = TestSplitInfo.createTestSplitInfo(primaryActivityStack,
+                secondaryActivityStack, splitAttributes);
+
+        assertEquals(primaryActivityStack, splitInfo.getPrimaryActivityStack());
+        assertEquals(secondaryActivityStack, splitInfo.getSecondaryActivityStack());
+        assertEquals(splitAttributes, splitInfo.getSplitAttributes());
+    }
+}
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingTest.kt b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingTest.kt
new file mode 100644
index 0000000..b434d3b
--- /dev/null
+++ b/window/window-testing/src/test/java/androidx/window/testing/embedding/SplitInfoTestingTest.kt
@@ -0,0 +1,60 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.testing.embedding
+
+import androidx.window.core.ExperimentalWindowApi
+import androidx.window.embedding.SplitAttributes
+import org.junit.Assert.assertEquals
+import org.junit.Test
+import org.mockito.kotlin.mock
+
+/** Test class to verify [TestSplitInfo] */
+@OptIn(ExperimentalWindowApi::class)
+class SplitInfoTestingTest {
+
+    /** Verifies the default value of [TestSplitInfo]. */
+    @Test
+    fun testSplitInfoDefaultValue() {
+        val splitInfo = TestSplitInfo()
+
+        assertEquals(TestActivityStack(), splitInfo.primaryActivityStack)
+        assertEquals(TestActivityStack(), splitInfo.secondaryActivityStack)
+        assertEquals(
+            SplitAttributes.Builder()
+                .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_EQUAL)
+                .setLayoutDirection(SplitAttributes.LayoutDirection.LOCALE)
+                .build(),
+            splitInfo.splitAttributes)
+    }
+
+    /** Verifies [TestSplitInfo] */
+    @Test
+    fun testSplitInfoWithNonEmptyActivityStacks() {
+        val primaryActivityStack = TestActivityStack(listOf(mock()), isEmpty = false)
+        val secondaryActivityStack = TestActivityStack(listOf(mock()), isEmpty = false)
+        val splitAttributes = SplitAttributes.Builder()
+            .setSplitType(SplitAttributes.SplitType.SPLIT_TYPE_HINGE)
+            .setLayoutDirection(SplitAttributes.LayoutDirection.TOP_TO_BOTTOM)
+            .build()
+
+        val splitInfo = TestSplitInfo(primaryActivityStack, secondaryActivityStack, splitAttributes)
+
+        assertEquals(primaryActivityStack, splitInfo.primaryActivityStack)
+        assertEquals(secondaryActivityStack, splitInfo.secondaryActivityStack)
+        assertEquals(splitAttributes, splitInfo.splitAttributes)
+    }
+}
\ No newline at end of file
diff --git a/window/window-testing/src/test/java/androidx/window/testing/embedding/TestSplitInfo.kt b/window/window-testing/src/test/java/androidx/window/testing/embedding/TestSplitInfo.kt
deleted file mode 100644
index b7fc505..0000000
--- a/window/window-testing/src/test/java/androidx/window/testing/embedding/TestSplitInfo.kt
+++ /dev/null
@@ -1,69 +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.window.testing.embedding
-
-import android.app.Activity
-import androidx.window.core.ExperimentalWindowApi
-import androidx.window.embedding.ActivityStack
-import androidx.window.embedding.SplitAttributes
-import androidx.window.embedding.SplitInfo
-
-/**
- * A convenience method to get a test [SplitInfo] with default values provided. With the default
- * values it returns an empty [ActivityStack] for the primary and secondary stacks. The default
- * [SplitAttributes] are for splitting equally and matching the locale layout.
- *
- * Note: This method should be used for testing local logic as opposed to end to end verification.
- * End to end verification requires a device that supports Activity Embedding.
- *
- * @param primaryActivity the [Activity] for the primary container.
- * @param secondaryActivity the [Activity] for the secondary container.
- * @param splitAttributes the [SplitAttributes].
- */
-@ExperimentalWindowApi
-fun TestSplitInfo(
-    primaryActivity: Activity,
-    secondaryActivity: Activity,
-    splitAttributes: SplitAttributes = SplitAttributes()
-): SplitInfo {
-    val primaryActivityStack = TestActivityStack(primaryActivity, false)
-    val secondaryActivityStack = TestActivityStack(secondaryActivity, false)
-    return SplitInfo(primaryActivityStack, secondaryActivityStack, splitAttributes)
-}
-
-/**
- * A convenience method to get a test [ActivityStack] with default values provided. With the default
- * values, there will be a single [Activity] in the stack and it will be considered not empty.
- *
- * Note: This method should be used for testing local logic as opposed to end to end verification.
- * End to end verification requires a device that supports Activity Embedding.
- *
- * @param testActivity an [Activity] that should be considered in the stack
- * @param isEmpty states if the stack is empty or not. In practice an [ActivityStack] with a single
- * [Activity] but [isEmpty] set to `false` means there is an [Activity] from outside the process
- * in the stack.
- */
-@ExperimentalWindowApi
-fun TestActivityStack(
-    testActivity: Activity,
-    isEmpty: Boolean = true
-): ActivityStack {
-    return ActivityStack(
-        listOf(testActivity),
-        isEmpty
-    )
-}
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
index 5c022fa..399401b 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairFilter.kt
@@ -32,64 +32,77 @@
  * If the filter matches the primary [Activity.getComponentName] and the new started activity
  * [Intent], it matches the [SplitPairRule] that holds this filter.
  *
- * @param primaryActivityName Component name of the primary activity in the split. Must be
- * non-empty. Can contain a single wildcard at the end.
- * Supported formats:
- * - package/class
- * - `package/*`
- * - `package/suffix.*`
- * - `*/*`
- * @param secondaryActivityName Component name of the secondary activity in the split. Must be
- * non-empty. Can contain a single wildcard at the end.
- * Supported formats:
- * - package/class
- * - `package/*`
- * - `package/suffix.*`
- * - `*/*`
- * @param secondaryActivityIntentAction action used for secondary activity launch Intent. If it is
- * not `null`, the [SplitPairFilter] will check the activity [Intent.getAction] besides the
- * component name. If it is `null`, [Intent.getAction] will be ignored.
+
  */
-class SplitPairFilter(
-    /**
-     * Component name of the primary activity in the split. Must be non-empty. Can contain a single
-     * wildcard at the end.
-     * Supported formats:
-     * - package/class
-     * - `package/*`
-     * - `package/suffix.*`
-     * - `*/*`
-     */
-    val primaryActivityName: ComponentName,
-    /**
-     * Component name of the secondary activity in the split. Must be non-empty. Can contain a
-     * single wildcard at the end.
-     * Supported formats:
-     * - package/class
-     * - `package/*`
-     * - `package/suffix.*`
-     * - `*/*`
-     */
-    val secondaryActivityName: ComponentName,
-    /**
-     * Action used for secondary activity launch Intent.
-     *
-     * If it is not `null`, the [SplitPairFilter] will check the activity [Intent.getAction] besides
-     * the component name. If it is `null`, [Intent.getAction] will be ignored.
-     */
+class SplitPairFilter internal constructor(
+    private val _primaryActivityName: ActivityComponentInfo,
+    private val _secondaryActivityName: ActivityComponentInfo,
     val secondaryActivityIntentAction: String?
 ) {
 
+    /**
+     * @param primaryActivityName Component name of the primary activity in the split. Must be
+     * non-empty. Can contain a single wildcard at the end.
+     * Supported formats:
+     * - package/class
+     * - `package/*`
+     * - `package/suffix.*`
+     * - `*/*`
+     * @param secondaryActivityName Component name of the secondary activity in the split. Must be
+     * non-empty. Can contain a single wildcard at the end.
+     * Supported formats:
+     * - package/class
+     * - `package/*`
+     * - `package/suffix.*`
+     * - `*/*`
+     * @param secondaryActivityIntentAction action used for secondary activity launch Intent. If it
+     * is not `null`, the [SplitPairFilter] will check the activity [Intent.getAction] besides the
+     * component name. If it is `null`, [Intent.getAction] will be ignored.
+     */
+    constructor(
+        /**
+         * Component name of the primary activity in the split. Must be non-empty. Can contain a
+         * single wildcard at the end.
+         * Supported formats:
+         * - package/class
+         * - `package/*`
+         * - `package/suffix.*`
+         * - `*/*`
+         */
+        primaryActivityName: ComponentName,
+        /**
+         * Component name of the secondary activity in the split. Must be non-empty. Can contain a
+         * single wildcard at the end.
+         * Supported formats:
+         * - package/class
+         * - `package/*`
+         * - `package/suffix.*`
+         * - `*/*`
+         */
+        secondaryActivityName: ComponentName,
+        /**
+         * Action used for secondary activity launch Intent.
+         *
+         * If it is not `null`, the [SplitPairFilter] will check the activity [Intent.getAction]
+         * besides the component name. If it is `null`, [Intent.getAction] will be ignored.
+         */
+        secondaryActivityIntentAction: String?
+    ) : this(
+        ActivityComponentInfo(primaryActivityName),
+        ActivityComponentInfo(secondaryActivityName),
+        secondaryActivityIntentAction
+    )
+
     init {
-        validateComponentName(primaryActivityName.packageName, primaryActivityName.className)
-        validateComponentName(secondaryActivityName.packageName, secondaryActivityName.className)
+        validateComponentName(_primaryActivityName.packageName, _primaryActivityName.className)
+        validateComponentName(_secondaryActivityName.packageName, _secondaryActivityName.className)
     }
 
-    private val primaryActivityInfo: ActivityComponentInfo
-        get() = ActivityComponentInfo(primaryActivityName)
+    val primaryActivityName: ComponentName
+        get() = ComponentName(_primaryActivityName.packageName, _primaryActivityName.className)
 
-    private val secondaryActivityInfo: ActivityComponentInfo
-        get() = ActivityComponentInfo(secondaryActivityName)
+    val secondaryActivityName: ComponentName
+        get() = ComponentName(_secondaryActivityName.packageName, _secondaryActivityName.className)
 
     /**
      * Returns `true` if this [SplitPairFilter] matches [primaryActivity] and [secondaryActivity].
@@ -101,9 +114,9 @@
      */
     fun matchesActivityPair(primaryActivity: Activity, secondaryActivity: Activity): Boolean {
         // Check if the activity component names match
-        val match = if (!isActivityMatching(primaryActivity, primaryActivityInfo)) {
+        val match = if (!isActivityMatching(primaryActivity, _primaryActivityName)) {
             false
-        } else if (!isActivityMatching(secondaryActivity, secondaryActivityInfo)) {
+        } else if (!isActivityMatching(secondaryActivity, _secondaryActivityName)) {
             false
         } else {
             secondaryActivityIntentAction == null ||
@@ -134,9 +147,9 @@
         primaryActivity: Activity,
         secondaryActivityIntent: Intent
     ): Boolean {
-        val match = if (!isActivityMatching(primaryActivity, primaryActivityInfo)) {
+        val match = if (!isActivityMatching(primaryActivity, _primaryActivityName)) {
             false
-        } else if (!isIntentMatching(secondaryActivityIntent, secondaryActivityInfo)) {
+        } else if (!isIntentMatching(secondaryActivityIntent, _secondaryActivityName)) {
             false
         } else {
             secondaryActivityIntentAction == null ||
@@ -153,27 +166,29 @@
         return match
     }
 
+    override fun toString(): String {
+        return "SplitPairFilter{primaryActivityName=$primaryActivityName, " +
+            "secondaryActivityName=$secondaryActivityName, " +
+            "secondaryActivityAction=$secondaryActivityIntentAction}"
+    }
+
     override fun equals(other: Any?): Boolean {
         if (this === other) return true
-        if (other !is SplitPairFilter) return false
+        if (javaClass != other?.javaClass) return false
 
-        if (primaryActivityName != other.primaryActivityName) return false
-        if (secondaryActivityName != other.secondaryActivityName) return false
+        other as SplitPairFilter
+
+        if (_primaryActivityName != other._primaryActivityName) return false
+        if (_secondaryActivityName != other._secondaryActivityName) return false
         if (secondaryActivityIntentAction != other.secondaryActivityIntentAction) return false
 
         return true
     }
 
     override fun hashCode(): Int {
-        var result = primaryActivityName.hashCode()
-        result = 31 * result + secondaryActivityName.hashCode()
+        var result = _primaryActivityName.hashCode()
+        result = 31 * result + _secondaryActivityName.hashCode()
         result = 31 * result + (secondaryActivityIntentAction?.hashCode() ?: 0)
         return result
     }
-
-    override fun toString(): String {
-        return "SplitPairFilter{primaryActivityName=$primaryActivityName, " +
-            "secondaryActivityName=$secondaryActivityName, " +
-            "secondaryActivityAction=$secondaryActivityIntentAction}"
-    }
 }
\ No newline at end of file
diff --git a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
index 67af938..7ea22f8 100644
--- a/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
+++ b/window/window/src/main/java/androidx/window/embedding/SplitPairRule.kt
@@ -31,14 +31,14 @@
  * applied only to activities that will be started from the activity fills the whole parent task
  * container or activity in the primary split after the rules were set.
  */
-class SplitPairRule : SplitRule {
-
+class SplitPairRule internal constructor(
     /**
      * Filters used to choose when to apply this rule. The rule may be used if any one of the
      * provided filters matches.
      */
-    val filters: Set<SplitPairFilter>
-
+    val filters: Set<SplitPairFilter>,
+    defaultSplitAttributes: SplitAttributes,
+    tag: String? = null,
     /**
      * Determines what happens with the primary container when all activities are finished in the
      * associated secondary container.
@@ -47,8 +47,7 @@
      * @see SplitRule.FinishBehavior.ALWAYS
      * @see SplitRule.FinishBehavior.ADJACENT
      */
-    val finishPrimaryWithSecondary: FinishBehavior
-
+    val finishPrimaryWithSecondary: FinishBehavior = NEVER,
     /**
      * Determines what happens with the secondary container when all activities are finished in the
      * associated primary container.
@@ -57,34 +56,22 @@
      * @see SplitRule.FinishBehavior.ALWAYS
      * @see SplitRule.FinishBehavior.ADJACENT
      */
-    val finishSecondaryWithPrimary: FinishBehavior
-
+    val finishSecondaryWithPrimary: FinishBehavior = ALWAYS,
     /**
      * If there is an existing split with the same primary container, indicates whether the
      * existing secondary container on top and all activities in it should be destroyed when a new
      * split is created using this rule. Otherwise the new secondary will appear on top by default.
      */
-    val clearTop: Boolean
-
-    internal constructor(
-        tag: String? = null,
-        filters: Set<SplitPairFilter>,
-        finishPrimaryWithSecondary: FinishBehavior = NEVER,
-        finishSecondaryWithPrimary: FinishBehavior = ALWAYS,
-        clearTop: Boolean = false,
-        @IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
-        @IntRange(from = 0) minHeightDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
-        @IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
-        maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
-        maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT,
-        defaultSplitAttributes: SplitAttributes,
-    ) : super(tag, minWidthDp, minHeightDp, minSmallestWidthDp, maxAspectRatioInPortrait,
-        maxAspectRatioInLandscape, defaultSplitAttributes) {
-        this.filters = filters.toSet()
-        this.clearTop = clearTop
-        this.finishPrimaryWithSecondary = finishPrimaryWithSecondary
-        this.finishSecondaryWithPrimary = finishSecondaryWithPrimary
-    }
+    val clearTop: Boolean = false,
+    @IntRange(from = 0) minWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+    @IntRange(from = 0) minHeightDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+    @IntRange(from = 0) minSmallestWidthDp: Int = SPLIT_MIN_DIMENSION_DP_DEFAULT,
+    maxAspectRatioInPortrait: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_PORTRAIT_DEFAULT,
+    maxAspectRatioInLandscape: EmbeddingAspectRatio = SPLIT_MAX_ASPECT_RATIO_LANDSCAPE_DEFAULT
+) : SplitRule(
+    tag, minWidthDp, minHeightDp, minSmallestWidthDp, maxAspectRatioInPortrait,
+    maxAspectRatioInLandscape, defaultSplitAttributes
+) {
 
     /**
      * Builder for [SplitPairRule].
@@ -275,8 +262,9 @@
          * @return The new `SplitPairRule` instance.
          */
         fun build() = SplitPairRule(
-            tag,
             filters,
+            defaultSplitAttributes,
+            tag,
             finishPrimaryWithSecondary,
             finishSecondaryWithPrimary,
             clearTop,
@@ -285,7 +273,6 @@
             minSmallestWidthDp,
             maxAspectRatioInPortrait,
             maxAspectRatioInLandscape,
-            defaultSplitAttributes,
         )
     }
 
diff --git a/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt b/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt
new file mode 100644
index 0000000..e8b48a2
--- /dev/null
+++ b/window/window/src/test/java/androidx/window/embedding/SplitPairRuleTest.kt
@@ -0,0 +1,46 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.window.embedding
+
+import androidx.window.core.ActivityComponentInfo
+import org.junit.Assert.assertEquals
+import org.junit.Test
+
+internal class SplitPairRuleTest {
+
+    @Test
+    fun test_builderMatchesConstruction() {
+        val splitAttributes = SplitAttributes()
+        val filterSet = setOf(createSplitPairFilter())
+        val expected = SplitPairRule(
+            filterSet,
+            splitAttributes
+        )
+
+        val actual = SplitPairRule.Builder(filterSet).build()
+
+        assertEquals(expected, actual)
+    }
+
+    private fun createSplitPairFilter(): SplitPairFilter {
+        return SplitPairFilter(
+            ActivityComponentInfo("package", "class"),
+            ActivityComponentInfo("otherPackage", "otherClass"),
+            null
+        )
+    }
+}
\ No newline at end of file
diff --git a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
index 3dd4813..7e14f39 100644
--- a/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
+++ b/work/work-gcm/src/androidTest/java/androidx/work/impl/background/gcm/WorkManagerGcmDispatcherTest.kt
@@ -25,6 +25,7 @@
 import androidx.test.filters.MediumTest
 import androidx.work.Configuration
 import androidx.work.OneTimeWorkRequest
+import androidx.work.impl.TestWorkManagerImpl
 import androidx.work.impl.model.WorkGenerationalId
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.utils.SerialExecutorImpl
@@ -92,7 +93,7 @@
             .setExecutor(mExecutor)
             .build()
 
-        mWorkManager = WorkManagerImpl(mContext, configuration, workTaskExecutor, true)
+        mWorkManager = TestWorkManagerImpl(mContext, configuration, workTaskExecutor)
         WorkManagerImpl.setDelegate(mWorkManager)
         mWorkTimer = spy(WorkTimer(configuration.runnableScheduler))
         mDispatcher = WorkManagerGcmDispatcher(mWorkManager, mWorkTimer)
diff --git a/work/work-runtime/api/current.ignore b/work/work-runtime/api/current.ignore
index ecd7f87..7b196e5 100644
--- a/work/work-runtime/api/current.ignore
+++ b/work/work-runtime/api/current.ignore
@@ -1,4 +1,14 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfoByIdFlow(java.util.UUID):
+    Added method androidx.work.WorkManager.getWorkInfoByIdFlow(java.util.UUID)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosByTagFlow(String):
+    Added method androidx.work.WorkManager.getWorkInfosByTagFlow(String)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosFlow(androidx.work.WorkQuery):
+    Added method androidx.work.WorkManager.getWorkInfosFlow(androidx.work.WorkQuery)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosForUniqueWorkFlow(String):
+    Added method androidx.work.WorkManager.getWorkInfosForUniqueWorkFlow(String)
+
+
 ChangedType: androidx.work.Configuration#getInitializationExceptionHandler():
     Method androidx.work.Configuration.getInitializationExceptionHandler has changed return type from androidx.core.util.Consumer<java.lang.Throwable!> to androidx.core.util.Consumer<java.lang.Throwable>
 ChangedType: androidx.work.Configuration#getSchedulingExceptionHandler():
diff --git a/work/work-runtime/api/current.txt b/work/work-runtime/api/current.txt
index ef7ce80..3e5a864 100644
--- a/work/work-runtime/api/current.txt
+++ b/work/work-runtime/api/current.txt
@@ -394,11 +394,15 @@
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long!> getLastCancelAllTimeMillis();
     method public abstract androidx.lifecycle.LiveData<java.lang.Long!> getLastCancelAllTimeMillisLiveData();
     method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo!> getWorkInfoById(java.util.UUID);
+    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo!> getWorkInfoByIdFlow(java.util.UUID);
     method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo!> getWorkInfoByIdLiveData(java.util.UUID);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfos(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTag(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagLiveData(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosFlow(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWork(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkLiveData(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosLiveData(androidx.work.WorkQuery);
     method public static void initialize(android.content.Context, androidx.work.Configuration);
diff --git a/work/work-runtime/api/public_plus_experimental_current.txt b/work/work-runtime/api/public_plus_experimental_current.txt
index ef7ce80..3e5a864 100644
--- a/work/work-runtime/api/public_plus_experimental_current.txt
+++ b/work/work-runtime/api/public_plus_experimental_current.txt
@@ -394,11 +394,15 @@
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long!> getLastCancelAllTimeMillis();
     method public abstract androidx.lifecycle.LiveData<java.lang.Long!> getLastCancelAllTimeMillisLiveData();
     method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo!> getWorkInfoById(java.util.UUID);
+    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo!> getWorkInfoByIdFlow(java.util.UUID);
     method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo!> getWorkInfoByIdLiveData(java.util.UUID);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfos(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTag(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagLiveData(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosFlow(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWork(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkLiveData(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosLiveData(androidx.work.WorkQuery);
     method public static void initialize(android.content.Context, androidx.work.Configuration);
diff --git a/work/work-runtime/api/restricted_current.ignore b/work/work-runtime/api/restricted_current.ignore
index ecd7f87..7b196e5 100644
--- a/work/work-runtime/api/restricted_current.ignore
+++ b/work/work-runtime/api/restricted_current.ignore
@@ -1,4 +1,14 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfoByIdFlow(java.util.UUID):
+    Added method androidx.work.WorkManager.getWorkInfoByIdFlow(java.util.UUID)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosByTagFlow(String):
+    Added method androidx.work.WorkManager.getWorkInfosByTagFlow(String)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosFlow(androidx.work.WorkQuery):
+    Added method androidx.work.WorkManager.getWorkInfosFlow(androidx.work.WorkQuery)
+AddedAbstractMethod: androidx.work.WorkManager#getWorkInfosForUniqueWorkFlow(String):
+    Added method androidx.work.WorkManager.getWorkInfosForUniqueWorkFlow(String)
+
+
 ChangedType: androidx.work.Configuration#getInitializationExceptionHandler():
     Method androidx.work.Configuration.getInitializationExceptionHandler has changed return type from androidx.core.util.Consumer<java.lang.Throwable!> to androidx.core.util.Consumer<java.lang.Throwable>
 ChangedType: androidx.work.Configuration#getSchedulingExceptionHandler():
diff --git a/work/work-runtime/api/restricted_current.txt b/work/work-runtime/api/restricted_current.txt
index ef7ce80..3e5a864 100644
--- a/work/work-runtime/api/restricted_current.txt
+++ b/work/work-runtime/api/restricted_current.txt
@@ -394,11 +394,15 @@
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.lang.Long!> getLastCancelAllTimeMillis();
     method public abstract androidx.lifecycle.LiveData<java.lang.Long!> getLastCancelAllTimeMillisLiveData();
     method public abstract com.google.common.util.concurrent.ListenableFuture<androidx.work.WorkInfo!> getWorkInfoById(java.util.UUID);
+    method public abstract kotlinx.coroutines.flow.Flow<androidx.work.WorkInfo!> getWorkInfoByIdFlow(java.util.UUID);
     method public abstract androidx.lifecycle.LiveData<androidx.work.WorkInfo!> getWorkInfoByIdLiveData(java.util.UUID);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfos(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTag(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosByTagLiveData(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosFlow(androidx.work.WorkQuery);
     method public abstract com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWork(String);
+    method public abstract kotlinx.coroutines.flow.Flow<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkFlow(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosForUniqueWorkLiveData(String);
     method public abstract androidx.lifecycle.LiveData<java.util.List<androidx.work.WorkInfo!>!> getWorkInfosLiveData(androidx.work.WorkQuery);
     method public static void initialize(android.content.Context, androidx.work.Configuration);
diff --git a/work/work-runtime/build.gradle b/work/work-runtime/build.gradle
index 4912a2d..ac57f69 100644
--- a/work/work-runtime/build.gradle
+++ b/work/work-runtime/build.gradle
@@ -58,7 +58,7 @@
 dependencies {
     implementation("androidx.core:core:1.9.0")
     ksp("androidx.room:room-compiler:2.5.0")
-    implementation("androidx.room:room-runtime:2.5.0")
+    implementation("androidx.room:room-ktx:2.5.0")
     androidTestImplementation("androidx.room:room-testing:2.5.0")
     implementation("androidx.sqlite:sqlite-framework:2.3.0")
     api("androidx.annotation:annotation-experimental:1.0.0")
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/ContentUriTriggerWorkersTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/ContentUriTriggerWorkersTest.kt
index 3b3aeeb..c9bf3cb 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/ContentUriTriggerWorkersTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/ContentUriTriggerWorkersTest.kt
@@ -24,11 +24,11 @@
 import androidx.test.filters.SdkSuppress
 import androidx.work.Configuration.Companion.MIN_SCHEDULER_LIMIT
 import androidx.work.Constraints.ContentUriTrigger
-import androidx.work.impl.Processor
 import androidx.work.impl.Scheduler
 import androidx.work.impl.WorkDatabase
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.model.WorkSpec
+import androidx.work.impl.schedulers
 import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
 import androidx.work.worker.TestWorker
 import com.google.common.truth.Truth.assertThat
@@ -49,11 +49,14 @@
         .build()
     val executor = Executors.newSingleThreadExecutor()
     val taskExecutor = WorkManagerTaskExecutor(executor)
-    val db = WorkDatabase.create(context, executor, true)
     internal val testScheduler = TestScheduler()
-    val processor = Processor(context, configuration, taskExecutor, db)
-    val workManager = WorkManagerImpl(context,
-        configuration, taskExecutor, db, listOf<Scheduler>(testScheduler), processor)
+    val workManager = WorkManagerImpl(
+        context = context,
+        configuration = configuration,
+        workTaskExecutor = taskExecutor,
+        workDatabase = WorkDatabase.create(context, executor, true),
+        schedulersCreator = schedulers(testScheduler)
+    )
 
     @Test
     fun maxSchedulerLimitNotApplicable() {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
index 72fc7af..21a8de2 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/SchedulersTest.kt
@@ -18,6 +18,8 @@
 
 import android.content.Context
 import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
 import androidx.work.impl.Processor
 import androidx.work.impl.Scheduler
 import androidx.work.impl.StartStopTokens
@@ -28,6 +30,7 @@
 import androidx.work.impl.constraints.trackers.Trackers
 import androidx.work.impl.model.WorkSpec
 import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
 import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
 import androidx.work.worker.FailureWorker
 import androidx.work.worker.LatchWorker
@@ -37,7 +40,10 @@
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
 import org.junit.Test
+import org.junit.runner.RunWith
 
+@MediumTest
+@RunWith(AndroidJUnit4::class)
 class SchedulersTest {
     val context = ApplicationProvider.getApplicationContext<Context>().applicationContext
     val factory = TrackingWorkerFactory()
@@ -60,8 +66,20 @@
 
             override fun hasLimitedSchedulingSlots() = false
         }
-        val wm = WorkManagerImpl(context, configuration, taskExecutor, db,
-            listOf(trackingScheduler, greedyScheduler), processor, trackers)
+        val wm = WorkManagerImpl(
+            context, configuration, taskExecutor, db,
+        ) { context: Context,
+            configuration: Configuration,
+            taskExecutor: TaskExecutor,
+            _: WorkDatabase,
+            trackers: Trackers,
+            processor: Processor ->
+            listOf(
+                GreedyScheduler(context, configuration, trackers, processor,
+                    WorkLauncherImpl(processor, taskExecutor)),
+                trackingScheduler
+            )
+        }
 
         val workRequest = OneTimeWorkRequest.from(TestWorker::class.java)
         val dependency = OneTimeWorkRequest.from(TestWorker::class.java)
@@ -87,8 +105,10 @@
 
             override fun hasLimitedSchedulingSlots() = false
         }
-        val wm = WorkManagerImpl(context, configuration, taskExecutor, db,
-            listOf(trackingScheduler, greedyScheduler), processor, trackers)
+        val wm = WorkManagerImpl(
+            context, configuration, taskExecutor, db,
+            listOf(trackingScheduler, greedyScheduler), processor, trackers
+        )
 
         val workRequest = OneTimeWorkRequest.from(FailureWorker::class.java)
         wm.enqueue(workRequest)
@@ -105,7 +125,9 @@
     @Test
     fun interruptionReschedules() {
         val schedulers = mutableListOf<Scheduler>()
-        val wm = WorkManagerImpl(context, configuration, taskExecutor, db, schedulers, processor)
+        val wm = WorkManagerImpl(
+            context, configuration, taskExecutor, db, schedulers, processor, trackers
+        )
         val scheduledSpecs = mutableListOf<WorkSpec>()
         val cancelledIds = mutableListOf<String>()
         val scheduler = object : Scheduler {
@@ -154,7 +176,10 @@
     @Test
     fun periodicReschedules() {
         val schedulers = mutableListOf<Scheduler>()
-        val wm = WorkManagerImpl(context, configuration, taskExecutor, db, schedulers, processor)
+        val wm = WorkManagerImpl(
+            context, configuration, taskExecutor, db,
+            schedulers, processor, trackers
+        )
         val scheduledSpecs = mutableListOf<WorkSpec>()
         val cancelledIds = mutableListOf<String>()
         val scheduler = object : Scheduler {
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
new file mode 100644
index 0000000..10b44d3
--- /dev/null
+++ b/work/work-runtime/src/androidTest/java/androidx/work/WorkInfoFlowsTest.kt
@@ -0,0 +1,203 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT 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.work
+
+import android.content.Context
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.SmallTest
+import androidx.work.ExistingWorkPolicy.APPEND
+import androidx.work.ExistingWorkPolicy.KEEP
+import androidx.work.impl.Processor
+import androidx.work.impl.Scheduler
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkLauncherImpl
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.background.greedy.GreedyScheduler
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.testutils.TestConstraintTracker
+import androidx.work.impl.testutils.TrackingWorkerFactory
+import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+import androidx.work.worker.LatchWorker
+import androidx.work.worker.TestWorker
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.TimeoutCancellationException
+import kotlinx.coroutines.channels.Channel
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.launchIn
+import kotlinx.coroutines.flow.onEach
+import kotlinx.coroutines.job
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.Test
+
+@SmallTest
+class WorkInfoFlowsTest {
+    val context = ApplicationProvider.getApplicationContext<Context>()
+    val workerFactory = TrackingWorkerFactory()
+    val configuration = Configuration.Builder().setWorkerFactory(workerFactory).build()
+    val executor = Executors.newSingleThreadExecutor()
+    val taskExecutor = WorkManagerTaskExecutor(executor)
+    val fakeChargingTracker = TestConstraintTracker(false, context, taskExecutor)
+    val trackers = Trackers(
+        context = context,
+        taskExecutor = taskExecutor,
+        batteryChargingTracker = fakeChargingTracker
+    )
+    val db = WorkDatabase.create(context, executor, true)
+
+    // ugly, ugly hack because of circular dependency:
+    // Schedulers need WorkManager, WorkManager needs schedulers
+    val schedulers = mutableListOf<Scheduler>()
+    val processor = Processor(context, configuration, taskExecutor, db)
+    val workManager = WorkManagerImpl(
+        context, configuration, taskExecutor, db, schedulers, processor, trackers
+    )
+    val greedyScheduler = GreedyScheduler(context, configuration, trackers,
+        processor, WorkLauncherImpl(processor, taskExecutor))
+
+    init {
+        schedulers.add(greedyScheduler)
+        WorkManagerImpl.setDelegate(workManager)
+    }
+
+    val unrelatedRequest = OneTimeWorkRequest.Builder(TestWorker::class.java)
+        .setInitialDelay(1, TimeUnit.DAYS)
+        .build()
+
+    @Test
+    fun flowById() = runBlocking {
+        val request = OneTimeWorkRequest.Builder(LatchWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .build()
+        val tester = launchTester(workManager.getWorkInfoByIdFlow(request.id))
+        assertThat(tester.awaitNext()).isNull()
+        workManager.enqueue(unrelatedRequest)
+        workManager.enqueue(request)
+        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.ENQUEUED)
+        fakeChargingTracker.state = true
+
+        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.RUNNING)
+        val worker = workerFactory.awaitWorker(request.id) as LatchWorker
+        worker.mLatch.countDown()
+        assertThat(tester.awaitNext().state).isEqualTo(WorkInfo.State.SUCCEEDED)
+    }
+
+    @Test
+    fun flowByName() = runBlocking<Unit> {
+        val request1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .build()
+        val request2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .build()
+        val tester = launchTester(workManager.getWorkInfosForUniqueWorkFlow("name"))
+        assertThat(tester.awaitNext()).isEmpty()
+        workManager.enqueue(unrelatedRequest)
+        workManager.enqueueUniqueWork("name", KEEP, request1)
+        val firstList = tester.awaitNext()
+        assertThat(firstList.size).isEqualTo(1)
+        assertThat(firstList.first().id).isEqualTo(request1.id)
+        workManager.enqueueUniqueWork("name", APPEND, request2)
+        val secondList = tester.awaitNext()
+        assertThat(secondList.size).isEqualTo(2)
+        assertThat(secondList.map { it.id }).containsExactly(request1.id, request2.id)
+    }
+
+    @Test
+    fun flowByQuery() = runBlocking<Unit> {
+        val request1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .build()
+        val request2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .build()
+        val query = WorkQuery.fromIds(request1.id, request2.id)
+        val tester = launchTester(workManager.getWorkInfosFlow(query))
+        assertThat(tester.awaitNext()).isEmpty()
+        workManager.enqueue(unrelatedRequest)
+        workManager.enqueue(request1)
+        val firstList = tester.awaitNext()
+        assertThat(firstList.size).isEqualTo(1)
+        assertThat(firstList.first().id).isEqualTo(request1.id)
+        workManager.enqueue(request2)
+        val secondList = tester.awaitNext()
+        assertThat(secondList.size).isEqualTo(2)
+        assertThat(secondList.map { it.id }).containsExactly(request1.id, request2.id)
+    }
+
+    @Test
+    fun flowByTag() = runBlocking<Unit> {
+        val request1 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .addTag("tag")
+            .build()
+        val request2 = OneTimeWorkRequest.Builder(TestWorker::class.java)
+            .setConstraints(Constraints(requiresCharging = true))
+            .addTag("tag")
+            .build()
+        val tester = launchTester(workManager.getWorkInfosByTagFlow("tag"))
+
+        assertThat(tester.awaitNext()).isEmpty()
+        workManager.enqueue(unrelatedRequest)
+        workManager.enqueue(request1)
+        val firstList = tester.awaitNext()
+        assertThat(firstList.size).isEqualTo(1)
+        assertThat(firstList.first().id).isEqualTo(request1.id)
+        workManager.enqueue(unrelatedRequest)
+        workManager.enqueue(request2)
+        val secondList = tester.awaitNext()
+        assertThat(secondList.size).isEqualTo(2)
+        assertThat(secondList.map { it.id }).containsExactly(request1.id, request2.id)
+    }
+}
+
+private fun <T> CoroutineScope.launchTester(flow: Flow<T>): FlowTester<T> {
+    val tester = FlowTester(flow)
+    // we don't block parent from completing and simply stop collecting once parent is done
+    val forked = Job()
+    coroutineContext.job.invokeOnCompletion { forked.cancel() }
+    launch(Job()) { tester.launch(this) }
+    return tester
+}
+
+private class FlowTester<T>(private val flow: Flow<T>) {
+    private val channel = Channel<T>(10)
+
+    suspend fun awaitNext(): T {
+        val result = try {
+            withTimeout(3000L) { channel.receive() }
+        } catch (e: TimeoutCancellationException) {
+            throw AssertionError("Didn't receive event")
+        }
+        val next = channel.tryReceive()
+        if (next.isSuccess || next.isClosed)
+            throw AssertionError(
+                "Two events received instead of one;\n" +
+                    "first: $result;\nsecond: ${next.getOrNull()}"
+            )
+        return result
+    }
+
+    fun launch(scope: CoroutineScope) {
+        flow.onEach { channel.send(it) }.launchIn(scope)
+    }
+}
\ No newline at end of file
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
index aef0621..1819c89 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkContinuationImplTest.java
@@ -16,6 +16,8 @@
 
 package androidx.work.impl;
 
+import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
+
 import static org.hamcrest.CoreMatchers.hasItems;
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.CoreMatchers.not;
@@ -104,7 +106,7 @@
                 .build();
 
         mWorkManagerImpl =
-                spy(new WorkManagerImpl(context, mConfiguration, new InstantWorkTaskExecutor()));
+                spy(createWorkManager(context, mConfiguration, new InstantWorkTaskExecutor()));
         when(mWorkManagerImpl.getSchedulers()).thenReturn(Collections.singletonList(mScheduler));
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
         mDatabase = mWorkManagerImpl.getWorkDatabase();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
index da88dcf..60777ec 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplLargeExecutorTest.java
@@ -16,6 +16,7 @@
 
 package androidx.work.impl;
 
+import static androidx.work.impl.WorkManagerImplExtKt.createTestWorkManager;
 import static androidx.work.worker.RandomSleepTestWorker.MAX_SLEEP_DURATION_MS;
 
 import static org.hamcrest.CoreMatchers.is;
@@ -111,8 +112,7 @@
                 .setMaxSchedulerLimit(TEST_SCHEDULER_LIMIT)
                 .build();
         TaskExecutor taskExecutor = new InstantWorkTaskExecutor();
-        mWorkManagerImplSpy = spy(
-                new WorkManagerImpl(context, configuration, taskExecutor, true));
+        mWorkManagerImplSpy = spy(createTestWorkManager(context, configuration, taskExecutor));
 
         TrackingScheduler trackingScheduler =
                 new TrackingScheduler(context, configuration, mWorkManagerImplSpy);
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
index 700390a..d64b7d8 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerImplTest.java
@@ -29,6 +29,8 @@
 import static androidx.work.WorkInfo.State.FAILED;
 import static androidx.work.WorkInfo.State.RUNNING;
 import static androidx.work.WorkInfo.State.SUCCEEDED;
+import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
+import static androidx.work.impl.WorkManagerImplExtKt.schedulers;
 import static androidx.work.impl.model.WorkSpec.SCHEDULE_NOT_REQUESTED_YET;
 import static androidx.work.impl.workers.ConstraintTrackingWorkerKt.ARGUMENT_CLASS_NAME;
 
@@ -96,6 +98,7 @@
 import androidx.work.WorkRequest;
 import androidx.work.impl.background.greedy.GreedyScheduler;
 import androidx.work.impl.background.systemalarm.RescheduleReceiver;
+import androidx.work.impl.constraints.trackers.Trackers;
 import androidx.work.impl.model.Dependency;
 import androidx.work.impl.model.DependencyDao;
 import androidx.work.impl.model.WorkName;
@@ -168,8 +171,7 @@
                 .setMinimumLoggingLevel(Log.DEBUG)
                 .build();
         InstantWorkTaskExecutor workTaskExecutor = new InstantWorkTaskExecutor();
-        mWorkManagerImpl =
-                spy(new WorkManagerImpl(mContext, mConfiguration, workTaskExecutor));
+        mWorkManagerImpl = spy(createWorkManager(mContext, mConfiguration, workTaskExecutor));
         WorkLauncher workLauncher = new WorkLauncherImpl(mWorkManagerImpl.getProcessor(),
                 workTaskExecutor);
         mScheduler =
@@ -1802,14 +1804,15 @@
         Processor processor = new Processor(mContext,  mConfiguration, workTaskExecutor, mDatabase);
         WorkLauncherImpl launcher = new WorkLauncherImpl(processor, workTaskExecutor);
 
+        Trackers trackers = mWorkManagerImpl.getTrackers();
         Scheduler scheduler =
                 new GreedyScheduler(
                         mContext,
                         mWorkManagerImpl.getConfiguration(),
-                        mWorkManagerImpl.getTrackers(),
+                        trackers,
                         processor, launcher);
-        mWorkManagerImpl = new WorkManagerImpl(mContext, mConfiguration, workTaskExecutor,
-                mDatabase, Collections.singletonList(scheduler), processor);
+        mWorkManagerImpl =  createWorkManager(mContext, mConfiguration, workTaskExecutor,
+                mDatabase, trackers, processor, schedulers(scheduler));
 
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
         mDatabase = mWorkManagerImpl.getWorkDatabase();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerInitializationTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerInitializationTest.kt
index fc84c9b..ee46004 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerInitializationTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/WorkManagerInitializationTest.kt
@@ -42,7 +42,7 @@
     @SdkSuppress(minSdkVersion = 24)
     fun directBootTest() {
         val context = DeviceProtectedStoreContext(true)
-        WorkManagerImpl(context, configuration, taskExecutor, true)
+        TestWorkManagerImpl(context, configuration, taskExecutor)
     }
 
     @Test
@@ -50,7 +50,7 @@
     @SdkSuppress(minSdkVersion = 24)
     fun credentialBackedStorageTest() {
         val context = DeviceProtectedStoreContext(false)
-        val workManager = WorkManagerImpl(context, configuration, taskExecutor, true)
+        val workManager = TestWorkManagerImpl(context, configuration, taskExecutor)
         assertNotNull(workManager)
     }
 }
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
index 7594a1e..8356427 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/background/systemjob/SystemJobServiceTest.java
@@ -17,6 +17,8 @@
 package androidx.work.impl.background.systemjob;
 
 import static androidx.test.espresso.matcher.ViewMatchers.assertThat;
+import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
+import static androidx.work.impl.WorkManagerImplExtKt.schedulers;
 
 import static org.hamcrest.CoreMatchers.is;
 import static org.hamcrest.Matchers.containsInAnyOrder;
@@ -54,6 +56,7 @@
 import androidx.work.impl.Scheduler;
 import androidx.work.impl.WorkDatabase;
 import androidx.work.impl.WorkManagerImpl;
+import androidx.work.impl.constraints.trackers.Trackers;
 import androidx.work.impl.model.WorkSpecDao;
 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor;
 import androidx.work.worker.InfiniteTestWorker;
@@ -63,7 +66,6 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
-import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executors;
 
@@ -108,15 +110,14 @@
                 .setExecutor(Executors.newSingleThreadExecutor())
                 .build();
         mScheduler = mock(Scheduler.class);
-        List<Scheduler> schedulers = Collections.singletonList(mScheduler);
         mProcessor = new Processor(
                 context,
                 configuration,
                 taskExecutor,
                 mDatabase);
 
-        mWorkManagerImpl = new WorkManagerImpl(
-                context, configuration, taskExecutor, mDatabase, schedulers, mProcessor);
+        mWorkManagerImpl = createWorkManager(context, configuration, taskExecutor,
+                mDatabase, new Trackers(context, taskExecutor), mProcessor, schedulers(mScheduler));
         WorkManagerImpl.setDelegate(mWorkManagerImpl);
         mSystemJobServiceSpy = spy(new SystemJobService());
         doReturn(context).when(mSystemJobServiceSpy).getApplicationContext();
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
index 4ff0283..e13f7d1 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/SystemForegroundDispatcherTest.kt
@@ -45,6 +45,7 @@
 import androidx.work.impl.foreground.SystemForegroundDispatcher.createStartForegroundIntent
 import androidx.work.impl.foreground.SystemForegroundDispatcher.createStopForegroundIntent
 import androidx.work.impl.model.WorkGenerationalId
+import androidx.work.impl.schedulers
 import androidx.work.impl.utils.SynchronousExecutor
 import androidx.work.impl.utils.futures.SettableFuture
 import androidx.work.impl.utils.taskexecutor.InstantWorkTaskExecutor
@@ -95,12 +96,12 @@
         processor = spy(Processor(context, config, taskExecutor, workDatabase))
         workManager = spy(
             WorkManagerImpl(
-                context,
-                config,
-                taskExecutor,
-                workDatabase,
-                listOf(scheduler),
-                processor
+                context = context,
+                configuration = config,
+                workTaskExecutor = taskExecutor,
+                workDatabase = workDatabase,
+                processor = processor,
+                schedulersCreator = schedulers(scheduler),
             )
         )
         workDatabase = workManager.workDatabase
diff --git a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
index 58b88ae..159a514 100644
--- a/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
+++ b/work/work-runtime/src/androidTest/java/androidx/work/impl/foreground/WorkerWrapperForegroundTest.kt
@@ -27,16 +27,20 @@
 import androidx.test.filters.LargeTest
 import androidx.work.Configuration
 import androidx.work.OneTimeWorkRequest
-import androidx.work.impl.Processor
 import androidx.work.impl.Scheduler
 import androidx.work.impl.WorkDatabase
 import androidx.work.impl.WorkManagerImpl
 import androidx.work.impl.WorkerWrapper
-import androidx.work.impl.utils.SerialExecutorImpl
+import androidx.work.impl.schedulers
 import androidx.work.impl.utils.futures.SettableFuture
 import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
 import androidx.work.worker.StopAwareForegroundWorker
 import androidx.work.worker.TestForegroundWorker
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.ExecutorService
+import java.util.concurrent.Executors
+import java.util.concurrent.TimeUnit
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Before
@@ -46,12 +50,6 @@
 import org.mockito.Mockito.doReturn
 import org.mockito.Mockito.mock
 import org.mockito.Mockito.spy
-import java.util.Collections
-import java.util.concurrent.CountDownLatch
-import java.util.concurrent.Executor
-import java.util.concurrent.ExecutorService
-import java.util.concurrent.Executors
-import java.util.concurrent.TimeUnit
 
 @RunWith(AndroidJUnit4::class)
 @LargeTest
@@ -63,8 +61,6 @@
     private lateinit var internalExecutor: ExecutorService
     private lateinit var taskExecutor: TaskExecutor
     private lateinit var workDatabase: WorkDatabase
-    private lateinit var schedulers: List<Scheduler>
-    private lateinit var processor: Processor
     private lateinit var workManager: WorkManagerImpl
     private lateinit var foregroundProcessor: ForegroundProcessor
 
@@ -84,26 +80,16 @@
             .setMinimumLoggingLevel(Log.DEBUG)
             .build()
 
-        taskExecutor = object : TaskExecutor {
-            val main = Executor { runnable ->
-                handler.post(runnable)
-            }
-            val serialExecutor = SerialExecutorImpl(internalExecutor)
-
-            override fun getMainThreadExecutor(): Executor {
-                return main
-            }
-
-            override fun getSerialTaskExecutor() = serialExecutor
-        }
+        taskExecutor = WorkManagerTaskExecutor(internalExecutor)
 
         workDatabase = WorkDatabase.create(context, taskExecutor.serialTaskExecutor, true)
-        val scheduler = mock(Scheduler::class.java)
-        schedulers = Collections.singletonList(scheduler)
-        processor = Processor(context, config, taskExecutor, workDatabase)
-        workManager =
-            spy(WorkManagerImpl(context, config, taskExecutor, workDatabase, schedulers, processor))
-        workDatabase = workManager.workDatabase
+        workManager = WorkManagerImpl(
+                context = context,
+                configuration = config,
+                workTaskExecutor = taskExecutor,
+                workDatabase = workDatabase,
+                schedulersCreator = schedulers(mock(Scheduler::class.java))
+        )
         WorkManagerImpl.setDelegate(workManager)
         // Foreground processor
         foregroundProcessor = mock(ForegroundProcessor::class.java)
diff --git a/work/work-runtime/src/main/java/androidx/work/WorkManager.java b/work/work-runtime/src/main/java/androidx/work/WorkManager.java
index a01f374..ddfb9ade 100644
--- a/work/work-runtime/src/main/java/androidx/work/WorkManager.java
+++ b/work/work-runtime/src/main/java/androidx/work/WorkManager.java
@@ -32,6 +32,8 @@
 import java.util.UUID;
 import java.util.concurrent.TimeUnit;
 
+import kotlinx.coroutines.flow.Flow;
+
 /**
  * WorkManager is the recommended library for persistent work.
  * Scheduled work is guaranteed to execute sometime after its {@link Constraints} are met.
@@ -523,6 +525,16 @@
     public abstract @NonNull LiveData<WorkInfo> getWorkInfoByIdLiveData(@NonNull UUID id);
 
     /**
+     * Gets a {@link Flow} of the {@link WorkInfo} for a given work id.
+     *
+     * @param id The id of the work
+     * @return A {@link Flow} of the {@link WorkInfo} associated with {@code id}; note that
+     *         this {@link WorkInfo} may be {@code null} if {@code id} is not known to
+     *         WorkManager.
+     */
+    public abstract @NonNull Flow<WorkInfo> getWorkInfoByIdFlow(@NonNull UUID id);
+
+    /**
      * Gets a {@link ListenableFuture} of the {@link WorkInfo} for a given work id.
      *
      * @param id The id of the work
@@ -542,6 +554,14 @@
             @NonNull String tag);
 
     /**
+     * Gets a {@link Flow} of the {@link WorkInfo} for all work for a given tag.
+     *
+     * @param tag The tag of the work
+     * @return A {@link Flow} list of {@link WorkInfo} for work tagged with {@code tag}
+     */
+    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosByTagFlow(@NonNull String tag);
+
+    /**
      * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work for a given tag.
      *
      * @param tag The tag of the work
@@ -563,6 +583,17 @@
             @NonNull String uniqueWorkName);
 
     /**
+     * Gets a {@link Flow} of the {@link WorkInfo} for all work in a work chain with a given
+     * unique name.
+     *
+     * @param uniqueWorkName The unique name used to identify the chain of work
+     * @return A {@link Flow} of the {@link WorkInfo} for work in the chain named
+     *         {@code uniqueWorkName}
+     */
+    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosForUniqueWorkFlow(
+            @NonNull String uniqueWorkName);
+
+    /**
      * Gets a {@link ListenableFuture} of the {@link WorkInfo} for all work in a work chain
      * with a given unique name.
      *
@@ -585,6 +616,17 @@
             @NonNull WorkQuery workQuery);
 
     /**
+     * Gets the {@link Flow} of the {@link List} of {@link WorkInfo} for all work
+     * referenced by the {@link WorkQuery} specification.
+     *
+     * @param workQuery The work query specification
+     * @return A {@link Flow} of the {@link List} of {@link WorkInfo} for work
+     * referenced by this {@link WorkQuery}.
+     */
+    public abstract @NonNull Flow<List<WorkInfo>> getWorkInfosFlow(
+            @NonNull WorkQuery workQuery);
+
+    /**
      * Gets the {@link ListenableFuture} of the {@link List} of {@link WorkInfo} for all work
      * referenced by the {@link WorkQuery} specification.
      *
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
index 7656327..aa02965 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImpl.java
@@ -20,9 +20,13 @@
 import static android.app.PendingIntent.FLAG_UPDATE_CURRENT;
 import static android.text.TextUtils.isEmpty;
 
-import static androidx.work.impl.Schedulers.createBestAvailableBackgroundScheduler;
+import static androidx.work.impl.WorkManagerImplExtKt.createWorkManager;
 import static androidx.work.impl.WorkerUpdater.enqueueUniquelyNamedPeriodic;
 import static androidx.work.impl.foreground.SystemForegroundDispatcher.createCancelWorkIntent;
+import static androidx.work.impl.model.RawWorkInfoDaoKt.getWorkInfoPojosFlow;
+import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowDataForIds;
+import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForName;
+import static androidx.work.impl.model.WorkSpecDaoKt.getWorkStatusPojoFlowForTag;
 
 import android.app.PendingIntent;
 import android.content.BroadcastReceiver;
@@ -44,13 +48,11 @@
 import androidx.work.OneTimeWorkRequest;
 import androidx.work.Operation;
 import androidx.work.PeriodicWorkRequest;
-import androidx.work.R;
 import androidx.work.WorkContinuation;
 import androidx.work.WorkInfo;
 import androidx.work.WorkManager;
 import androidx.work.WorkQuery;
 import androidx.work.WorkRequest;
-import androidx.work.impl.background.greedy.GreedyScheduler;
 import androidx.work.impl.background.systemalarm.RescheduleReceiver;
 import androidx.work.impl.background.systemjob.SystemJobScheduler;
 import androidx.work.impl.constraints.trackers.Trackers;
@@ -68,16 +70,16 @@
 import androidx.work.impl.utils.StopWorkRunnable;
 import androidx.work.impl.utils.futures.SettableFuture;
 import androidx.work.impl.utils.taskexecutor.TaskExecutor;
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor;
 import androidx.work.multiprocess.RemoteWorkManager;
 
 import com.google.common.util.concurrent.ListenableFuture;
 
-import java.util.Arrays;
 import java.util.Collections;
 import java.util.List;
 import java.util.UUID;
 
+import kotlinx.coroutines.flow.Flow;
+
 /**
  * A concrete implementation of {@link WorkManager}.
  *
@@ -99,11 +101,10 @@
     private List<Scheduler> mSchedulers;
     private Processor mProcessor;
     private PreferenceUtils mPreferenceUtils;
-    private boolean mForceStopRunnableCompleted;
+    private boolean mForceStopRunnableCompleted = false;
     private BroadcastReceiver.PendingResult mRescheduleReceiverResult;
     private volatile RemoteWorkManager mRemoteWorkManager;
     private final Trackers mTrackers;
-    private final WorkLauncher mWorkLauncher;
     private static WorkManagerImpl sDelegatedInstance = null;
     private static WorkManagerImpl sDefaultInstance = null;
     private static final Object sLock = new Object();
@@ -202,10 +203,7 @@
             if (sDelegatedInstance == null) {
                 context = context.getApplicationContext();
                 if (sDefaultInstance == null) {
-                    sDefaultInstance = new WorkManagerImpl(
-                            context,
-                            configuration,
-                            new WorkManagerTaskExecutor(configuration.getTaskExecutor()));
+                    sDefaultInstance = createWorkManager(context, configuration);
                 }
                 sDelegatedInstance = sDefaultInstance;
             }
@@ -215,103 +213,6 @@
     /**
      * Create an instance of {@link WorkManagerImpl}.
      *
-     * @param context The application {@link Context}
-     * @param configuration The {@link Configuration} configuration
-     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
-     *                         enqueueing, scheduling, cancellation, etc.
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkManagerImpl(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull TaskExecutor workTaskExecutor) {
-        this(context,
-                configuration,
-                workTaskExecutor,
-                context.getResources().getBoolean(R.bool.workmanager_test_configuration));
-    }
-
-    /**
-     * Create an instance of {@link WorkManagerImpl}.
-     *
-     * @param context The application {@link Context}
-     * @param configuration The {@link Configuration} configuration
-     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
-     *                         enqueueing, scheduling, cancellation, etc.
-     * @param useTestDatabase {@code true} If using an in-memory test database
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkManagerImpl(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull TaskExecutor workTaskExecutor,
-            boolean useTestDatabase) {
-        this(context,
-                configuration,
-                workTaskExecutor,
-                WorkDatabase.create(
-                        context.getApplicationContext(),
-                        workTaskExecutor.getSerialTaskExecutor(),
-                        useTestDatabase)
-        );
-    }
-
-    /**
-     * Create an instance of {@link WorkManagerImpl}.
-     *
-     * @param context          The application {@link Context}
-     * @param configuration    The {@link Configuration} configuration
-     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
-     *                         enqueueing, scheduling, cancellation, etc.
-     * @param database         The {@link WorkDatabase}
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkManagerImpl(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull TaskExecutor workTaskExecutor,
-            @NonNull WorkDatabase database) {
-        Context applicationContext = context.getApplicationContext();
-        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
-        mTrackers = new Trackers(applicationContext, workTaskExecutor);
-        mProcessor = new Processor(
-                context,
-                configuration,
-                workTaskExecutor,
-                database);
-        mWorkLauncher = new WorkLauncherImpl(mProcessor, workTaskExecutor);
-        mWorkTaskExecutor = workTaskExecutor;
-        mWorkDatabase = database;
-        List<Scheduler> schedulers =
-                createSchedulers(applicationContext, configuration, mTrackers);
-        internalInit(context, configuration, workTaskExecutor, schedulers);
-    }
-
-    /**
-     * Create an instance of {@link WorkManagerImpl}.
-     *
-     * @param context The application {@link Context}
-     * @param configuration The {@link Configuration} configuration
-     * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
-     *                         enqueueing, scheduling, cancellation, etc.
-     * @param workDatabase The {@link WorkDatabase} instance
-     * @param processor The {@link Processor} instance
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public WorkManagerImpl(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull TaskExecutor workTaskExecutor,
-            @NonNull WorkDatabase workDatabase,
-            @NonNull List<Scheduler> schedulers,
-            @NonNull Processor processor) {
-        this(context, configuration, workTaskExecutor, workDatabase, schedulers, processor,
-                new Trackers(context.getApplicationContext(), workTaskExecutor));
-    }
-
-    /**
-     * Create an instance of {@link WorkManagerImpl}.
-     *
      * @param context          The application {@link Context}
      * @param configuration    The {@link Configuration} configuration
      * @param workTaskExecutor The {@link TaskExecutor} for running "processing" jobs, such as
@@ -329,12 +230,26 @@
             @NonNull List<Scheduler> schedulers,
             @NonNull Processor processor,
             @NonNull Trackers trackers) {
-        mTrackers = trackers;
-        mWorkLauncher = new WorkLauncherImpl(processor, workTaskExecutor);
-        mProcessor = processor;
+        context = context.getApplicationContext();
+        // Check for direct boot mode
+        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Api24Impl.isDeviceProtectedStorage(
+                context)) {
+            throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode");
+        }
+        Logger.setLogger(new Logger.LogcatLogger(configuration.getMinimumLoggingLevel()));
+        mContext = context;
         mWorkTaskExecutor = workTaskExecutor;
         mWorkDatabase = workDatabase;
-        internalInit(context, configuration, workTaskExecutor, schedulers);
+        mProcessor = processor;
+        mTrackers = trackers;
+        mConfiguration = configuration;
+        mSchedulers = schedulers;
+        mPreferenceUtils = new PreferenceUtils(mWorkDatabase);
+        Schedulers.registerRescheduling(schedulers, mProcessor,
+                workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration);
+
+        // Checks for app force stops.
+        mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
     }
 
     /**
@@ -575,6 +490,12 @@
                 mWorkTaskExecutor);
     }
 
+    @NonNull
+    @Override
+    public Flow<WorkInfo> getWorkInfoByIdFlow(@NonNull UUID id) {
+        return getWorkStatusPojoFlowDataForIds(getWorkDatabase().workSpecDao(), id);
+    }
+
     @Override
     public @NonNull ListenableFuture<WorkInfo> getWorkInfoById(@NonNull UUID id) {
         StatusRunnable<WorkInfo> runnable = StatusRunnable.forUUID(this, id);
@@ -582,6 +503,14 @@
         return runnable.getFuture();
     }
 
+    @NonNull
+    @Override
+    public Flow<List<WorkInfo>> getWorkInfosByTagFlow(@NonNull String tag) {
+        WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
+        return getWorkStatusPojoFlowForTag(workSpecDao,
+                mWorkTaskExecutor.getTaskCoroutineDispatcher(), tag);
+    }
+
     @Override
     public @NonNull LiveData<List<WorkInfo>> getWorkInfosByTagLiveData(@NonNull String tag) {
         WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
@@ -613,6 +542,14 @@
                 mWorkTaskExecutor);
     }
 
+    @NonNull
+    @Override
+    public Flow<List<WorkInfo>> getWorkInfosForUniqueWorkFlow(@NonNull String uniqueWorkName) {
+        WorkSpecDao workSpecDao = mWorkDatabase.workSpecDao();
+        return getWorkStatusPojoFlowForName(workSpecDao,
+                mWorkTaskExecutor.getTaskCoroutineDispatcher(), uniqueWorkName);
+    }
+
     @Override
     @NonNull
     public ListenableFuture<List<WorkInfo>> getWorkInfosForUniqueWork(
@@ -639,6 +576,14 @@
 
     @NonNull
     @Override
+    public Flow<List<WorkInfo>> getWorkInfosFlow(@NonNull WorkQuery workQuery) {
+        RawWorkInfoDao rawWorkInfoDao = mWorkDatabase.rawWorkInfoDao();
+        return getWorkInfoPojosFlow(rawWorkInfoDao, mWorkTaskExecutor.getTaskCoroutineDispatcher(),
+                RawQueries.toRawQuery(workQuery));
+    }
+
+    @NonNull
+    @Override
     public ListenableFuture<List<WorkInfo>> getWorkInfos(
             @NonNull WorkQuery workQuery) {
         StatusRunnable<List<WorkInfo>> runnable =
@@ -741,6 +686,10 @@
     public void setReschedulePendingResult(
             @NonNull BroadcastReceiver.PendingResult rescheduleReceiverResult) {
         synchronized (sLock) {
+            // if we have two broadcast in the row, finish old one and use new one
+            if (mRescheduleReceiverResult != null) {
+                mRescheduleReceiverResult.finish();
+            }
             mRescheduleReceiverResult = rescheduleReceiverResult;
             if (mForceStopRunnableCompleted) {
                 mRescheduleReceiverResult.finish();
@@ -750,53 +699,6 @@
     }
 
     /**
-     * Initializes an instance of {@link WorkManagerImpl}.
-     *
-     * @param context The application {@link Context}
-     * @param configuration The {@link Configuration} configuration
-     * @param schedulers The {@link List} of {@link Scheduler}s to use
-     */
-    private void internalInit(@NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull TaskExecutor workTaskExecutor,
-            @NonNull List<Scheduler> schedulers) {
-
-        context = context.getApplicationContext();
-        mContext = context;
-        mConfiguration = configuration;
-        mSchedulers = schedulers;
-        mPreferenceUtils = new PreferenceUtils(mWorkDatabase);
-        mForceStopRunnableCompleted = false;
-        Schedulers.registerRescheduling(schedulers, mProcessor,
-                workTaskExecutor.getSerialTaskExecutor(), mWorkDatabase, configuration);
-        // Check for direct boot mode
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N && Api24Impl.isDeviceProtectedStorage(
-                context)) {
-            throw new IllegalStateException("Cannot initialize WorkManager in direct boot mode");
-        }
-
-        // Checks for app force stops.
-        mWorkTaskExecutor.executeOnTaskThread(new ForceStopRunnable(context, this));
-    }
-
-    /**
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @NonNull
-    public List<Scheduler> createSchedulers(
-            @NonNull Context context,
-            @NonNull Configuration configuration,
-            @NonNull Trackers trackers
-    ) {
-
-        return Arrays.asList(
-                createBestAvailableBackgroundScheduler(context, mWorkDatabase, configuration),
-                // Specify the task executor directly here as this happens before internalInit.
-                // GreedyScheduler creates ConstraintTrackers and controllers eagerly.
-                new GreedyScheduler(context, configuration, trackers, mProcessor, mWorkLauncher));
-    }
-
-    /**
      * Tries to find a multi-process safe implementation for  {@link WorkManager}.
      */
     private void tryInitializeMultiProcessSupport() {
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt
new file mode 100644
index 0000000..d27ca51
--- /dev/null
+++ b/work/work-runtime/src/main/java/androidx/work/impl/WorkManagerImplExt.kt
@@ -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.work.impl
+
+import android.content.Context
+import androidx.work.Configuration
+import androidx.work.R
+import androidx.work.impl.background.greedy.GreedyScheduler
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+
+@JvmName("createWorkManager")
+@JvmOverloads
+fun WorkManagerImpl(
+    context: Context,
+    configuration: Configuration,
+    workTaskExecutor: TaskExecutor = WorkManagerTaskExecutor(configuration.taskExecutor),
+    workDatabase: WorkDatabase =
+        WorkDatabase.create(
+            context.applicationContext, workTaskExecutor.serialTaskExecutor,
+            context.resources.getBoolean(R.bool.workmanager_test_configuration)
+        ),
+    trackers: Trackers = Trackers(context.applicationContext, workTaskExecutor),
+    processor: Processor = Processor(
+        context.applicationContext, configuration, workTaskExecutor, workDatabase
+    ),
+    schedulersCreator: SchedulersCreator = ::createSchedulers
+): WorkManagerImpl {
+    val schedulers = schedulersCreator(
+        context, configuration,
+        workTaskExecutor, workDatabase, trackers, processor
+    )
+    return WorkManagerImpl(
+        context.applicationContext, configuration, workTaskExecutor, workDatabase,
+        schedulers, processor, trackers
+    )
+}
+
+@JvmName("createTestWorkManager")
+fun TestWorkManagerImpl(
+    context: Context,
+    configuration: Configuration,
+    workTaskExecutor: TaskExecutor,
+) = WorkManagerImpl(
+    context, configuration, workTaskExecutor,
+    WorkDatabase.create(context, workTaskExecutor.serialTaskExecutor, true)
+)
+
+internal typealias SchedulersCreator = (
+    context: Context,
+    configuration: Configuration,
+    workTaskExecutor: TaskExecutor,
+    workDatabase: WorkDatabase,
+    trackers: Trackers,
+    processor: Processor
+) -> List<Scheduler>
+
+fun schedulers(vararg schedulers: Scheduler): SchedulersCreator =
+    { _, _, _, _, _, _ -> schedulers.toList() }
+
+private fun createSchedulers(
+    context: Context,
+    configuration: Configuration,
+    workTaskExecutor: TaskExecutor,
+    workDatabase: WorkDatabase,
+    trackers: Trackers,
+    processor: Processor,
+): List<Scheduler> =
+    listOf(
+        Schedulers.createBestAvailableBackgroundScheduler(context, workDatabase, configuration),
+        GreedyScheduler(
+            context, configuration, trackers, processor,
+            WorkLauncherImpl(processor, workTaskExecutor)
+        ),
+    )
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/RawWorkInfoDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/RawWorkInfoDao.kt
index 80588f6..47d39d9 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/RawWorkInfoDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/RawWorkInfoDao.kt
@@ -19,6 +19,9 @@
 import androidx.room.Dao
 import androidx.room.RawQuery
 import androidx.sqlite.db.SupportSQLiteQuery
+import androidx.work.WorkInfo
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
 
 /**
  * A Data Access Object for accessing [androidx.work.WorkInfo]s that uses raw SQL queries.
@@ -41,4 +44,17 @@
     fun getWorkInfoPojosLiveData(
         query: SupportSQLiteQuery
     ): LiveData<List<WorkSpec.WorkInfoPojo>>
-}
\ No newline at end of file
+
+    /**
+     * @param query The raw query obtained using [androidx.work.WorkQuery]
+     * @return A [Flow] of a [List] of [WorkSpec.WorkInfoPojo]s using the
+     * raw query.
+     */
+    @RawQuery(observedEntities = [WorkSpec::class])
+    fun getWorkInfoPojosFlow(query: SupportSQLiteQuery): Flow<List<WorkSpec.WorkInfoPojo>>
+}
+
+fun RawWorkInfoDao.getWorkInfoPojosFlow(
+    dispatcher: CoroutineDispatcher,
+    query: SupportSQLiteQuery
+): Flow<List<WorkInfo>> = getWorkInfoPojosFlow(query).dedup(dispatcher)
\ No newline at end of file
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
index cfd36f4..ac78f50 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
+++ b/work/work-runtime/src/main/java/androidx/work/impl/model/WorkSpecDao.kt
@@ -28,6 +28,13 @@
 import androidx.work.WorkInfo
 import androidx.work.impl.model.WorkTypeConverters.StateIds.COMPLETED_STATES
 import androidx.work.impl.model.WorkTypeConverters.StateIds.ENQUEUED
+import java.util.UUID
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.distinctUntilChanged
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.flow.map
+import org.intellij.lang.annotations.Language
 
 /**
  * The Data Access Object for [WorkSpec]s.
@@ -171,10 +178,21 @@
      * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
      */
     @Transaction
-    @Query("SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN (:ids)")
+    @Query(WORK_INFO_BY_IDS)
     fun getWorkStatusPojoLiveDataForIds(ids: List<String>): LiveData<List<WorkSpec.WorkInfoPojo>>
 
     /**
+     * For a list of [WorkSpec] identifiers, retrieves a [LiveData] list of their
+     * [WorkSpec.WorkInfoPojo].
+     *
+     * @param ids The identifier of the [WorkSpec]s
+     * @return A [Flow] list of [WorkSpec.WorkInfoPojo]
+     */
+    @Transaction
+    @Query(WORK_INFO_BY_IDS)
+    fun getWorkStatusPojoFlowDataForIds(ids: List<String>): Flow<List<WorkSpec.WorkInfoPojo>>
+
+    /**
      * Retrieves a list of [WorkSpec.WorkInfoPojo] for all work with a given tag.
      *
      * @param tag The tag for the [WorkSpec]s
@@ -195,10 +213,18 @@
      * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
      */
     @Transaction
-    @Query(
-        """SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN
-            (SELECT work_spec_id FROM worktag WHERE tag=:tag)"""
-    )
+    @Query(WORK_INFO_BY_TAG)
+    fun getWorkStatusPojoFlowForTag(tag: String): Flow<List<WorkSpec.WorkInfoPojo>>
+
+    /**
+     * Retrieves a [LiveData] list of [WorkSpec.WorkInfoPojo] for all work with a
+     * given tag.
+     *
+     * @param tag The tag for the [WorkSpec]s
+     * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
+     */
+    @Transaction
+    @Query(WORK_INFO_BY_TAG)
     fun getWorkStatusPojoLiveDataForTag(tag: String): LiveData<List<WorkSpec.WorkInfoPojo>>
 
     /**
@@ -222,13 +248,20 @@
      * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
      */
     @Transaction
-    @Query(
-        "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN " +
-            "(SELECT work_spec_id FROM workname WHERE name=:name)"
-    )
+    @Query(WORK_INFO_BY_NAME)
     fun getWorkStatusPojoLiveDataForName(name: String): LiveData<List<WorkSpec.WorkInfoPojo>>
 
     /**
+     * Retrieves a [Flow] list of [WorkSpec.WorkInfoPojo] for all work with a given name.
+     *
+     * @param name The name for the [WorkSpec]s
+     * @return A [LiveData] list of [WorkSpec.WorkInfoPojo]
+     */
+    @Transaction
+    @Query(WORK_INFO_BY_NAME)
+    fun getWorkStatusPojoFlowForName(name: String): Flow<List<WorkSpec.WorkInfoPojo>>
+
+    /**
      * Gets all inputs coming from prerequisites for a particular [WorkSpec].  These are
      * [Data] set via `Worker#setOutputData()`.
      *
@@ -409,5 +442,36 @@
     fun countNonFinishedContentUriTriggerWorkers(): Int
 }
 
+fun WorkSpecDao.getWorkStatusPojoFlowDataForIds(id: UUID): Flow<WorkInfo?> =
+    getWorkStatusPojoFlowDataForIds(listOf("$id"))
+        .map { it.firstOrNull()?.toWorkInfo() }.distinctUntilChanged()
+
+fun WorkSpecDao.getWorkStatusPojoFlowForName(
+    dispatcher: CoroutineDispatcher,
+    name: String
+): Flow<List<WorkInfo>> = getWorkStatusPojoFlowForName(name).dedup(dispatcher)
+
+fun WorkSpecDao.getWorkStatusPojoFlowForTag(
+    dispatcher: CoroutineDispatcher,
+    tag: String
+): Flow<List<WorkInfo>> = getWorkStatusPojoFlowForTag(tag).dedup(dispatcher)
+
+internal fun Flow<List<WorkSpec.WorkInfoPojo>>.dedup(
+    dispatcher: CoroutineDispatcher
+): Flow<List<WorkInfo>> = map { list -> list.map { pojo -> pojo.toWorkInfo() } }
+    .distinctUntilChanged()
+    .flowOn(dispatcher)
+
 private const val WORK_INFO_COLUMNS = "id, state, output, run_attempt_count, generation" +
-    ", $CONSTRAINTS_COLUMNS"
\ No newline at end of file
+    ", $CONSTRAINTS_COLUMNS"
+
+@Language("sql")
+private const val WORK_INFO_BY_IDS = "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN (:ids)"
+
+@Language("sql")
+private const val WORK_INFO_BY_TAG = """SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN
+            (SELECT work_spec_id FROM worktag WHERE tag=:tag)"""
+
+@Language("sql")
+private const val WORK_INFO_BY_NAME = "SELECT $WORK_INFO_COLUMNS FROM workspec WHERE id IN " +
+    "(SELECT work_spec_id FROM workname WHERE name=:name)"
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
index 3c2e331..e61932a 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/TaskExecutor.java
@@ -22,6 +22,9 @@
 
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExecutorsKt;
+
 /**
  * Interface for executing common tasks in WorkManager.
  *
@@ -52,4 +55,9 @@
      */
     @NonNull
     SerialExecutor getSerialTaskExecutor();
+
+    @NonNull
+    default CoroutineDispatcher getTaskCoroutineDispatcher() {
+        return ExecutorsKt.from(getSerialTaskExecutor());
+    }
 }
diff --git a/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java b/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
index 551cd60..9c4f92c 100644
--- a/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
+++ b/work/work-runtime/src/main/java/androidx/work/impl/utils/taskexecutor/WorkManagerTaskExecutor.java
@@ -25,6 +25,9 @@
 
 import java.util.concurrent.Executor;
 
+import kotlinx.coroutines.CoroutineDispatcher;
+import kotlinx.coroutines.ExecutorsKt;
+
 /**
  * Default Task Executor for executing common tasks in WorkManager
  */
@@ -32,11 +35,13 @@
 public class WorkManagerTaskExecutor implements TaskExecutor {
 
     private final SerialExecutorImpl mBackgroundExecutor;
+    private final CoroutineDispatcher mTaskDispatcher;
 
     public WorkManagerTaskExecutor(@NonNull Executor backgroundExecutor) {
         // Wrap it with a serial executor so we have ordering guarantees on commands
         // being executed.
         mBackgroundExecutor = new SerialExecutorImpl(backgroundExecutor);
+        mTaskDispatcher = ExecutorsKt.from(mBackgroundExecutor);
     }
 
     final Handler mMainThreadHandler = new Handler(Looper.getMainLooper());
@@ -59,4 +64,10 @@
     public SerialExecutorImpl getSerialTaskExecutor() {
         return mBackgroundExecutor;
     }
+
+    @NonNull
+    @Override
+    public CoroutineDispatcher getTaskCoroutineDispatcher() {
+        return mTaskDispatcher;
+    }
 }
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
index 42940b8..fd6d17b 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestScheduler.kt
@@ -38,7 +38,7 @@
 class TestScheduler(
     private val workDatabase: WorkDatabase,
     private val launcher: WorkLauncher
-) : Scheduler {
+) : Scheduler, TestDriver {
     @GuardedBy("lock")
     private val pendingWorkStates = mutableMapOf<String, InternalWorkState>()
     private val lock = Any()
@@ -85,7 +85,7 @@
      * @param workSpecId The [Worker]'s id
      * @throws IllegalArgumentException if `workSpecId` is not enqueued
      */
-    fun setAllConstraintsMet(workSpecId: UUID) {
+    override fun setAllConstraintsMet(workSpecId: UUID) {
         val id = workSpecId.toString()
         val spec = loadSpec(id)
         val state: InternalWorkState
@@ -104,7 +104,7 @@
      * @param workSpecId The [Worker]'s id
      * @throws IllegalArgumentException if `workSpecId` is not enqueued
      */
-    fun setInitialDelayMet(workSpecId: UUID) {
+    override fun setInitialDelayMet(workSpecId: UUID) {
         val id = workSpecId.toString()
         val state: InternalWorkState
         val spec = loadSpec(id)
@@ -123,7 +123,7 @@
      * @param workSpecId The [Worker]'s id
      * @throws IllegalArgumentException if `workSpecId` is not enqueued
      */
-    fun setPeriodDelayMet(workSpecId: UUID) {
+    override fun setPeriodDelayMet(workSpecId: UUID) {
         val id = workSpecId.toString()
         val spec = loadSpec(id)
         if (!spec.isPeriodic) throw IllegalArgumentException("Work with id $id isn't periodic!")
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java b/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
deleted file mode 100644
index 3358f9a..0000000
--- a/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.java
+++ /dev/null
@@ -1,116 +0,0 @@
-/*
- * 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.
- */
-
-package androidx.work.testing;
-
-import android.content.Context;
-
-import androidx.annotation.NonNull;
-import androidx.annotation.RestrictTo;
-import androidx.work.Configuration;
-import androidx.work.WorkManager;
-import androidx.work.impl.Scheduler;
-import androidx.work.impl.WorkLauncherImpl;
-import androidx.work.impl.WorkManagerImpl;
-import androidx.work.impl.constraints.trackers.Trackers;
-import androidx.work.impl.utils.taskexecutor.SerialExecutor;
-import androidx.work.impl.utils.taskexecutor.TaskExecutor;
-import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor;
-
-import java.util.Collections;
-import java.util.List;
-import java.util.UUID;
-import java.util.concurrent.Executor;
-
-/**
- * A concrete implementation of {@link WorkManager} which can be used for testing. This
- * implementation makes it easy to swap Schedulers.
- *
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-class TestWorkManagerImpl extends WorkManagerImpl implements TestDriver {
-
-    private TestScheduler mScheduler;
-
-    TestWorkManagerImpl(
-            @NonNull final Context context,
-            @NonNull final Configuration configuration
-    ) {
-        super(context, configuration,
-                new WorkManagerTaskExecutor(configuration.getTaskExecutor()), true);
-    }
-
-    TestWorkManagerImpl(
-            @NonNull final Context context,
-            @NonNull final Configuration configuration,
-            @NonNull final SerialExecutor serialExecutor
-    ) {
-
-        // Note: This implies that the call to ForceStopRunnable() actually does nothing.
-        // This is okay when testing.
-
-        // IMPORTANT: Leave the main thread executor as a Direct executor. This is very important.
-        // Otherwise we subtly change the order of callbacks. onExecuted() will execute after
-        // a call to StopWorkRunnable(). StopWorkRunnable() removes the pending WorkSpec and
-        // therefore the call to onExecuted() does not add the workSpecId to the list of
-        // terminated WorkSpecs. This is because internalWorkState == null.
-        // Also for PeriodicWorkRequests, Schedulers.schedule() will run before the call to
-        // onExecuted() and therefore PeriodicWorkRequests will always run twice.
-        super(
-                context,
-                configuration,
-                new TaskExecutor() {
-                    Executor mSynchronousExecutor = new SynchronousExecutor();
-
-                    @NonNull
-                    @Override
-                    public Executor getMainThreadExecutor() {
-                        return mSynchronousExecutor;
-                    }
-
-                    @NonNull
-                    @Override
-                    public SerialExecutor getSerialTaskExecutor() {
-                        return serialExecutor;
-                    }
-                },
-                true);
-    }
-
-    @Override
-    @NonNull
-    public List<Scheduler> createSchedulers(@NonNull Context context,
-            @NonNull Configuration configuration, @NonNull Trackers trackers) {
-        WorkLauncherImpl launcher = new WorkLauncherImpl(getProcessor(), getWorkTaskExecutor());
-        mScheduler = new TestScheduler(getWorkDatabase(), launcher);
-        return Collections.singletonList((Scheduler) mScheduler);
-    }
-
-    @Override
-    public void setAllConstraintsMet(@NonNull UUID workSpecId) {
-        mScheduler.setAllConstraintsMet(workSpecId);
-    }
-
-    @Override
-    public void setInitialDelayMet(@NonNull UUID workSpecId) {
-        mScheduler.setInitialDelayMet(workSpecId);
-    }
-
-    @Override
-    public void setPeriodDelayMet(@NonNull UUID workSpecId) {
-        mScheduler.setPeriodDelayMet(workSpecId);
-    }
-}
diff --git a/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.kt b/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.kt
new file mode 100644
index 0000000..a2b86d3
--- /dev/null
+++ b/work/work-testing/src/main/java/androidx/work/testing/TestWorkManagerImpl.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.
+ */
+
+package androidx.work.testing
+
+import android.content.Context
+
+import androidx.work.Configuration
+import androidx.work.impl.Processor
+import androidx.work.impl.Scheduler
+import androidx.work.impl.WorkDatabase
+import androidx.work.impl.WorkLauncherImpl
+import androidx.work.impl.WorkManagerImpl
+import androidx.work.impl.constraints.trackers.Trackers
+import androidx.work.impl.utils.taskexecutor.SerialExecutor
+import androidx.work.impl.utils.taskexecutor.TaskExecutor
+import androidx.work.impl.utils.taskexecutor.WorkManagerTaskExecutor
+
+internal fun createTestWorkManagerImpl(
+    context: Context,
+    configuration: Configuration,
+    serialExecutor: SerialExecutor,
+): WorkManagerImpl {
+    val taskExecutor = object : TaskExecutor {
+        val synchronousExecutor = SynchronousExecutor()
+        override fun getMainThreadExecutor() = synchronousExecutor
+
+        override fun getSerialTaskExecutor() = serialExecutor
+    }
+    return WorkManagerImpl(
+        context = context,
+        configuration = configuration,
+        workTaskExecutor = taskExecutor,
+        workDatabase = WorkDatabase.create(context, taskExecutor.serialTaskExecutor, true),
+        schedulersCreator = ::createTestSchedulers
+    )
+}
+
+internal fun createTestWorkManagerImpl(
+    context: Context,
+    configuration: Configuration,
+): WorkManagerImpl {
+    val taskExecutor = WorkManagerTaskExecutor(configuration.taskExecutor)
+    return WorkManagerImpl(
+        context = context,
+        configuration = configuration,
+        workTaskExecutor = taskExecutor,
+        workDatabase = WorkDatabase.create(context, taskExecutor.serialTaskExecutor, true),
+        schedulersCreator = ::createTestSchedulers
+    )
+}
+
+internal val WorkManagerImpl.testDriver: TestDriver
+    get() {
+        return schedulers.find { it is TestScheduler } as? TestScheduler
+            ?: throw IllegalStateException(
+                "WorkManager is incorrectly initialized. " +
+                    "Was WorkManagerTestInitHelper.initializeTestWorkManager* method called?"
+            )
+    }
+
+@Suppress("UNUSED_PARAMETER")
+private fun createTestSchedulers(
+    context: Context,
+    configuration: Configuration,
+    workTaskExecutor: TaskExecutor,
+    workDatabase: WorkDatabase,
+    trackers: Trackers,
+    processor: Processor,
+): List<Scheduler> {
+    val launcher = WorkLauncherImpl(processor, workTaskExecutor)
+    return listOf<Scheduler>(TestScheduler(workDatabase, launcher))
+}
\ No newline at end of file
diff --git a/work/work-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java b/work/work-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
index cb3f365..22155ed 100644
--- a/work/work-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
+++ b/work/work-testing/src/main/java/androidx/work/testing/WorkManagerTestInitHelper.java
@@ -16,6 +16,8 @@
 
 package androidx.work.testing;
 
+import static androidx.work.testing.TestWorkManagerImplKt.createTestWorkManagerImpl;
+
 import android.content.Context;
 
 import androidx.annotation.NonNull;
@@ -69,7 +71,7 @@
         }
 
         WorkManagerImpl.setDelegate(
-                new TestWorkManagerImpl(context, configuration, serialExecutor)
+                createTestWorkManagerImpl(context, configuration, serialExecutor)
         );
     }
 
@@ -83,7 +85,7 @@
      */
     public static void initializeTestWorkManagerWithRealExecutors(@NonNull Context context) {
         Configuration configuration = new Configuration.Builder().build();
-        WorkManagerImpl.setDelegate(new TestWorkManagerImpl(context, configuration));
+        WorkManagerImpl.setDelegate(createTestWorkManagerImpl(context, configuration));
     }
 
     /**
@@ -96,7 +98,7 @@
      */
     public static void initializeTestWorkManagerWithRealExecutors(
             @NonNull Context context, @NonNull Configuration configuration) {
-        WorkManagerImpl.setDelegate(new TestWorkManagerImpl(context, configuration));
+        WorkManagerImpl.setDelegate(createTestWorkManagerImpl(context, configuration));
     }
 
     /**
@@ -110,7 +112,7 @@
         if (workManager == null) {
             return null;
         } else {
-            return (TestWorkManagerImpl) workManager;
+            return TestWorkManagerImplKt.getTestDriver(workManager);
         }
     }
 
@@ -120,7 +122,7 @@
      */
     public static @Nullable TestDriver getTestDriver(@NonNull Context context) {
         try {
-            return (TestWorkManagerImpl) WorkManagerImpl.getInstance(context);
+            return TestWorkManagerImplKt.getTestDriver(WorkManagerImpl.getInstance(context));
         } catch (IllegalStateException e) {
             return null;
         }