Merge "Update hasCameraTransform bit" into androidx-main
diff --git a/OWNERS b/OWNERS
index 89b9afc..7e8dbef 100644
--- a/OWNERS
+++ b/OWNERS
@@ -17,10 +17,8 @@
 natnaelbelay@google.com
 owengray@google.com
 romainguy@android.com
-saff@google.com
 sergeyv@google.com
 siyamed@google.com
-sjgilbert@google.com
 sumir@google.com
 tiem@google.com
 yboyar@google.com
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
index 68e1f87..cbbe2c8 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
@@ -59,8 +59,8 @@
     val composeTestRule = createComposeRule()
 
     var launchCount = 0
-    val registryOwner = ActivityResultRegistryOwner {
-        object : ActivityResultRegistry() {
+    val registryOwner = object : ActivityResultRegistryOwner {
+        override val activityResultRegistry = object : ActivityResultRegistry() {
             override fun <I : Any?, O : Any?> onLaunch(
                 requestCode: Int,
                 contract: ActivityResultContract<I, O>,
@@ -161,8 +161,8 @@
 
         activityScenario.recreate()
 
-        val restoredOwner = ActivityResultRegistryOwner {
-            object : ActivityResultRegistry() {
+        val restoredOwner = object : ActivityResultRegistryOwner {
+            override val activityResultRegistry = object : ActivityResultRegistry() {
                 override fun <I : Any?, O : Any?> onLaunch(
                     requestCode: Int,
                     contract: ActivityResultContract<I, O>,
@@ -210,7 +210,9 @@
                 code = requestCode
             }
         }
-        val owner = ActivityResultRegistryOwner { registry }
+        val owner = object : ActivityResultRegistryOwner {
+            override val activityResultRegistry = registry
+        }
         var recompose by mutableStateOf(false)
         val launchChannel = Channel<Boolean>()
         val launchFlow = launchChannel.receiveAsFlow()
@@ -261,7 +263,9 @@
                 launchCount++
             }
         }
-        val owner = ActivityResultRegistryOwner { registry }
+        val owner = object : ActivityResultRegistryOwner {
+            override val activityResultRegistry = registry
+        }
         composeTestRule.setContent {
             var recompose by remember { mutableStateOf(false) }
             CompositionLocalProvider(
@@ -307,7 +311,9 @@
                 launchCount++
             }
         }
-        val owner = ActivityResultRegistryOwner { registry }
+        val owner = object : ActivityResultRegistryOwner {
+            override val activityResultRegistry = registry
+        }
         val contract = ActivityResultContracts.StartActivityForResult()
         composeTestRule.setContent {
             var recompose by remember { mutableStateOf(false) }
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
index a17c127..def0983 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackHandlerTest.kt
@@ -29,6 +29,7 @@
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -113,22 +114,21 @@
      */
     @Test
     fun testBackHandlerLifecycle() {
-        var inteceptedBack = false
+        var interceptedBack = false
         val lifecycleOwner = TestLifecycleOwner()
 
         composeTestRule.setContent {
             val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
-            val dispatcherOwner = object : OnBackPressedDispatcherOwner {
-                override fun getLifecycle() = lifecycleOwner.lifecycle
-
-                override fun getOnBackPressedDispatcher() = dispatcher
+            val dispatcherOwner =
+                object : OnBackPressedDispatcherOwner, LifecycleOwner by lifecycleOwner {
+                    override val onBackPressedDispatcher = dispatcher
             }
             dispatcher.addCallback(lifecycleOwner) { }
             CompositionLocalProvider(
                 LocalOnBackPressedDispatcherOwner provides dispatcherOwner,
                 LocalLifecycleOwner provides lifecycleOwner
             ) {
-                BackHandler { inteceptedBack = true }
+                BackHandler { interceptedBack = true }
             }
             Button(onClick = { dispatcher.onBackPressed() }) {
                 Text(text = "Press Back")
@@ -140,7 +140,7 @@
 
         composeTestRule.onNodeWithText("Press Back").performClick()
         composeTestRule.runOnIdle {
-            assertThat(inteceptedBack).isEqualTo(true)
+            assertThat(interceptedBack).isEqualTo(true)
         }
     }
 }
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackPressedDispatcherOwnerTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackPressedDispatcherOwnerTest.kt
index 1dc4598..458eafa 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackPressedDispatcherOwnerTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/BackPressedDispatcherOwnerTest.kt
@@ -24,8 +24,8 @@
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.LifecycleOwner
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
@@ -55,14 +55,8 @@
     @Test
     fun testGetBackPressedDispatcherProviders() {
         val testDispatcherOwner: OnBackPressedDispatcherOwner =
-            object : OnBackPressedDispatcherOwner {
-                override fun getLifecycle(): Lifecycle {
-                    return LifecycleRegistry(this)
-                }
-
-                override fun getOnBackPressedDispatcher(): OnBackPressedDispatcher {
-                    return OnBackPressedDispatcher()
-                }
+            object : OnBackPressedDispatcherOwner, LifecycleOwner by TestLifecycleOwner() {
+                override val onBackPressedDispatcher = OnBackPressedDispatcher()
             }
 
         var innerDispatcherOwner: OnBackPressedDispatcherOwner? = null
diff --git a/activity/activity/api/current.ignore b/activity/activity/api/current.ignore
new file mode 100644
index 0000000..149cb0c
--- /dev/null
+++ b/activity/activity/api/current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.activity.result.IntentSenderRequest#CREATOR:
+    Field androidx.activity.result.IntentSenderRequest.CREATOR has changed type from android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest!> to android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest>
diff --git a/activity/activity/api/current.txt b/activity/activity/api/current.txt
index b7e30ab..688b1a0 100644
--- a/activity/activity/api/current.txt
+++ b/activity/activity/api/current.txt
@@ -48,10 +48,12 @@
   public class ComponentDialog extends android.app.Dialog implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner {
     ctor public ComponentDialog(android.content.Context context, optional @StyleRes int themeResId);
     ctor public ComponentDialog(android.content.Context context);
-    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method @CallSuper public void onBackPressed();
+    property public androidx.lifecycle.Lifecycle lifecycle;
+    property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
   }
 
@@ -99,6 +101,7 @@
 
   public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
     method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+    property public abstract androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
   }
 
   public final class ViewTreeFullyDrawnReporterOwner {
@@ -178,6 +181,7 @@
 
   public interface ActivityResultRegistryOwner {
     method public androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
+    property public abstract androidx.activity.result.ActivityResultRegistry activityResultRegistry;
   }
 
   public final class IntentSenderRequest implements android.os.Parcelable {
@@ -186,16 +190,24 @@
     method public int getFlagsMask();
     method public int getFlagsValues();
     method public android.content.IntentSender getIntentSender();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest!> CREATOR;
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.content.Intent? fillInIntent;
+    property public final int flagsMask;
+    property public final int flagsValues;
+    property public final android.content.IntentSender intentSender;
+    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest> CREATOR;
+    field public static final androidx.activity.result.IntentSenderRequest.Companion Companion;
   }
 
   public static final class IntentSenderRequest.Builder {
-    ctor public IntentSenderRequest.Builder(android.content.IntentSender);
-    ctor public IntentSenderRequest.Builder(android.app.PendingIntent);
+    ctor public IntentSenderRequest.Builder(android.content.IntentSender intentSender);
+    ctor public IntentSenderRequest.Builder(android.app.PendingIntent pendingIntent);
     method public androidx.activity.result.IntentSenderRequest build();
-    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent?);
-    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int, int);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent? fillInIntent);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int values, int mask);
+  }
+
+  public static final class IntentSenderRequest.Companion {
   }
 
   public final class PickVisualMediaRequest {
diff --git a/activity/activity/api/public_plus_experimental_current.txt b/activity/activity/api/public_plus_experimental_current.txt
index b7e30ab..688b1a0 100644
--- a/activity/activity/api/public_plus_experimental_current.txt
+++ b/activity/activity/api/public_plus_experimental_current.txt
@@ -48,10 +48,12 @@
   public class ComponentDialog extends android.app.Dialog implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner {
     ctor public ComponentDialog(android.content.Context context, optional @StyleRes int themeResId);
     ctor public ComponentDialog(android.content.Context context);
-    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method @CallSuper public void onBackPressed();
+    property public androidx.lifecycle.Lifecycle lifecycle;
+    property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
   }
 
@@ -99,6 +101,7 @@
 
   public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
     method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+    property public abstract androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
   }
 
   public final class ViewTreeFullyDrawnReporterOwner {
@@ -178,6 +181,7 @@
 
   public interface ActivityResultRegistryOwner {
     method public androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
+    property public abstract androidx.activity.result.ActivityResultRegistry activityResultRegistry;
   }
 
   public final class IntentSenderRequest implements android.os.Parcelable {
@@ -186,16 +190,24 @@
     method public int getFlagsMask();
     method public int getFlagsValues();
     method public android.content.IntentSender getIntentSender();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest!> CREATOR;
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.content.Intent? fillInIntent;
+    property public final int flagsMask;
+    property public final int flagsValues;
+    property public final android.content.IntentSender intentSender;
+    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest> CREATOR;
+    field public static final androidx.activity.result.IntentSenderRequest.Companion Companion;
   }
 
   public static final class IntentSenderRequest.Builder {
-    ctor public IntentSenderRequest.Builder(android.content.IntentSender);
-    ctor public IntentSenderRequest.Builder(android.app.PendingIntent);
+    ctor public IntentSenderRequest.Builder(android.content.IntentSender intentSender);
+    ctor public IntentSenderRequest.Builder(android.app.PendingIntent pendingIntent);
     method public androidx.activity.result.IntentSenderRequest build();
-    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent?);
-    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int, int);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent? fillInIntent);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int values, int mask);
+  }
+
+  public static final class IntentSenderRequest.Companion {
   }
 
   public final class PickVisualMediaRequest {
diff --git a/activity/activity/api/restricted_current.ignore b/activity/activity/api/restricted_current.ignore
new file mode 100644
index 0000000..149cb0c
--- /dev/null
+++ b/activity/activity/api/restricted_current.ignore
@@ -0,0 +1,3 @@
+// Baseline format: 1.0
+ChangedType: androidx.activity.result.IntentSenderRequest#CREATOR:
+    Field androidx.activity.result.IntentSenderRequest.CREATOR has changed type from android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest!> to android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest>
diff --git a/activity/activity/api/restricted_current.txt b/activity/activity/api/restricted_current.txt
index 474f211..1d24b8c 100644
--- a/activity/activity/api/restricted_current.txt
+++ b/activity/activity/api/restricted_current.txt
@@ -47,10 +47,12 @@
   public class ComponentDialog extends android.app.Dialog implements androidx.lifecycle.LifecycleOwner androidx.activity.OnBackPressedDispatcherOwner androidx.savedstate.SavedStateRegistryOwner {
     ctor public ComponentDialog(android.content.Context context, optional @StyleRes int themeResId);
     ctor public ComponentDialog(android.content.Context context);
-    method public final androidx.lifecycle.Lifecycle getLifecycle();
+    method public androidx.lifecycle.Lifecycle getLifecycle();
     method public final androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
     method public androidx.savedstate.SavedStateRegistry getSavedStateRegistry();
     method @CallSuper public void onBackPressed();
+    property public androidx.lifecycle.Lifecycle lifecycle;
+    property public final androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
   }
 
@@ -98,6 +100,7 @@
 
   public interface OnBackPressedDispatcherOwner extends androidx.lifecycle.LifecycleOwner {
     method public androidx.activity.OnBackPressedDispatcher getOnBackPressedDispatcher();
+    property public abstract androidx.activity.OnBackPressedDispatcher onBackPressedDispatcher;
   }
 
   public final class ViewTreeFullyDrawnReporterOwner {
@@ -177,6 +180,7 @@
 
   public interface ActivityResultRegistryOwner {
     method public androidx.activity.result.ActivityResultRegistry getActivityResultRegistry();
+    property public abstract androidx.activity.result.ActivityResultRegistry activityResultRegistry;
   }
 
   public final class IntentSenderRequest implements android.os.Parcelable {
@@ -185,16 +189,24 @@
     method public int getFlagsMask();
     method public int getFlagsValues();
     method public android.content.IntentSender getIntentSender();
-    method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest!> CREATOR;
+    method public void writeToParcel(android.os.Parcel dest, int flags);
+    property public final android.content.Intent? fillInIntent;
+    property public final int flagsMask;
+    property public final int flagsValues;
+    property public final android.content.IntentSender intentSender;
+    field public static final android.os.Parcelable.Creator<androidx.activity.result.IntentSenderRequest> CREATOR;
+    field public static final androidx.activity.result.IntentSenderRequest.Companion Companion;
   }
 
   public static final class IntentSenderRequest.Builder {
-    ctor public IntentSenderRequest.Builder(android.content.IntentSender);
-    ctor public IntentSenderRequest.Builder(android.app.PendingIntent);
+    ctor public IntentSenderRequest.Builder(android.content.IntentSender intentSender);
+    ctor public IntentSenderRequest.Builder(android.app.PendingIntent pendingIntent);
     method public androidx.activity.result.IntentSenderRequest build();
-    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent?);
-    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int, int);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFillInIntent(android.content.Intent? fillInIntent);
+    method public androidx.activity.result.IntentSenderRequest.Builder setFlags(int values, int mask);
+  }
+
+  public static final class IntentSenderRequest.Companion {
   }
 
   public final class PickVisualMediaRequest {
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
index 23758b0..a7769dc 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivityOverrideLifecycleTest.kt
@@ -62,17 +62,15 @@
 
     private val overrideLifecycle = LifecycleRegistry(this)
 
-    override fun getLifecycle(): Lifecycle {
-        return overrideLifecycle
-    }
+    override val lifecycle: Lifecycle
+        get() = overrideLifecycle
 }
 
 class LazyOverrideLifecycleComponentActivity : ComponentActivity() {
     private var overrideLifecycle: LifecycleRegistry? = null
 
-    override fun getLifecycle(): Lifecycle {
-        return overrideLifecycle ?: LifecycleRegistry(this).also {
+    override val lifecycle: Lifecycle
+        get() = overrideLifecycle ?: LifecycleRegistry(this).also {
             overrideLifecycle = it
         }
-    }
 }
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ViewTreeOnBackPressedDispatcherTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ViewTreeOnBackPressedDispatcherTest.kt
index f78e6ea..16430d7 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ViewTreeOnBackPressedDispatcherTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ViewTreeOnBackPressedDispatcherTest.kt
@@ -115,12 +115,10 @@
     }
 
     private class FakeOnBackPressedDispatcherOwner : OnBackPressedDispatcherOwner {
-        override fun getLifecycle(): Lifecycle {
-            throw UnsupportedOperationException("not a real OnBackPressedDispatcherOwner")
-        }
+        override val lifecycle: Lifecycle
+            get() = throw UnsupportedOperationException("not a real OnBackPressedDispatcherOwner")
 
-        override fun getOnBackPressedDispatcher(): OnBackPressedDispatcher {
-            throw UnsupportedOperationException("not a real OnBackPressedDispatcherOwner")
-        }
+        override val onBackPressedDispatcher
+            get() = throw UnsupportedOperationException("not a real OnBackPressedDispatcherOwner")
     }
 }
\ No newline at end of file
diff --git a/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt b/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt
index b04c8f7..2a2059c 100644
--- a/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt
+++ b/activity/activity/src/main/java/androidx/activity/ComponentDialog.kt
@@ -55,7 +55,8 @@
     override val savedStateRegistry: SavedStateRegistry
         get() = savedStateRegistryController.savedStateRegistry
 
-    final override fun getLifecycle(): Lifecycle = lifecycleRegistry
+    override val lifecycle: Lifecycle
+        get() = lifecycleRegistry
 
     override fun onSaveInstanceState(): Bundle {
         val bundle = super.onSaveInstanceState()
@@ -89,12 +90,10 @@
     }
 
     @Suppress("DEPRECATION")
-    private val onBackPressedDispatcher = OnBackPressedDispatcher {
+    final override val onBackPressedDispatcher = OnBackPressedDispatcher {
         super.onBackPressed()
     }
 
-    final override fun getOnBackPressedDispatcher() = onBackPressedDispatcher
-
     @CallSuper
     override fun onBackPressed() {
         onBackPressedDispatcher.onBackPressed()
diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt
index 8568d78..3101ef7 100644
--- a/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt
+++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedCallback.kt
@@ -36,7 +36,7 @@
  *
  * @param enabled The default enabled state for this callback.
  *
- * @see ComponentActivity.getOnBackPressedDispatcher
+ * @see OnBackPressedDispatcher
  */
 abstract class OnBackPressedCallback(enabled: Boolean) {
     /**
diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.java b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.java
deleted file mode 100644
index e07b3f9..0000000
--- a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.java
+++ /dev/null
@@ -1,40 +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.activity;
-
-import androidx.annotation.NonNull;
-import androidx.lifecycle.LifecycleOwner;
-
-/**
- * A class that has an {@link OnBackPressedDispatcher} that allows you to register a
- * {@link OnBackPressedCallback} for handling the system back button.
- * <p>
- * It is expected that classes that implement this interface route the system back button
- * to the dispatcher
- *
- * @see OnBackPressedDispatcher
- */
-public interface OnBackPressedDispatcherOwner extends LifecycleOwner {
-
-    /**
-     * Retrieve the {@link OnBackPressedDispatcher} that should handle the system back button.
-     *
-     * @return The {@link OnBackPressedDispatcher}.
-     */
-    @NonNull
-    OnBackPressedDispatcher getOnBackPressedDispatcher();
-}
diff --git a/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt
new file mode 100644
index 0000000..21401c7
--- /dev/null
+++ b/activity/activity/src/main/java/androidx/activity/OnBackPressedDispatcherOwner.kt
@@ -0,0 +1,34 @@
+/*
+ * 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.activity
+
+import androidx.lifecycle.LifecycleOwner
+
+/**
+ * A class that has an [OnBackPressedDispatcher] that allows you to register a
+ * [OnBackPressedCallback] for handling the system back button.
+ *
+ * It is expected that classes that implement this interface route the system back button
+ * to the dispatcher
+ *
+ * @see OnBackPressedDispatcher
+ */
+interface OnBackPressedDispatcherOwner : LifecycleOwner {
+    /**
+     * The [OnBackPressedDispatcher] that should handle the system back button.
+     */
+    val onBackPressedDispatcher: OnBackPressedDispatcher
+}
\ No newline at end of file
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.java b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.java
deleted file mode 100644
index c6ba684..0000000
--- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.java
+++ /dev/null
@@ -1,42 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.activity.result;
-
-import androidx.activity.result.contract.ActivityResultContract;
-import androidx.annotation.NonNull;
-
-/**
- * A class that has an {@link ActivityResultRegistry} that allows you to register a
- * {@link ActivityResultCallback} for handling an
- * {@link androidx.activity.result.contract.ActivityResultContract}.
- *
- * If it is not safe to call
- * {@link ActivityResultRegistry#register(String, ActivityResultContract, ActivityResultCallback)}
- * in the constructor, it is strongly recommended to also implement {@link ActivityResultCaller}.
- *
- * @see ActivityResultRegistry
- */
-public interface ActivityResultRegistryOwner {
-
-    /**
-     * Returns the ActivityResultRegistry of the provider.
-     *
-     * @return The activity result registry of the provider.
-     */
-    @NonNull
-    ActivityResultRegistry getActivityResultRegistry();
-}
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt
new file mode 100644
index 0000000..9ce51d1
--- /dev/null
+++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistryOwner.kt
@@ -0,0 +1,35 @@
+/*
+ * 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.activity.result
+
+/**
+ * A class that has an [ActivityResultRegistry] that allows you to register a
+ * [ActivityResultCallback] for handling an
+ * [androidx.activity.result.contract.ActivityResultContract].
+ *
+ * If it is not safe to call [ActivityResultRegistry.register]
+ * in the constructor, it is strongly recommended to also implement [ActivityResultCaller].
+ *
+ * @see ActivityResultRegistry
+ */
+interface ActivityResultRegistryOwner {
+    /**
+     * Returns the ActivityResultRegistry of the provider.
+     *
+     * @return The activity result registry of the provider.
+     */
+    val activityResultRegistry: ActivityResultRegistry
+}
\ No newline at end of file
diff --git a/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.java b/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.java
deleted file mode 100644
index cf43d82..0000000
--- a/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.java
+++ /dev/null
@@ -1,242 +0,0 @@
-/*
- * Copyright 2020 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.activity.result;
-
-import static android.content.Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS;
-import static android.content.Intent.FLAG_ACTIVITY_FORWARD_RESULT;
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT;
-import static android.content.Intent.FLAG_ACTIVITY_MATCH_EXTERNAL;
-import static android.content.Intent.FLAG_ACTIVITY_MULTIPLE_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_DOCUMENT;
-import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
-import static android.content.Intent.FLAG_ACTIVITY_NO_ANIMATION;
-import static android.content.Intent.FLAG_ACTIVITY_NO_HISTORY;
-import static android.content.Intent.FLAG_ACTIVITY_NO_USER_ACTION;
-import static android.content.Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_REORDER_TO_FRONT;
-import static android.content.Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED;
-import static android.content.Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS;
-import static android.content.Intent.FLAG_ACTIVITY_SINGLE_TOP;
-import static android.content.Intent.FLAG_ACTIVITY_TASK_ON_HOME;
-import static android.content.Intent.FLAG_DEBUG_LOG_RESOLUTION;
-import static android.content.Intent.FLAG_EXCLUDE_STOPPED_PACKAGES;
-import static android.content.Intent.FLAG_FROM_BACKGROUND;
-import static android.content.Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION;
-import static android.content.Intent.FLAG_GRANT_PREFIX_URI_PERMISSION;
-import static android.content.Intent.FLAG_GRANT_READ_URI_PERMISSION;
-import static android.content.Intent.FLAG_GRANT_WRITE_URI_PERMISSION;
-import static android.content.Intent.FLAG_INCLUDE_STOPPED_PACKAGES;
-
-import android.annotation.SuppressLint;
-import android.app.PendingIntent;
-import android.content.Intent;
-import android.content.IntentSender;
-import android.os.Parcel;
-import android.os.Parcelable;
-
-import androidx.annotation.IntDef;
-import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-
-import java.lang.annotation.Retention;
-import java.lang.annotation.RetentionPolicy;
-
-/**
- * A request for a
- * {@link androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult}
- * Activity Contract.
- */
-@SuppressLint("BanParcelableUsage")
-public final class IntentSenderRequest implements Parcelable {
-    @NonNull
-    private final IntentSender mIntentSender;
-    @Nullable
-    private final Intent mFillInIntent;
-    private final int mFlagsMask;
-    private final int mFlagsValues;
-
-    IntentSenderRequest(@NonNull IntentSender intentSender, @Nullable Intent intent, int flagsMask,
-            int flagsValues) {
-        mIntentSender = intentSender;
-        mFillInIntent = intent;
-        mFlagsMask = flagsMask;
-        mFlagsValues = flagsValues;
-    }
-
-    /**
-     * Get the intentSender from this IntentSenderRequest.
-     *
-     * @return the IntentSender to launch.
-     */
-    @NonNull
-    public IntentSender getIntentSender() {
-        return mIntentSender;
-    }
-
-    /**
-     * Get the intent from this IntentSender request.  If non-null, this will be provided as the
-     * intent parameter to IntentSender#sendIntent.
-     *
-     * @return the fill in intent.
-     */
-    @Nullable
-    public Intent getFillInIntent() {
-        return mFillInIntent;
-    }
-
-    /**
-     * Get the flag mask from this IntentSender request.
-     *
-     * @return intent flags in the original IntentSender that you would like to change.
-     */
-    public int getFlagsMask() {
-        return mFlagsMask;
-    }
-
-    /**
-     * Get the flag values from this IntentSender request.
-     *
-     * @return desired values for any bits set in flagsMask
-     */
-    public int getFlagsValues() {
-        return mFlagsValues;
-    }
-
-    @SuppressWarnings({"ConstantConditions", "deprecation"})
-    IntentSenderRequest(@NonNull Parcel in) {
-        mIntentSender = in.readParcelable(IntentSender.class.getClassLoader());
-        mFillInIntent = in.readParcelable(Intent.class.getClassLoader());
-        mFlagsMask = in.readInt();
-        mFlagsValues = in.readInt();
-    }
-
-    @NonNull
-    public static final Creator<IntentSenderRequest> CREATOR = new Creator<IntentSenderRequest>() {
-        @Override
-        public IntentSenderRequest createFromParcel(Parcel in) {
-            return new IntentSenderRequest(in);
-        }
-
-        @Override
-        public IntentSenderRequest[] newArray(int size) {
-            return new IntentSenderRequest[size];
-        }
-    };
-
-    @Override
-    public int describeContents() {
-        return 0;
-    }
-
-    @Override
-    public void writeToParcel(@NonNull Parcel dest, int flags) {
-        dest.writeParcelable(mIntentSender, flags);
-        dest.writeParcelable(mFillInIntent, flags);
-        dest.writeInt(mFlagsMask);
-        dest.writeInt(mFlagsValues);
-    }
-
-    /**
-     * A builder for constructing {@link IntentSenderRequest} instances.
-     */
-    public static final class Builder {
-        private IntentSender mIntentSender;
-        private Intent mFillInIntent;
-        private int mFlagsMask;
-        private int mFlagsValues;
-
-        @IntDef({FLAG_GRANT_READ_URI_PERMISSION, FLAG_GRANT_WRITE_URI_PERMISSION,
-                FLAG_FROM_BACKGROUND, FLAG_DEBUG_LOG_RESOLUTION, FLAG_EXCLUDE_STOPPED_PACKAGES,
-                FLAG_INCLUDE_STOPPED_PACKAGES, FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
-                FLAG_GRANT_PREFIX_URI_PERMISSION, FLAG_ACTIVITY_MATCH_EXTERNAL,
-                FLAG_ACTIVITY_NO_HISTORY, FLAG_ACTIVITY_SINGLE_TOP, FLAG_ACTIVITY_NEW_TASK,
-                FLAG_ACTIVITY_MULTIPLE_TASK, FLAG_ACTIVITY_CLEAR_TOP,
-                FLAG_ACTIVITY_FORWARD_RESULT, FLAG_ACTIVITY_PREVIOUS_IS_TOP,
-                FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS, FLAG_ACTIVITY_BROUGHT_TO_FRONT,
-                FLAG_ACTIVITY_RESET_TASK_IF_NEEDED, FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
-                FLAG_ACTIVITY_NEW_DOCUMENT, FLAG_ACTIVITY_NO_USER_ACTION,
-                FLAG_ACTIVITY_REORDER_TO_FRONT, FLAG_ACTIVITY_NO_ANIMATION,
-                FLAG_ACTIVITY_CLEAR_TASK, FLAG_ACTIVITY_TASK_ON_HOME,
-                FLAG_ACTIVITY_RETAIN_IN_RECENTS, FLAG_ACTIVITY_LAUNCH_ADJACENT})
-        @Retention(RetentionPolicy.SOURCE)
-        private @interface Flag {}
-
-        /**
-         * Constructor that takes an {@link IntentSender} and sets it for the builder.
-         *
-         * @param intentSender IntentSender to go in the IntentSenderRequest.
-         */
-        public Builder(@NonNull IntentSender intentSender) {
-            mIntentSender = intentSender;
-        }
-
-        /**
-         * Convenience constructor that takes an {@link PendingIntent} and uses
-         * its {@link IntentSender}.
-         *
-         * @param pendingIntent the pendingIntent containing with the intentSender to go in the
-         *                      IntentSenderRequest.
-         */
-        public Builder(@NonNull PendingIntent pendingIntent) {
-            this(pendingIntent.getIntentSender());
-        }
-
-        /**
-         * Set the intent for the {@link IntentSenderRequest}.
-         *
-         * @param fillInIntent intent to go in the IntentSenderRequest. If non-null, this
-         *                     will be provided as the intent parameter to IntentSender#sendIntent.
-         * @return This builder.
-         */
-        @NonNull
-        public Builder setFillInIntent(@Nullable Intent fillInIntent) {
-            mFillInIntent = fillInIntent;
-            return this;
-        }
-
-        /**
-         * Set the flag mask and flag values for the {@link IntentSenderRequest}.
-         *
-         * @param values flagValues to go in the IntentSenderRequest. Desired values for any bits
-         *             set in flagsMask
-         * @param mask mask to go in the IntentSenderRequest. Intent flags in the original
-         *             IntentSender that you would like to change.
-         *
-         * @return This builder.
-         */
-        @NonNull
-        public Builder setFlags(@Flag int values, int mask) {
-            mFlagsValues = values;
-            mFlagsMask = mask;
-            return this;
-        }
-
-        /**
-         * Build the IntentSenderRequest specified by this builder.
-         *
-         * @return the newly constructed IntentSenderRequest.
-         */
-        @NonNull
-        public IntentSenderRequest build() {
-            return new IntentSenderRequest(mIntentSender, mFillInIntent, mFlagsMask, mFlagsValues);
-        }
-    }
-}
diff --git a/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt b/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt
new file mode 100644
index 0000000..7da957a
--- /dev/null
+++ b/activity/activity/src/main/java/androidx/activity/result/IntentSenderRequest.kt
@@ -0,0 +1,176 @@
+/*
+ * 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.activity.result
+
+import android.annotation.SuppressLint
+import android.app.PendingIntent
+import android.content.Intent
+import android.content.IntentSender
+import android.os.Parcel
+import android.os.Parcelable
+import androidx.annotation.IntDef
+
+/**
+ * A request for a
+ * [androidx.activity.result.contract.ActivityResultContracts.StartIntentSenderForResult]
+ * Activity Contract.
+ */
+@SuppressLint("BanParcelableUsage")
+class IntentSenderRequest internal constructor(
+    /**
+     * The intentSender from this IntentSenderRequest.
+     */
+    val intentSender: IntentSender,
+    /**
+     * The intent from this IntentSender request. If non-null, this will be provided as the
+     * intent parameter to IntentSender#sendIntent.
+     */
+    val fillInIntent: Intent? = null,
+    /**
+     * The flag mask from this IntentSender request.
+     */
+    val flagsMask: Int = 0,
+    /**
+     * The flag values from this IntentSender request.
+     */
+    val flagsValues: Int = 0,
+) : Parcelable {
+
+    @Suppress("DEPRECATION")
+    internal constructor(parcel: Parcel) : this(
+        parcel.readParcelable(IntentSender::class.java.classLoader)!!,
+        parcel.readParcelable(Intent::class.java.classLoader),
+        parcel.readInt(),
+        parcel.readInt()
+    )
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    override fun writeToParcel(dest: Parcel, flags: Int) {
+        dest.writeParcelable(intentSender, flags)
+        dest.writeParcelable(fillInIntent, flags)
+        dest.writeInt(flagsMask)
+        dest.writeInt(flagsValues)
+    }
+
+    /**
+     * A builder for constructing [IntentSenderRequest] instances.
+     */
+    class Builder(private val intentSender: IntentSender) {
+        private var fillInIntent: Intent? = null
+        private var flagsMask = 0
+        private var flagsValues = 0
+
+        /**
+         * Convenience constructor that takes an [PendingIntent] and uses
+         * its [IntentSender].
+         *
+         * @param pendingIntent the pendingIntent containing with the intentSender to go in the
+         * IntentSenderRequest.
+         */
+        constructor(pendingIntent: PendingIntent) : this(pendingIntent.intentSender)
+
+        @IntDef(
+            flag = true,
+            value = [
+                Intent.FLAG_GRANT_READ_URI_PERMISSION,
+                Intent.FLAG_GRANT_WRITE_URI_PERMISSION,
+                Intent.FLAG_FROM_BACKGROUND,
+                Intent.FLAG_DEBUG_LOG_RESOLUTION,
+                Intent.FLAG_EXCLUDE_STOPPED_PACKAGES,
+                Intent.FLAG_INCLUDE_STOPPED_PACKAGES,
+                Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION,
+                Intent.FLAG_GRANT_PREFIX_URI_PERMISSION,
+                Intent.FLAG_ACTIVITY_MATCH_EXTERNAL,
+                Intent.FLAG_ACTIVITY_NO_HISTORY,
+                Intent.FLAG_ACTIVITY_SINGLE_TOP,
+                Intent.FLAG_ACTIVITY_NEW_TASK,
+                Intent.FLAG_ACTIVITY_MULTIPLE_TASK,
+                Intent.FLAG_ACTIVITY_CLEAR_TOP,
+                Intent.FLAG_ACTIVITY_FORWARD_RESULT,
+                Intent.FLAG_ACTIVITY_PREVIOUS_IS_TOP,
+                Intent.FLAG_ACTIVITY_EXCLUDE_FROM_RECENTS,
+                Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT,
+                Intent.FLAG_ACTIVITY_RESET_TASK_IF_NEEDED,
+                Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY,
+                Intent.FLAG_ACTIVITY_NEW_DOCUMENT,
+                Intent.FLAG_ACTIVITY_NO_USER_ACTION,
+                Intent.FLAG_ACTIVITY_REORDER_TO_FRONT,
+                Intent.FLAG_ACTIVITY_NO_ANIMATION,
+                Intent.FLAG_ACTIVITY_CLEAR_TASK,
+                Intent.FLAG_ACTIVITY_TASK_ON_HOME,
+                Intent.FLAG_ACTIVITY_RETAIN_IN_RECENTS,
+                Intent.FLAG_ACTIVITY_LAUNCH_ADJACENT
+            ]
+        )
+        @Retention(AnnotationRetention.SOURCE)
+        private annotation class Flag
+
+        /**
+         * Set the intent for the [IntentSenderRequest].
+         *
+         * @param fillInIntent intent to go in the IntentSenderRequest. If non-null, this
+         * will be provided as the intent parameter to IntentSender#sendIntent.
+         * @return This builder.
+         */
+        fun setFillInIntent(fillInIntent: Intent?): Builder {
+            this.fillInIntent = fillInIntent
+            return this
+        }
+
+        /**
+         * Set the flag mask and flag values for the [IntentSenderRequest].
+         *
+         * @param values flagValues to go in the IntentSenderRequest. Desired values for any bits
+         * set in flagsMask
+         * @param mask mask to go in the IntentSenderRequest. Intent flags in the original
+         * IntentSender that you would like to change.
+         *
+         * @return This builder.
+         */
+        fun setFlags(@Flag values: Int, mask: Int): Builder {
+            flagsValues = values
+            flagsMask = mask
+            return this
+        }
+
+        /**
+         * Build the IntentSenderRequest specified by this builder.
+         *
+         * @return the newly constructed IntentSenderRequest.
+         */
+        fun build(): IntentSenderRequest {
+            return IntentSenderRequest(intentSender, fillInIntent, flagsMask, flagsValues)
+        }
+    }
+
+    companion object {
+        @Suppress("unused")
+        @JvmField
+        val CREATOR: Parcelable.Creator<IntentSenderRequest> =
+            object : Parcelable.Creator<IntentSenderRequest> {
+                override fun createFromParcel(inParcel: Parcel): IntentSenderRequest {
+                    return IntentSenderRequest(inParcel)
+                }
+
+                override fun newArray(size: Int): Array<IntentSenderRequest?> {
+                    return arrayOfNulls(size)
+                }
+            }
+    }
+}
\ No newline at end of file
diff --git a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
index 947be53..d795756 100644
--- a/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
+++ b/activity/integration-tests/testapp/src/main/java/androidx/activity/integration/testapp/MainActivity.kt
@@ -172,10 +172,13 @@
                     val request = IntentSenderRequest.Builder(
                         PendingIntent.getActivity(
                             context,
-                            0, Intent(MediaStore.ACTION_IMAGE_CAPTURE), 0
-                        ).intentSender
+                            0,
+                            Intent(MediaStore.ACTION_IMAGE_CAPTURE),
+                            PendingIntent.FLAG_IMMUTABLE
+                        )
                     )
-                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK, 1)
+                        .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP,
+                            1)
                         .build()
                     intentSender.launch(request)
                 }
diff --git a/appactions/interaction/interaction-capabilities-core/build.gradle b/appactions/interaction/interaction-capabilities-core/build.gradle
index 066897d..57dc092 100644
--- a/appactions/interaction/interaction-capabilities-core/build.gradle
+++ b/appactions/interaction/interaction-capabilities-core/build.gradle
@@ -15,6 +15,7 @@
  */
 
 import androidx.build.LibraryType
+import androidx.build.Publish
 
 plugins {
     id("AndroidXPlugin")
@@ -22,15 +23,43 @@
 }
 
 dependencies {
+    api(libs.autoValueAnnotations)
+    annotationProcessor(libs.autoValue)
+    implementation(libs.protobufLite)
+    implementation(libs.guavaListenableFuture)
+    implementation("androidx.concurrent:concurrent-futures:1.1.0")
+    implementation(project(":appactions:interaction:interaction-proto"))
+    testImplementation(libs.junit)
+    testImplementation(libs.truth)
+    testImplementation(libs.testCore)
+    testImplementation(libs.mockitoCore)
+    testImplementation(libs.kotlinStdlib)
+    testImplementation(libs.androidLint)
+    testImplementation(libs.androidLintTests)
+    testImplementation(libs.autoValueAnnotations)
+    testImplementation(libs.testRunner)
+    testAnnotationProcessor(libs.autoValue)
 }
 
 android {
+    defaultConfig {
+        // TODO(b/266649259): lower minSdk version to 19.
+        minSdkVersion 33
+    }
+
+    lintOptions {
+        // TODO(b/266849030): Remove when updating library.
+        disable("UnknownNullness", "SyntheticAccessor")
+    }
+
     namespace "androidx.appactions.interaction.capabilities.core"
 }
 
 androidx {
     name = "androidx.appactions.interaction:interaction-capabilities-core"
     type = LibraryType.PUBLISHED_LIBRARY
+    publish = Publish.NONE
     inceptionYear = "2023"
     description = "App Interaction library core capabilities API and implementation."
+    failOnDeprecationWarnings = false
 }
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractCapabilityBuilder.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractCapabilityBuilder.java
new file mode 100644
index 0000000..a7c0999
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractCapabilityBuilder.java
@@ -0,0 +1,137 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionCapabilityImpl;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
+import androidx.appactions.interaction.capabilities.core.task.impl.AbstractTaskUpdater;
+import androidx.appactions.interaction.capabilities.core.task.impl.TaskCapabilityImpl;
+
+import java.util.Objects;
+import java.util.Optional;
+
+/**
+ * An abstract Builder class for ActionCapability.
+ *
+ * @param <BuilderT>
+ * @param <PropertyT>
+ * @param <ArgumentT>
+ * @param <OutputT>
+ * @param <ConfirmationT>
+ * @param <TaskUpdaterT>
+ */
+public abstract class AbstractCapabilityBuilder<
+        BuilderT extends
+                AbstractCapabilityBuilder<
+                        BuilderT, PropertyT, ArgumentT, OutputT, ConfirmationT, TaskUpdaterT>,
+        PropertyT,
+        ArgumentT,
+        OutputT,
+        ConfirmationT,
+        TaskUpdaterT extends AbstractTaskUpdater> {
+
+    private final ActionSpec<PropertyT, ArgumentT, OutputT> mActionSpec;
+    @Nullable
+    private String mId;
+    @Nullable
+    private PropertyT mProperty;
+    @Nullable
+    private ActionExecutor<ArgumentT, OutputT> mActionExecutor;
+    @Nullable
+    private TaskHandler<ArgumentT, OutputT, ConfirmationT, TaskUpdaterT> mTaskHandler;
+
+    /**
+     * @param actionSpec
+     */
+    protected AbstractCapabilityBuilder(
+            @NonNull ActionSpec<PropertyT, ArgumentT, OutputT> actionSpec) {
+        this.mActionSpec = actionSpec;
+    }
+
+    @SuppressWarnings("unchecked") // cast to child class
+    private BuilderT asBuilder() {
+        return (BuilderT) this;
+    }
+
+    /**
+     * Sets the Id of the capability being built. The Id should be a non-null string that is unique
+     * among all ActionCapability, and should not change during/across activity lifecycles.
+     */
+    @NonNull
+    public final BuilderT setId(@NonNull String id) {
+        this.mId = id;
+        return asBuilder();
+    }
+
+    /**
+     * Sets the Property instance for this capability. Must be called before {@link
+     * AbstractCapabilityBuilder.build}.
+     */
+    protected final BuilderT setProperty(@NonNull PropertyT property) {
+        this.mProperty = property;
+        return asBuilder();
+    }
+
+    /**
+     * Sets the TaskHandler for this capability. The individual capability factory classes can
+     * decide
+     * to expose their own public {@code setTaskHandler} method and invoke this parent method.
+     * Setting
+     * the TaskHandler should build a capability instance that supports multi-turn tasks.
+     */
+    protected final BuilderT setTaskHandler(
+            @NonNull TaskHandler<ArgumentT, OutputT, ConfirmationT, TaskUpdaterT> taskHandler) {
+        this.mTaskHandler = taskHandler;
+        return asBuilder();
+    }
+
+    /** Sets the ActionExecutor for this capability. */
+    @NonNull
+    public final BuilderT setActionExecutor(
+            @NonNull ActionExecutor<ArgumentT, OutputT> actionExecutor) {
+        this.mActionExecutor = actionExecutor;
+        return asBuilder();
+    }
+
+    /** Builds and returns this ActionCapability. */
+    @NonNull
+    public ActionCapability build() {
+        Objects.requireNonNull(mProperty, "property must not be null.");
+        if (mTaskHandler == null) {
+            Objects.requireNonNull(mActionExecutor, "actionExecutor must not be null.");
+            return new ActionCapabilityImpl<>(
+                    mActionSpec, Optional.ofNullable(mId), mProperty, mActionExecutor);
+        }
+        TaskCapabilityImpl<PropertyT, ArgumentT, OutputT, ConfirmationT, TaskUpdaterT>
+                taskCapability =
+                new TaskCapabilityImpl<>(
+                        Objects.requireNonNull(mId, "id field must not be null."),
+                        mActionSpec,
+                        mProperty,
+                        mTaskHandler.getParamsRegistry(),
+                        mTaskHandler.getOnInitListener(),
+                        mTaskHandler.getOnReadyToConfirmListener(),
+                        mTaskHandler.getOnFinishListener(),
+                        mTaskHandler.getConfirmationDataBindings(),
+                        mTaskHandler.getExecutionOutputBindings(),
+                        Runnable::run);
+        taskCapability.setTaskUpdaterSupplier(mTaskHandler.getTaskUpdaterSupplier());
+        return taskCapability;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractTaskHandlerBuilder.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractTaskHandlerBuilder.java
new file mode 100644
index 0000000..b73802c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/AbstractTaskHandlerBuilder.java
@@ -0,0 +1,288 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.impl.converters.DisambigEntityConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.SearchActionConverter;
+import androidx.appactions.interaction.capabilities.core.task.AppEntityListResolver;
+import androidx.appactions.interaction.capabilities.core.task.AppEntityResolver;
+import androidx.appactions.interaction.capabilities.core.task.InvalidTaskException;
+import androidx.appactions.interaction.capabilities.core.task.InventoryListResolver;
+import androidx.appactions.interaction.capabilities.core.task.InventoryResolver;
+import androidx.appactions.interaction.capabilities.core.task.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+import androidx.appactions.interaction.capabilities.core.task.ValueListListener;
+import androidx.appactions.interaction.capabilities.core.task.ValueListener;
+import androidx.appactions.interaction.capabilities.core.task.impl.AbstractTaskUpdater;
+import androidx.appactions.interaction.capabilities.core.task.impl.GenericResolverInternal;
+import androidx.appactions.interaction.capabilities.core.task.impl.OnReadyToConfirmListenerInternal;
+import androidx.appactions.interaction.capabilities.core.task.impl.TaskParamRegistry;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * An abstract Builder class for an ActionCapability that supports task.
+ *
+ * @param <BuilderT>
+ * @param <ArgumentT>
+ * @param <OutputT>
+ * @param <ConfirmationT>
+ * @param <TaskUpdaterT>
+ */
+public abstract class AbstractTaskHandlerBuilder<
+        BuilderT extends
+                AbstractTaskHandlerBuilder<BuilderT, ArgumentT, OutputT, ConfirmationT,
+                        TaskUpdaterT>,
+        ArgumentT,
+        OutputT,
+        ConfirmationT,
+        TaskUpdaterT extends AbstractTaskUpdater> {
+
+    private final ConfirmationType mConfirmationType;
+    private final TaskParamRegistry.Builder mParamsRegistry;
+    private final Map<String, Function<OutputT, List<ParamValue>>> mExecutionOutputBindings =
+            new HashMap<>();
+    private final Map<String, Function<ConfirmationT, List<ParamValue>>> mConfirmationDataBindings =
+            new HashMap<>();
+    @Nullable
+    private OnInitListener<TaskUpdaterT> mOnInitListener;
+    @Nullable
+    private OnReadyToConfirmListenerInternal<ConfirmationT> mOnReadyToConfirmListener;
+    @Nullable
+    private OnDialogFinishListener<ArgumentT, OutputT> mOnFinishListener;
+
+    protected AbstractTaskHandlerBuilder() {
+        this(ConfirmationType.NOT_SUPPORTED);
+    }
+
+    protected AbstractTaskHandlerBuilder(@NonNull ConfirmationType confirmationType) {
+        this.mConfirmationType = confirmationType;
+        this.mParamsRegistry = TaskParamRegistry.builder();
+    }
+
+    @SuppressWarnings("unchecked") // cast to child class
+    protected BuilderT asBuilder() {
+        return (BuilderT) this;
+    }
+
+    /** Sets the OnInitListener for this capability. */
+    public final BuilderT setOnInitListener(@NonNull OnInitListener<TaskUpdaterT> onInitListener) {
+        this.mOnInitListener = onInitListener;
+        return asBuilder();
+    }
+
+    /** Sets the onReadyToConfirmListener for this capability. */
+    protected final BuilderT setOnReadyToConfirmListenerInternal(
+            @NonNull OnReadyToConfirmListenerInternal<ConfirmationT> onReadyToConfirm) {
+        this.mOnReadyToConfirmListener = onReadyToConfirm;
+        return asBuilder();
+    }
+
+    /** Sets the onFinishListener for this capability. */
+    public final BuilderT setOnFinishListener(
+            @NonNull OnDialogFinishListener<ArgumentT, OutputT> onFinishListener) {
+        this.mOnFinishListener = onFinishListener;
+        return asBuilder();
+    }
+
+    protected <ValueTypeT> void registerInventoryTaskParam(
+            @NonNull String paramName,
+            @NonNull InventoryResolver<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                (paramValue) -> !paramValue.hasIdentifier(),
+                GenericResolverInternal.fromInventoryResolver(listener),
+                Optional.empty(),
+                Optional.empty(),
+                converter);
+    }
+
+    protected <ValueTypeT> void registerInventoryListTaskParam(
+            @NonNull String paramName,
+            @NonNull InventoryListResolver<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                (paramValue) -> !paramValue.hasIdentifier(),
+                GenericResolverInternal.fromInventoryListResolver(listener),
+                Optional.empty(),
+                Optional.empty(),
+                converter);
+    }
+
+    protected <ValueTypeT> void registerAppEntityTaskParam(
+            @NonNull String paramName,
+            @NonNull AppEntityResolver<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter,
+            @NonNull DisambigEntityConverter<ValueTypeT> entityConverter,
+            @NonNull SearchActionConverter<ValueTypeT> searchActionConverter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                (paramValue) -> !paramValue.hasIdentifier(),
+                GenericResolverInternal.fromAppEntityResolver(listener),
+                Optional.of(entityConverter),
+                Optional.of(searchActionConverter),
+                converter);
+    }
+
+    protected <ValueTypeT> void registerAppEntityListTaskParam(
+            @NonNull String paramName,
+            @NonNull AppEntityListResolver<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter,
+            @NonNull DisambigEntityConverter<ValueTypeT> entityConverter,
+            @NonNull SearchActionConverter<ValueTypeT> searchActionConverter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                (paramValue) -> !paramValue.hasIdentifier(),
+                GenericResolverInternal.fromAppEntityListResolver(listener),
+                Optional.of(entityConverter),
+                Optional.of(searchActionConverter),
+                converter);
+    }
+
+    protected <ValueTypeT> void registerValueTaskParam(
+            @NonNull String paramName,
+            @NonNull ValueListener<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                unused -> false,
+                GenericResolverInternal.fromValueListener(listener),
+                Optional.empty(),
+                Optional.empty(),
+                converter);
+    }
+
+    protected <ValueTypeT> void registerValueListTaskParam(
+            @NonNull String paramName,
+            @NonNull ValueListListener<ValueTypeT> listener,
+            @NonNull ParamValueConverter<ValueTypeT> converter) {
+        mParamsRegistry.addTaskParameter(
+                paramName,
+                unused -> false,
+                GenericResolverInternal.fromValueListListener(listener),
+                Optional.empty(),
+                Optional.empty(),
+                converter);
+    }
+
+    /**
+     * Registers an optional execution output.
+     *
+     * @param paramName    the BII output slot name of this parameter.
+     * @param outputGetter a getter of the output from the {@code OutputT} instance.
+     * @param converter    a converter from an output object to a ParamValue.
+     */
+    protected <T> void registerExecutionOutput(
+            @NonNull String paramName,
+            @NonNull Function<OutputT, Optional<T>> outputGetter,
+            @NonNull Function<T, ParamValue> converter) {
+        mExecutionOutputBindings.put(
+                paramName,
+                output -> outputGetter.apply(output).stream().map(converter).collect(
+                        toImmutableList()));
+    }
+
+    /**
+     * Registers a repeated execution output.
+     *
+     * @param paramName    the BII output slot name of this parameter.
+     * @param outputGetter a getter of the output from the {@code OutputT} instance.
+     * @param converter    a converter from an output object to a ParamValue.
+     */
+    protected <T> void registerRepeatedExecutionOutput(
+            @NonNull String paramName,
+            @NonNull Function<OutputT, List<T>> outputGetter,
+            @NonNull Function<T, ParamValue> converter) {
+        mExecutionOutputBindings.put(
+                paramName,
+                output -> outputGetter.apply(output).stream().map(converter).collect(
+                        toImmutableList()));
+    }
+
+    /**
+     * Registers an optional confirmation data.
+     *
+     * @param paramName          the BIC confirmation data slot name of this parameter.
+     * @param confirmationGetter a getter of the confirmation data from the {@code ConfirmationT}
+     *                           instance.
+     * @param converter          a converter from confirmation data to a ParamValue.
+     */
+    protected <T> void registerConfirmationOutput(
+            @NonNull String paramName,
+            @NonNull Function<ConfirmationT, Optional<T>> confirmationGetter,
+            @NonNull Function<T, ParamValue> converter) {
+        mConfirmationDataBindings.put(
+                paramName,
+                output ->
+                        confirmationGetter.apply(output).stream().map(converter).collect(
+                                toImmutableList()));
+    }
+
+    /** Specific capability builders override this to support BII-specific TaskUpdaters. */
+    @NonNull
+    protected abstract Supplier<TaskUpdaterT> getTaskUpdaterSupplier();
+
+    /**
+     * Build a TaskHandler.
+     */
+    @NonNull
+    public TaskHandler<ArgumentT, OutputT, ConfirmationT, TaskUpdaterT> build() {
+        if (this.mConfirmationType == ConfirmationType.REQUIRED
+                && mOnReadyToConfirmListener == null) {
+            throw new InvalidTaskException(
+                    "ConfirmationType is REQUIRED, but onReadyToConfirmListener is not set.");
+        }
+        if (this.mConfirmationType == ConfirmationType.NOT_SUPPORTED
+                && mOnReadyToConfirmListener != null) {
+            throw new InvalidTaskException(
+                    "ConfirmationType is NOT_SUPPORTED, but onReadyToConfirmListener is set.");
+        }
+        return new TaskHandler<>(
+                mConfirmationType,
+                mParamsRegistry.build(),
+                Optional.ofNullable(mOnInitListener),
+                Optional.ofNullable(mOnReadyToConfirmListener),
+                Objects.requireNonNull(mOnFinishListener, "onTaskFinishListener must not be null."),
+                mConfirmationDataBindings,
+                mExecutionOutputBindings,
+                getTaskUpdaterSupplier());
+    }
+
+    /** Confirmation types for a Capability. */
+    protected enum ConfirmationType {
+        // Confirmation is not supported for this Capability.
+        NOT_SUPPORTED,
+        // This Capability requires confirmation.
+        REQUIRED,
+        // Confirmation is optional for this Capability.
+        OPTIONAL
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.java
new file mode 100644
index 0000000..7f1b4e5
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionCapability.java
@@ -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.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+
+import java.util.Optional;
+
+/**
+ * <b>Do not implement this interface yourself.</b>
+ *
+ * <p>An ActionCapability represents some supported App Action that can be given to App Control.
+ *
+ * <p>Use helper classes provided by the capability library to get instances of this interface.
+ */
+public interface ActionCapability {
+
+    /** Returns the unique Id of this capability declaration. */
+    @NonNull
+    Optional<String> getId();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutor.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutor.java
new file mode 100644
index 0000000..c0c32f5
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ActionExecutor.java
@@ -0,0 +1,67 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+
+/**
+ * An interface of executing the action.
+ *
+ * @param <ArgumentT>
+ * @param <OutputT>
+ */
+public interface ActionExecutor<ArgumentT, OutputT> {
+    /**
+     * Calls to execute the action.
+     *
+     * @param argument the argument for this action.
+     * @param callback the callback to send back the action execution result.
+     */
+    void execute(@NonNull ArgumentT argument, @NonNull ActionCallback<OutputT> callback);
+
+    /** Reasons for the action execution error. */
+    enum ErrorStatus {
+        CANCELLED,
+        /** The action execution error was caused by a timeout. */
+        TIMEOUT,
+    }
+
+    /**
+     * An interface for receiving the result of action.
+     *
+     * @param <OutputT>
+     */
+    interface ActionCallback<OutputT> {
+
+        /** Invoke to set an action result upon success. */
+        void onSuccess(@NonNull ExecutionResult<OutputT> executionResult);
+
+        /** Invoke to set an action result upon success. */
+        default void onSuccess() {
+            onSuccess(ExecutionResult.<OutputT>newBuilderWithOutput().build());
+        }
+
+        /**
+         * Invokes to set an error status for the action.
+         *
+         * @deprecated
+         */
+        @Deprecated
+        void onError(@NonNull ErrorStatus errorStatus);
+    }
+
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ConfirmationOutput.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ConfirmationOutput.java
new file mode 100644
index 0000000..7a9c9c6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ConfirmationOutput.java
@@ -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.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Class that represents the response after all slots are filled and accepted and the task is ready
+ * to enter the confirmation turn.
+ *
+ * @param <ConfirmationT>
+ */
+@AutoValue
+public abstract class ConfirmationOutput<ConfirmationT> {
+
+    /**
+     * Create a Builder instance for building a ConfirmationOutput instance without confirmation
+     * output.
+     */
+    @NonNull
+    public static Builder<Void> newBuilder() {
+        return new AutoValue_ConfirmationOutput.Builder<>();
+    }
+
+    /** Returns a default ConfirmationOutput instance. */
+    @NonNull
+    public static ConfirmationOutput<Void> getDefaultInstance() {
+        return ConfirmationOutput.newBuilder().build();
+    }
+
+    /** Create a Builder instance for building a ConfirmationOutput instance. */
+    @NonNull
+    public static <ConfirmationT> Builder<ConfirmationT> newBuilderWithConfirmation() {
+        return new AutoValue_ConfirmationOutput.Builder<>();
+    }
+
+    /** Returns a default ConfirmationOutput instance with a confirmation output type. */
+    @NonNull
+    public static <ConfirmationT>
+            ConfirmationOutput<ConfirmationT> getDefaultInstanceWithConfirmation() {
+        return ConfirmationOutput.<ConfirmationT>newBuilderWithConfirmation().build();
+    }
+
+    /** The confirmation output. */
+    @Nullable
+    public abstract ConfirmationT getConfirmation();
+
+    /**
+     * Builder for ConfirmationOutput.
+     *
+     * @param <ConfirmationT>
+     */
+    @AutoValue.Builder
+    public abstract static class Builder<ConfirmationT> {
+
+        /** Sets the confirmation output. */
+        @NonNull
+        public abstract Builder<ConfirmationT> setConfirmation(ConfirmationT confirmation);
+
+        /** Builds and returns the ConfirmationOutput instance. */
+        @NonNull
+        public abstract ConfirmationOutput<ConfirmationT> build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ExecutionResult.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ExecutionResult.java
new file mode 100644
index 0000000..b42f20b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/ExecutionResult.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.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import com.google.auto.value.AutoValue;
+
+/**
+ * Class that represents the response after an ActionCapability fulfills an action.
+ *
+ * @param <OutputT>
+ */
+@AutoValue
+public abstract class ExecutionResult<OutputT> {
+
+    /** Create a Builder instance for building a ExecutionResult instance without output. */
+    @NonNull
+    public static Builder<Void> newBuilder() {
+        return new AutoValue_ExecutionResult.Builder<Void>().setStartDictation(false);
+    }
+
+    /** Returns a default ExecutionResult instance. */
+    @NonNull
+    public static ExecutionResult<Void> getDefaultInstance() {
+        return ExecutionResult.newBuilder().build();
+    }
+
+    /** Create a Builder instance for building a ExecutionResult instance. */
+    @NonNull
+    public static <OutputT> Builder<OutputT> newBuilderWithOutput() {
+        return new AutoValue_ExecutionResult.Builder<OutputT>().setStartDictation(false);
+    }
+
+    /** Returns a default ExecutionResult instance with an output type. */
+    @NonNull
+    public static <OutputT> ExecutionResult<OutputT> getDefaultInstanceWithOutput() {
+        return ExecutionResult.<OutputT>newBuilderWithOutput().build();
+    }
+
+    /** Whether to start dictation mode after the fulfillment. */
+    public abstract boolean getStartDictation();
+
+    /** The execution output. */
+    @Nullable
+    public abstract OutputT getOutput();
+
+    /**
+     * Builder for ExecutionResult.
+     *
+     * @param <OutputT>
+     */
+    @AutoValue.Builder
+    public abstract static class Builder<OutputT> {
+        /** Sets whether or not this fulfillment should start dictation. */
+        @NonNull
+        public abstract Builder<OutputT> setStartDictation(boolean startDictation);
+
+        /** Sets the execution output. */
+        @NonNull
+        public abstract Builder<OutputT> setOutput(OutputT output);
+
+        /** Builds and returns the ExecutionResult instance. */
+        @NonNull
+        public abstract ExecutionResult<OutputT> build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/TaskHandler.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/TaskHandler.java
new file mode 100644
index 0000000..1341b36
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/TaskHandler.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.AbstractTaskHandlerBuilder.ConfirmationType;
+import androidx.appactions.interaction.capabilities.core.task.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+import androidx.appactions.interaction.capabilities.core.task.impl.AbstractTaskUpdater;
+import androidx.appactions.interaction.capabilities.core.task.impl.OnReadyToConfirmListenerInternal;
+import androidx.appactions.interaction.capabilities.core.task.impl.TaskParamRegistry;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Temporary holder for Task related data.
+ *
+ * @param <ArgumentT>
+ * @param <OutputT>
+ * @param <ConfirmationT>
+ * @param <TaskUpdaterT>
+ */
+public final class TaskHandler<
+        ArgumentT, OutputT, ConfirmationT, TaskUpdaterT extends AbstractTaskUpdater> {
+
+    private final ConfirmationType mConfirmationType;
+    private final TaskParamRegistry mParamsRegistry;
+    private final Map<String, Function<OutputT, List<ParamValue>>> mExecutionOutputBindings;
+    private final Map<String, Function<ConfirmationT, List<ParamValue>>> mConfirmationDataBindings;
+    private final Optional<OnInitListener<TaskUpdaterT>> mOnInitListener;
+    private final Optional<OnReadyToConfirmListenerInternal<ConfirmationT>>
+            mOnReadyToConfirmListener;
+    private final OnDialogFinishListener<ArgumentT, OutputT> mOnFinishListener;
+    private final Supplier<TaskUpdaterT> mTaskUpdaterSupplier;
+
+    TaskHandler(
+            @NonNull ConfirmationType confirmationType,
+            @NonNull TaskParamRegistry paramsRegistry,
+            @NonNull Optional<OnInitListener<TaskUpdaterT>> onInitListener,
+            @NonNull Optional<OnReadyToConfirmListenerInternal<ConfirmationT>> onReadyToConfirmListener,
+            @NonNull OnDialogFinishListener<ArgumentT, OutputT> onFinishListener,
+            @NonNull Map<String, Function<ConfirmationT, List<ParamValue>>> confirmationDataBindings,
+            @NonNull Map<String, Function<OutputT, List<ParamValue>>> executionOutputBindings,
+            @NonNull Supplier<TaskUpdaterT> taskUpdaterSupplier) {
+        this.mConfirmationType = confirmationType;
+        this.mParamsRegistry = paramsRegistry;
+        this.mOnInitListener = onInitListener;
+        this.mOnReadyToConfirmListener = onReadyToConfirmListener;
+        this.mOnFinishListener = onFinishListener;
+        this.mConfirmationDataBindings = confirmationDataBindings;
+        this.mExecutionOutputBindings = executionOutputBindings;
+        this.mTaskUpdaterSupplier = taskUpdaterSupplier;
+    }
+
+    ConfirmationType getConfirmationType() {
+        return mConfirmationType;
+    }
+
+    TaskParamRegistry getParamsRegistry() {
+        return mParamsRegistry;
+    }
+
+    Map<String, Function<OutputT, List<ParamValue>>> getExecutionOutputBindings() {
+        return mExecutionOutputBindings;
+    }
+
+    Map<String, Function<ConfirmationT, List<ParamValue>>> getConfirmationDataBindings() {
+        return mConfirmationDataBindings;
+    }
+
+    Optional<OnInitListener<TaskUpdaterT>> getOnInitListener() {
+        return mOnInitListener;
+    }
+
+    Optional<OnReadyToConfirmListenerInternal<ConfirmationT>> getOnReadyToConfirmListener() {
+        return mOnReadyToConfirmListener;
+    }
+
+    OnDialogFinishListener<ArgumentT, OutputT> getOnFinishListener() {
+        return mOnFinishListener;
+    }
+
+    Supplier<TaskUpdaterT> getTaskUpdaterSupplier() {
+        return mTaskUpdaterSupplier;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilityInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilityInternal.java
new file mode 100644
index 0000000..10ffa4b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ActionCapabilityInternal.java
@@ -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.appactions.interaction.capabilities.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ActionCapability;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+
+/** An interface that represents the capability of an app action. */
+public interface ActionCapabilityInternal extends ActionCapability {
+
+    /** Returns an app action proto describing how to fulfill this capability. */
+    @NonNull
+    AppAction getAppAction();
+
+    /**
+     * 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.
+     */
+    void execute(@NonNull ArgumentsWrapper argumentsWrapper, @NonNull CallbackInternal callback);
+
+    /**
+     * 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.
+     */
+    default void setTouchEventCallback(@NonNull TouchEventCallback callback) {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.java
new file mode 100644
index 0000000..a8ea310
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ArgumentsWrapper.java
@@ -0,0 +1,92 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentParam;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/** Represents Fulfillment request sent from assistant, including arguments. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class ArgumentsWrapper {
+
+    /**
+     * Creates an instance of ArgumentsWrapper based on the Fulfillment send from Assistant.
+     *
+     * @param fulfillment for a single BII sent from Assistant.
+     */
+    @NonNull
+    public static ArgumentsWrapper create(@NonNull Fulfillment fulfillment) {
+        return new AutoValue_ArgumentsWrapper(
+                Collections.unmodifiableMap(convertToArgumentMap(fulfillment)),
+                createRequestMetadata(fulfillment));
+    }
+
+    private static Optional<RequestMetadata> createRequestMetadata(Fulfillment fulfillment) {
+        if (fulfillment.getType() == Fulfillment.Type.UNKNOWN_TYPE
+                || fulfillment.getType() == Fulfillment.Type.UNRECOGNIZED) {
+            return Optional.empty();
+        }
+        return Optional.of(
+                RequestMetadata.newBuilder().setRequestType(fulfillment.getType()).build());
+    }
+
+    private static Map<String, List<FulfillmentValue>> convertToArgumentMap(
+            Fulfillment fulfillment) {
+        Map<String, List<FulfillmentValue>> result = new LinkedHashMap<>();
+        for (FulfillmentParam fp : fulfillment.getParamsList()) {
+            // Normalize deprecated param value list into new FulfillmentValue.
+            if (!fp.getValuesList().isEmpty()) {
+                result.put(
+                        fp.getName(),
+                        fp.getValuesList().stream()
+                                .map(paramValue -> FulfillmentValue.newBuilder().setValue(
+                                        paramValue).build())
+                                .collect(toImmutableList()));
+            } else {
+                result.put(fp.getName(), fp.getFulfillmentValuesList());
+            }
+        }
+        return result;
+    }
+
+    /**
+     * A map of BII parameter names to a task param value, where each {@code FulfillmentValue} can
+     * have a value and {@code DisambigData} sent from Assistant.
+     */
+    @NonNull
+    public abstract Map<String, List<FulfillmentValue>> paramValues();
+
+    /**
+     * Metadata from the FulfillmentRequest on the current Assistant turn. This field should be
+     * Optional.empty for one-shot capabilities.
+     */
+    @NonNull
+    public abstract Optional<RequestMetadata> requestMetadata();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
new file mode 100644
index 0000000..03934ba
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/BuilderOf.java
@@ -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.appactions.interaction.capabilities.core.impl;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A builder of objects of some specific type.
+ *
+ * @param <T>
+ */
+public interface BuilderOf<T> {
+    @NonNull
+    T build();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/CallbackInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/CallbackInternal.java
new file mode 100644
index 0000000..db2ab01
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/CallbackInternal.java
@@ -0,0 +1,34 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+
+/** An interface for receiving the result of action. */
+public interface CallbackInternal {
+
+    /** Invoke to set an action result upon success. */
+    void onSuccess(@NonNull FulfillmentResponse fulfillmentResponse);
+
+    default void onSuccess() {
+        onSuccess(FulfillmentResponse.getDefaultInstance());
+    }
+
+    /** Invokes to set an error status for the action. */
+    void onError(@NonNull ErrorStatusInternal errorStatus);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ErrorStatusInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ErrorStatusInternal.java
new file mode 100644
index 0000000..c707770
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/ErrorStatusInternal.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl;
+
+/** A class to define exceptions that are reported from dialog capability API. */
+public enum ErrorStatusInternal {
+    CANCELLED(0),
+    TIMEOUT(1),
+    INVALID_REQUEST_TYPE(2),
+    UNCHANGED_DISAMBIG_STATE(3),
+    INVALID_RESOLVER(4),
+    STRUCT_CONVERSION_FAILURE(5),
+    SYNC_REQUEST_FAILURE(6),
+    CONFIRMATION_REQUEST_FAILURE(7),
+    TOUCH_EVENT_REQUEST_FAILURE(8);
+
+    private final int mCode;
+
+    ErrorStatusInternal(int code) {
+        this.mCode = code;
+    }
+
+    public int getCode() {
+        return mCode;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/RequestMetadata.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/RequestMetadata.java
new file mode 100644
index 0000000..7230d51
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/RequestMetadata.java
@@ -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.appactions.interaction.capabilities.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents metadata from the Assistant FulfillmentRequest. */
+@AutoValue
+public abstract class RequestMetadata {
+
+    /** Create a Builder instance for building a RequestMetadata instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_RequestMetadata.Builder();
+    }
+
+    /** The Type of request Assistant is sending on this FulfillmentRequest. */
+    @NonNull
+    public abstract Fulfillment.Type requestType();
+
+    /** Builder for RequestMetadata. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+        /** Sets the FulfillmentRequest.Type. */
+        @NonNull
+        public abstract Builder setRequestType(@NonNull Fulfillment.Type requestType);
+
+        /** Builds the RequestMetadata instance. */
+        @NonNull
+        public abstract RequestMetadata build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/TouchEventCallback.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/TouchEventCallback.java
new file mode 100644
index 0000000..c86aeed
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/TouchEventCallback.java
@@ -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.capabilities.core.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+import androidx.appactions.interaction.proto.TouchEventMetadata;
+
+/**
+ * An internal interface to allow the AppInteraction SDKs to be notified of results from processing
+ * touch events.
+ */
+public interface TouchEventCallback {
+
+    /** Results from a successful touch event invocation. */
+    void onSuccess(
+            @NonNull FulfillmentResponse fulfillmentResponse,
+            @NonNull TouchEventMetadata touchEventMetadata);
+
+    /** Results from an unsuccessful touch event invocation. */
+    void onError(@NonNull ErrorStatusInternal errorStatus);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FutureCallback.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FutureCallback.java
new file mode 100644
index 0000000..6d2ca04
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FutureCallback.java
@@ -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.impl.concurrent;
+
+import androidx.annotation.NonNull;
+
+/**
+ * A FutureCallback that can be attached to a ListenableFuture with Futures#addCallback.
+ *
+ * @param <V>
+ */
+public interface FutureCallback<V> {
+    /** Called with the ListenableFuture's result if it completes successfully. */
+    void onSuccess(V result);
+
+    /** Called with the ListenableFuture's exception if it fails. */
+    void onFailure(@NonNull Throwable t);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Futures.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Futures.java
new file mode 100644
index 0000000..51a4614
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Futures.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.annotation.SuppressLint;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Executor;
+import java.util.concurrent.Future;
+import java.util.function.Function;
+
+/** Future/ListenableFuture related utility methods. */
+public final class Futures {
+    private Futures() {
+    }
+
+    /** Attach a FutureCallback to a ListenableFuture instance. */
+    public static <V> void addCallback(
+            @NonNull final ListenableFuture<V> future,
+            @NonNull final FutureCallback<? super V> callback,
+            @NonNull Executor executor) {
+        Utils.checkNotNull(callback);
+        future.addListener(new CallbackListener<>(future, callback), executor);
+    }
+
+    /**
+     * Transforms an input ListenableFuture into a second ListenableFuture by applying a
+     * transforming
+     * function to the result of the input ListenableFuture.
+     */
+    @NonNull
+    @SuppressLint("LambdaLast")
+    public static <I, O> ListenableFuture<O> transform(
+            @NonNull ListenableFuture<I> input,
+            @NonNull Function<I, O> function,
+            @NonNull Executor executor,
+            @Nullable String tag) {
+        return CallbackToFutureAdapter.getFuture(
+                completer -> {
+                    addCallback(input, transformFutureCallback(completer, function), executor);
+                    return tag;
+                });
+    }
+
+    /**
+     * Transforms an input ListenableFuture into a second ListenableFuture by applying an
+     * asynchronous
+     * transforming function to the result of the input ListenableFuture.
+     */
+    @NonNull
+    @SuppressLint("LambdaLast")
+    public static <I, O> ListenableFuture<O> transformAsync(
+            @NonNull ListenableFuture<I> input,
+            @NonNull Function<I, ListenableFuture<O>> asyncFunction,
+            @NonNull Executor executor,
+            @NonNull String tag) {
+
+        return CallbackToFutureAdapter.getFuture(
+                completer -> {
+                    addCallback(input, asyncTransformFutureCallback(completer, asyncFunction),
+                            executor);
+                    return tag;
+                });
+    }
+
+    /** Returns a Future that is immediately complete with the given value. */
+    @NonNull
+    public static <V> ListenableFuture<V> immediateFuture(V value) {
+        return CallbackToFutureAdapter.getFuture(completer -> completer.set(value));
+    }
+
+    /** Returns a Future that is immediately complete with null value. */
+    @NonNull
+    public static ListenableFuture<Void> immediateVoidFuture() {
+        return CallbackToFutureAdapter.getFuture(completer -> completer.set(null));
+    }
+
+    /** Returns a Future that is immediately complete with an exception. */
+    @NonNull
+    public static <V> ListenableFuture<V> immediateFailedFuture(@NonNull Throwable throwable) {
+        return CallbackToFutureAdapter.getFuture(completer -> completer.setException(throwable));
+    }
+
+    /**
+     * Returns a FutureCallback that transform the result in onSuccess, and then set the result in
+     * completer.
+     */
+    static <I, O> FutureCallback<I> transformFutureCallback(
+            Completer<O> completer, Function<I, O> function) {
+        return new FutureCallback<I>() {
+            @Override
+            public void onSuccess(I result) {
+                try {
+                    completer.set(function.apply(result));
+                } catch (Throwable t) {
+                    if (t instanceof InterruptedException) {
+                        Thread.currentThread().interrupt();
+                    }
+                    completer.setException(t);
+                }
+            }
+
+            @Override
+            public void onFailure(Throwable failure) {
+                completer.setException(failure);
+            }
+        };
+    }
+
+    /** Returns a FutureCallback that asynchronously transform the result. */
+    private static <I, O> FutureCallback<I> asyncTransformFutureCallback(
+            Completer<O> completer, Function<I, ListenableFuture<O>> asyncFunction) {
+        return new FutureCallback<I>() {
+            @Override
+            public void onSuccess(I inputResult) {
+                try {
+                    addCallback(
+                            asyncFunction.apply(inputResult),
+                            transformFutureCallback(completer, Function.identity()),
+                            Runnable::run);
+                } catch (Throwable t) {
+                    if (t instanceof InterruptedException) {
+                        Thread.currentThread().interrupt();
+                    }
+                    completer.setException(t);
+                }
+            }
+
+            @Override
+            public void onFailure(@NonNull Throwable failure) {
+                completer.setException(failure);
+            }
+        };
+    }
+
+    static <V> V getDone(Future<V> future) throws ExecutionException {
+        Utils.checkState(future.isDone(), "future is expected to be done already.");
+        boolean interrupted = false;
+        try {
+            while (true) {
+                try {
+                    return future.get();
+                } catch (InterruptedException e) {
+                    interrupted = true;
+                }
+            }
+        } finally {
+            if (interrupted) {
+                Thread.currentThread().interrupt();
+            }
+        }
+    }
+
+    private static final class CallbackListener<V> implements Runnable {
+        final Future<V> mFuture;
+        final FutureCallback<? super V> mCallback;
+
+        CallbackListener(Future<V> future, FutureCallback<? super V> callback) {
+            this.mFuture = future;
+            this.mCallback = callback;
+        }
+
+        @Override
+        public void run() {
+            final V value;
+            try {
+                value = getDone(mFuture);
+            } catch (ExecutionException e) {
+                Throwable cause = e.getCause();
+                mCallback.onFailure(cause != null ? cause : e);
+                return;
+            } catch (RuntimeException | Error e) {
+                mCallback.onFailure(e);
+                return;
+            }
+            mCallback.onSuccess(value);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Utils.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Utils.java
new file mode 100644
index 0000000..aeb0e9b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/Utils.java
@@ -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.appactions.interaction.capabilities.core.impl.concurrent;
+
+import androidx.annotation.Nullable;
+
+final class Utils {
+
+    private Utils() {
+    }
+
+    public static <T> T checkNotNull(@Nullable T reference) {
+        if (reference == null) {
+            throw new NullPointerException();
+        }
+        return reference;
+    }
+
+    public static <T> T checkNotNull(@Nullable T reference, String errorMessage) {
+        if (reference == null) {
+            throw new NullPointerException(errorMessage);
+        }
+        return reference;
+    }
+
+    public static void checkState(boolean b, String message) {
+        if (!b) {
+            throw new IllegalStateException(message);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/CheckedInterfaces.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/CheckedInterfaces.java
new file mode 100644
index 0000000..056c6e8
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/CheckedInterfaces.java
@@ -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.appactions.interaction.capabilities.core.impl.converters;
+
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+
+/** Contains function interfaces that throw checked exceptions. */
+public final class CheckedInterfaces {
+
+    private CheckedInterfaces() {
+    }
+
+    /** A BiConsumer interface that can throw StructConversionException. */
+    @FunctionalInterface
+    interface BiConsumer<T, U> {
+        void accept(T t, U u) throws StructConversionException;
+    }
+
+    /**
+     * A Function interface that can throw StructConversionException.
+     *
+     * @param <T>
+     * @param <R>
+     */
+    @FunctionalInterface
+    public interface Function<T, R> {
+        R apply(T t) throws StructConversionException;
+    }
+
+    /** A Consumer interface that can throw StructConversionException. */
+    @FunctionalInterface
+    interface Consumer<T> {
+        void accept(T t) throws StructConversionException;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/DisambigEntityConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/DisambigEntityConverter.java
new file mode 100644
index 0000000..9a95a32
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/DisambigEntityConverter.java
@@ -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.impl.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.Entity;
+
+/**
+ * Converter from {@code ValueTypeT} to the app-driven disambig entity i.e. {@code Entity} proto.
+ * The ValueTypeT instance is usually a value object provided by the app.
+ *
+ * @param <ValueTypeT>
+ */
+@FunctionalInterface
+public interface DisambigEntityConverter<ValueTypeT> {
+    @NonNull
+    Entity convert(ValueTypeT type) throws StructConversionException;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/FieldBinding.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/FieldBinding.java
new file mode 100644
index 0000000..048f5bb
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/FieldBinding.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+import com.google.protobuf.Value;
+
+import java.util.Optional;
+import java.util.function.Function;
+
+@AutoValue
+abstract class FieldBinding<T, BuilderT extends BuilderOf<T>> {
+
+    static <T, BuilderT extends BuilderOf<T>> FieldBinding<T, BuilderT> create(
+            String name,
+            Function<T, Optional<Value>> valueGetter,
+            CheckedInterfaces.BiConsumer<BuilderT, Optional<Value>> valueSetter) {
+        return new AutoValue_FieldBinding<>(name, valueGetter, valueSetter);
+    }
+
+    abstract String name();
+
+    abstract Function<T, Optional<Value>> valueGetter();
+
+    abstract CheckedInterfaces.BiConsumer<BuilderT, Optional<Value>> valueSetter();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java
new file mode 100644
index 0000000..21d5e41
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/ParamValueConverter.java
@@ -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.impl.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.ParamValue;
+
+/**
+ * A function that converts a single ParamValue to some generic object visible to the SDK user.
+ *
+ * @param <T>
+ */
+@FunctionalInterface
+public interface ParamValueConverter<T> {
+    @NonNull
+    T convert(@NonNull ParamValue paramValue) throws StructConversionException;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/PropertyConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/PropertyConverter.java
new file mode 100644
index 0000000..f665360
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/PropertyConverter.java
@@ -0,0 +1,158 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.properties.EnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.IntegerProperty;
+import androidx.appactions.interaction.capabilities.core.properties.ParamProperty;
+import androidx.appactions.interaction.capabilities.core.properties.SimpleProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringOrEnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty.PossibleValue;
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
+import androidx.appactions.interaction.proto.Entity;
+
+import java.util.List;
+import java.util.function.Function;
+
+/** Contains utility functions that convert properties to IntentParameter proto. */
+public final class PropertyConverter {
+
+    private PropertyConverter() {
+    }
+
+    /** Create IntentParameter proto from a StringProperty. */
+    @NonNull
+    public static IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull StringProperty property) {
+        IntentParameter.Builder builder = newIntentParameterBuilder(paramName, property);
+        extractPossibleValues(property, PropertyConverter::possibleValueToProto).stream()
+                .forEach(builder::addPossibleEntities);
+        return builder.build();
+    }
+
+    /** Create IntentParameter proto from a StringOrEnumProperty. */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull StringOrEnumProperty<EnumT> property) {
+        IntentParameter.Builder builder = newIntentParameterBuilder(paramName, property);
+        extractPossibleValues(property, PropertyConverter::possibleValueToProto).stream()
+                .forEach(builder::addPossibleEntities);
+        return builder.build();
+    }
+
+    /** Create IntentParameter proto from a EntityProperty. */
+    @NonNull
+    public static IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull EntityProperty property) {
+        IntentParameter.Builder builder = newIntentParameterBuilder(paramName, property);
+        extractPossibleValues(property, PropertyConverter::entityToProto).stream()
+                .forEach(builder::addPossibleEntities);
+        return builder.build();
+    }
+
+    /** Create IntentParameter proto from a EnumProperty. */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull EnumProperty<EnumT> property) {
+        IntentParameter.Builder builder = newIntentParameterBuilder(paramName, property);
+        extractPossibleValues(property, PropertyConverter::enumToProto).stream()
+                .forEach(builder::addPossibleEntities);
+        return builder.build();
+    }
+
+    /** Create IntentParameter proto from a IntegerProperty. */
+    @NonNull
+    public static IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull IntegerProperty property) {
+        return newIntentParameterBuilder(paramName, property).build();
+    }
+
+    /** Create IntentParameter proto from a SimpleProperty. */
+    @NonNull
+    public static IntentParameter getIntentParameter(
+            @NonNull String paramName, @NonNull SimpleProperty property) {
+        return newIntentParameterBuilder(paramName, property).build();
+    }
+
+    /** Create IntentParameter.Builder from a generic ParamProperty, fills in the common fields. */
+    private static IntentParameter.Builder newIntentParameterBuilder(
+            String paramName, ParamProperty<?> paramProperty) {
+        return IntentParameter.newBuilder()
+                .setName(paramName)
+                .setIsRequired(paramProperty.isRequired())
+                .setEntityMatchRequired(paramProperty.isValueMatchRequired())
+                .setIsProhibited(paramProperty.isProhibited());
+    }
+
+    private static <T> List<Entity> extractPossibleValues(
+            ParamProperty<T> property, Function<T, Entity> function) {
+        return property.getPossibleValues().stream().map(function).collect(toImmutableList());
+    }
+
+    /** Converts a properties/Entity to a appactions Entity proto. */
+    @NonNull
+    public static Entity entityToProto(
+            @NonNull androidx.appactions.interaction.capabilities.core.properties.Entity entity) {
+        Entity.Builder builder = Entity.newBuilder().addAllAlternateNames(
+                entity.getAlternateNames());
+        entity.getName().ifPresent(builder::setName);
+        entity.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /**
+     * Converts a capabilities library StringProperty.PossibleValue to a appactions Entity proto
+     * .
+     */
+    @NonNull
+    public static Entity possibleValueToProto(@NonNull PossibleValue possibleValue) {
+        return androidx.appactions.interaction.proto.Entity.newBuilder()
+                .setIdentifier(possibleValue.getName())
+                .setName(possibleValue.getName())
+                .addAllAlternateNames(possibleValue.getAlternateNames())
+                .build();
+    }
+
+    /**
+     * Converts a capabilities library StringOrEnumProperty.PossibleValue to a appactions Entity
+     * proto.
+     */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> Entity possibleValueToProto(
+            @NonNull StringOrEnumProperty.PossibleValue<EnumT> possibleValue) {
+
+        switch (possibleValue.getKind()) {
+            case STRING_VALUE:
+                return possibleValueToProto(possibleValue.stringValue());
+            case ENUM_VALUE:
+                return enumToProto(possibleValue.enumValue());
+        }
+        throw new IllegalStateException("unreachable");
+    }
+
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> Entity enumToProto(EnumT enumValue) {
+        return androidx.appactions.interaction.proto.Entity.newBuilder()
+                .setIdentifier(enumValue.toString())
+                .build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SearchActionConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SearchActionConverter.java
new file mode 100644
index 0000000..a9e98f5
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SearchActionConverter.java
@@ -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.impl.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+import androidx.appactions.interaction.proto.ParamValue;
+
+/**
+ * Converts an ungrounded ParamValue to a SearchAction object.
+ *
+ * @param <T>
+ */
+@FunctionalInterface
+public interface SearchActionConverter<T> {
+    @NonNull
+    SearchAction<T> toSearchAction(@NonNull ParamValue paramValue) throws StructConversionException;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java
new file mode 100644
index 0000000..d9ae329
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/SlotTypeConverter.java
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Converts from internal proto representation as defined in the AppActionContext to a public java
+ * type which can be consumed by developers.
+ *
+ * @param <T>
+ */
+@FunctionalInterface
+public interface SlotTypeConverter<T> {
+    @NonNull
+    static <T> SlotTypeConverter<List<T>> ofRepeated(
+            @NonNull ParamValueConverter<T> singularConverter) {
+        return (paramValues) -> {
+            List<T> results = new ArrayList<>();
+            for (ParamValue paramValue : paramValues) {
+                results.add(singularConverter.convert(paramValue));
+            }
+            return results;
+        };
+    }
+
+    /** This converter will throw IndexOutOfBoundsException if the input List is empty. */
+    @NonNull
+    static <T> SlotTypeConverter<T> ofSingular(
+            @NonNull ParamValueConverter<T> singularConverter) {
+        return (paramValues) -> singularConverter.convert(paramValues.get(0));
+    }
+
+    T convert(@NonNull List<ParamValue> protoList) throws StructConversionException;
+}
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
new file mode 100644
index 0000000..14c48d5
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConverters.java
@@ -0,0 +1,766 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.values.Alarm;
+import androidx.appactions.interaction.capabilities.core.values.CalendarEvent;
+import androidx.appactions.interaction.capabilities.core.values.Call;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+import androidx.appactions.interaction.capabilities.core.values.ItemList;
+import androidx.appactions.interaction.capabilities.core.values.ListItem;
+import androidx.appactions.interaction.capabilities.core.values.Message;
+import androidx.appactions.interaction.capabilities.core.values.Order;
+import androidx.appactions.interaction.capabilities.core.values.OrderItem;
+import androidx.appactions.interaction.capabilities.core.values.Organization;
+import androidx.appactions.interaction.capabilities.core.values.ParcelDelivery;
+import androidx.appactions.interaction.capabilities.core.values.Person;
+import androidx.appactions.interaction.capabilities.core.values.SafetyCheck;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+import androidx.appactions.interaction.capabilities.core.values.Timer;
+import androidx.appactions.interaction.capabilities.core.values.properties.Attendee;
+import androidx.appactions.interaction.capabilities.core.values.properties.Participant;
+import androidx.appactions.interaction.capabilities.core.values.properties.Recipient;
+import androidx.appactions.interaction.proto.Entity;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.protobuf.ListValue;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+
+/** Converters for capability argument values. Convert from internal proto types to public types. */
+public final class TypeConverters {
+    public static final String FIELD_NAME_TYPE = "@type";
+    public static final TypeSpec<ListItem> LIST_ITEM_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("ListItem", ListItem::newBuilder).build();
+    public static final TypeSpec<ItemList> ITEM_LIST_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("ItemList", ItemList::newBuilder)
+                    .bindRepeatedSpecField(
+                            "itemListElement",
+                            ItemList::getListItems,
+                            ItemList.Builder::addAllListItems,
+                            LIST_ITEM_TYPE_SPEC)
+                    .build();
+    public static final TypeSpec<OrderItem> ORDER_ITEM_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("OrderItem", OrderItem::newBuilder).build();
+    public static final TypeSpec<Organization> ORGANIZATION_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("Organization", Organization::newBuilder).build();
+    public static final TypeSpec<ParcelDelivery> PARCEL_DELIVERY_TYPE_SPEC =
+            TypeSpecBuilder.newBuilder("ParcelDelivery", ParcelDelivery::newBuilder)
+                    .bindStringField(
+                            "deliveryAddress",
+                            ParcelDelivery::getDeliveryAddress,
+                            ParcelDelivery.Builder::setDeliveryAddress)
+                    .bindZonedDateTimeField(
+                            "expectedArrivalFrom",
+                            ParcelDelivery::getExpectedArrivalFrom,
+                            ParcelDelivery.Builder::setExpectedArrivalFrom)
+                    .bindZonedDateTimeField(
+                            "expectedArrivalUntil",
+                            ParcelDelivery::getExpectedArrivalUntil,
+                            ParcelDelivery.Builder::setExpectedArrivalUntil)
+                    .bindStringField(
+                            "hasDeliveryMethod",
+                            ParcelDelivery::getDeliveryMethod,
+                            ParcelDelivery.Builder::setDeliveryMethod)
+                    .bindStringField(
+                            "trackingNumber",
+                            ParcelDelivery::getTrackingNumber,
+                            ParcelDelivery.Builder::setTrackingNumber)
+                    .bindStringField(
+                            "trackingUrl", ParcelDelivery::getTrackingUrl,
+                            ParcelDelivery.Builder::setTrackingUrl)
+                    .build();
+    public static final TypeSpec<Order> ORDER_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("Order", Order::newBuilder)
+                    .bindZonedDateTimeField("orderDate", Order::getOrderDate,
+                            Order.Builder::setOrderDate)
+                    .bindSpecField(
+                            "orderDelivery",
+                            Order::getOrderDelivery,
+                            Order.Builder::setOrderDelivery,
+                            PARCEL_DELIVERY_TYPE_SPEC)
+                    .bindRepeatedSpecField(
+                            "orderedItem",
+                            Order::getOrderedItems,
+                            Order.Builder::addAllOrderedItems,
+                            ORDER_ITEM_TYPE_SPEC)
+                    .bindEnumField(
+                            "orderStatus",
+                            Order::getOrderStatus,
+                            Order.Builder::setOrderStatus,
+                            Order.OrderStatus.class)
+                    .bindSpecField(
+                            "seller", Order::getSeller, Order.Builder::setSeller,
+                            ORGANIZATION_TYPE_SPEC)
+                    .build();
+    public static final TypeSpec<Person> PERSON_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("Person", Person::newBuilder)
+                    .bindStringField("email", Person::getEmail, Person.Builder::setEmail)
+                    .bindStringField("telephone", Person::getTelephone,
+                            Person.Builder::setTelephone)
+                    .bindStringField("name", Person::getName, Person.Builder::setName)
+                    .build();
+    public static final TypeSpec<Alarm> ALARM_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("Alarm", Alarm::newBuilder).build();
+    public static final TypeSpec<Timer> TIMER_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("Timer", Timer::newBuilder).build();
+    public static final TypeSpec<CalendarEvent> CALENDAR_EVENT_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("CalendarEvent", CalendarEvent::newBuilder)
+                    .bindZonedDateTimeField(
+                            "startDate", CalendarEvent::getStartDate,
+                            CalendarEvent.Builder::setStartDate)
+                    .bindZonedDateTimeField(
+                            "endDate", CalendarEvent::getEndDate, CalendarEvent.Builder::setEndDate)
+                    .bindRepeatedSpecField(
+                            "attendee",
+                            CalendarEvent::getAttendeeList,
+                            CalendarEvent.Builder::addAllAttendee,
+                            new AttendeeTypeSpec())
+                    .build();
+    public static final TypeSpec<SafetyCheck> SAFETY_CHECK_TYPE_SPEC =
+            TypeSpecBuilder.newBuilderForThing("SafetyCheck", SafetyCheck::newBuilder)
+                    .bindDurationField("duration", SafetyCheck::getDuration,
+                            SafetyCheck.Builder::setDuration)
+                    .bindZonedDateTimeField(
+                            "checkinTime", SafetyCheck::getCheckinTime,
+                            SafetyCheck.Builder::setCheckinTime)
+                    .build();
+    private static final String FIELD_NAME_CALL_FORMAT = "callFormat";
+    private static final String FIELD_NAME_PARTICIPANT = "participant";
+    private static final String FIELD_NAME_TYPE_CALL = "Call";
+    private static final String FIELD_NAME_TYPE_PERSON = "Person";
+    private static final String FIELD_NAME_TYPE_MESSAGE = "Message";
+    private static final String FIELD_NAME_RECIPIENT = "recipient";
+    private static final String FIELD_NAME_TEXT = "text";
+
+    private TypeConverters() {
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     */
+    @NonNull
+    public static EntityValue toEntityValue(@NonNull ParamValue paramValue) {
+        EntityValue.Builder value = EntityValue.newBuilder();
+        if (paramValue.hasIdentifier()) {
+            value.setId(paramValue.getIdentifier());
+        }
+        value.setValue(paramValue.getStringValue());
+        return value.build();
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     */
+    public static int toIntegerValue(@NonNull ParamValue paramValue) {
+        return (int) paramValue.getNumberValue();
+    }
+
+    /** Converts a ParamValue to a Boolean object.
+     *
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static Boolean toBooleanValue(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (paramValue.hasBoolValue()) {
+            return paramValue.getBoolValue();
+        }
+
+        throw new StructConversionException(
+                "Cannot parse boolean because bool_value is missing from ParamValue.");
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static LocalDate toLocalDate(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (paramValue.hasStringValue()) {
+            try {
+                return LocalDate.parse(paramValue.getStringValue());
+            } catch (DateTimeParseException e) {
+                throw new StructConversionException("Failed to parse ISO 8601 string to LocalDate",
+                        e);
+            }
+        }
+        throw new StructConversionException(
+                "Cannot parse date because string_value is missing from ParamValue.");
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static LocalTime toLocalTime(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (paramValue.hasStringValue()) {
+            try {
+                return LocalTime.parse(paramValue.getStringValue());
+            } catch (DateTimeParseException e) {
+                throw new StructConversionException("Failed to parse ISO 8601 string to LocalTime",
+                        e);
+            }
+        }
+        throw new StructConversionException(
+                "Cannot parse time because string_value is missing from ParamValue.");
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static ZoneId toZoneId(@NonNull ParamValue paramValue) throws StructConversionException {
+        if (paramValue.hasStringValue()) {
+            try {
+                return ZoneId.of(paramValue.getStringValue());
+            } catch (DateTimeParseException e) {
+                throw new StructConversionException("Failed to parse ISO 8601 string to ZoneId", e);
+            }
+        }
+        throw new StructConversionException(
+                "Cannot parse ZoneId because string_value is missing from ParamValue.");
+    }
+
+    /**
+     * Gets String value for a string property.
+     *
+     * <p>If identifier is present, it's the String value, otherwise it is {@code
+     * paramValue.getStringValue()}
+     *
+     * @param paramValue
+     * @return
+     */
+    @NonNull
+    public static String toStringValue(@NonNull ParamValue paramValue) {
+        if (paramValue.hasIdentifier()) {
+            return paramValue.getIdentifier();
+        }
+        return paramValue.getStringValue();
+    }
+
+    /**
+     * @param entityValue
+     * @return
+     */
+    @NonNull
+    public static Entity toEntity(@NonNull EntityValue entityValue) {
+        return Entity.newBuilder()
+                .setIdentifier(entityValue.getId().get())
+                .setName(entityValue.getValue())
+                .build();
+    }
+
+    /**
+     * Converts an ItemList object to an Entity proto message.
+     *
+     * @param itemList
+     * @return
+     */
+    @NonNull
+    public static Entity toEntity(@NonNull ItemList itemList) {
+        Entity.Builder builder = Entity.newBuilder().setValue(
+                ITEM_LIST_TYPE_SPEC.toStruct(itemList));
+        itemList.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /**
+     * Converts a ListItem object to an Entity proto message.
+     *
+     * @param listItem
+     * @return
+     */
+    @NonNull
+    public static Entity toEntity(@NonNull ListItem listItem) {
+        Entity.Builder builder = Entity.newBuilder().setValue(
+                LIST_ITEM_TYPE_SPEC.toStruct(listItem));
+        listItem.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /**
+     * Converts an Order object to an Entity proto message.
+     *
+     * @param order
+     * @return
+     */
+    @NonNull
+    public static Entity toEntity(@NonNull Order order) {
+        Entity.Builder builder = Entity.newBuilder().setValue(ORDER_TYPE_SPEC.toStruct(order));
+        order.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /**
+     * Converts an Alarm object to an Entity proto message.
+     *
+     * @param alarm
+     * @return
+     */
+    @NonNull
+    public static Entity toEntity(@NonNull Alarm alarm) {
+        Entity.Builder builder = Entity.newBuilder().setValue(ALARM_TYPE_SPEC.toStruct(alarm));
+        alarm.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /**
+     * Converts a ParamValue to a single ItemList object.
+     *
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static ItemList toItemList(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        return ITEM_LIST_TYPE_SPEC.fromStruct(paramValue.getStructValue());
+    }
+
+    /**
+     * Converts a ParamValue to a single ListItem object.
+     *
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static ListItem toListItem(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        return LIST_ITEM_TYPE_SPEC.fromStruct(paramValue.getStructValue());
+    }
+
+    /**
+     * Converts a ParamValue to a single Order object.
+     *
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static Order toOrder(@NonNull ParamValue paramValue) throws StructConversionException {
+        return ORDER_TYPE_SPEC.fromStruct(paramValue.getStructValue());
+    }
+
+    /**
+     * Converts a ParamValue to a Timer object.
+     *
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static Timer toTimer(@NonNull ParamValue paramValue) throws StructConversionException {
+        return TIMER_TYPE_SPEC.fromStruct(paramValue.getStructValue());
+    }
+
+    /**
+     * Converts a ParamValue to a single Alarm object.
+     *
+     * @param paramValue
+     * @return
+     */
+    @NonNull
+    public static Alarm toAssistantAlarm(@NonNull ParamValue paramValue) {
+        return Alarm.newBuilder().setId(paramValue.getIdentifier()).build();
+    }
+
+    /**
+     * @param executionResult
+     * @return
+     */
+    @NonNull
+    public static FulfillmentResponse toFulfillmentResponseProto(
+            @NonNull ExecutionResult<Void> executionResult) {
+        return FulfillmentResponse.newBuilder()
+                .setStartDictation(executionResult.getStartDictation())
+                .build();
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static ZonedDateTime toZonedDateTime(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (paramValue.hasStringValue()) {
+            try {
+                return ZonedDateTime.parse(paramValue.getStringValue());
+            } catch (DateTimeParseException e) {
+                throw new StructConversionException(
+                        "Failed to parse ISO 8601 string to ZonedDateTime", e);
+            }
+        }
+        throw new StructConversionException(
+                "Cannot parse datetime because string_value is missing from ParamValue.");
+    }
+
+    /**
+     * @param paramValue
+     * @return
+     * @throws StructConversionException
+     */
+    @NonNull
+    public static Duration toDuration(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (!paramValue.hasStringValue()) {
+            throw new StructConversionException(
+                    "Cannot parse duration because string_value is missing from ParamValue.");
+        }
+        try {
+            return Duration.parse(paramValue.getStringValue());
+        } catch (DateTimeParseException e) {
+            throw new StructConversionException("Failed to parse ISO 8601 string to Duration", e);
+        }
+    }
+
+    /**
+     * @param nestedTypeSpec
+     * @param <T>
+     * @return
+     */
+    @NonNull
+    public static <T> TypeSpec<SearchAction<T>> createSearchActionTypeSpec(
+            @NonNull TypeSpec<T> nestedTypeSpec) {
+        return TypeSpecBuilder.<SearchAction<T>, SearchAction.Builder<T>>newBuilder(
+                        "SearchAction", SearchAction::newBuilder)
+                .bindStringField("query", SearchAction<T>::getQuery,
+                        SearchAction.Builder<T>::setQuery)
+                .bindSpecField(
+                        "object",
+                        SearchAction<T>::getObject,
+                        SearchAction.Builder<T>::setObject,
+                        nestedTypeSpec)
+                .build();
+    }
+
+    /** Converts a ParamValue to a single Participant object. */
+    @NonNull
+    public static Participant toParticipant(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (FIELD_NAME_TYPE_PERSON.equals(getStructType(paramValue.getStructValue()))) {
+            return new Participant(PERSON_TYPE_SPEC.fromStruct(paramValue.getStructValue()));
+        }
+        throw new StructConversionException("The type is not expected.");
+    }
+
+    /** Converts a ParamValue to a single Recipient object. */
+    @NonNull
+    public static Recipient toRecipient(@NonNull ParamValue paramValue)
+            throws StructConversionException {
+        if (FIELD_NAME_TYPE_PERSON.equals(getStructType(paramValue.getStructValue()))) {
+            return new Recipient(PERSON_TYPE_SPEC.fromStruct(paramValue.getStructValue()));
+        }
+        throw new StructConversionException("The type is not expected.");
+    }
+
+    /** Given some class with a corresponding TypeSpec, create a SearchActionConverter instance. */
+    @NonNull
+    public static <T> SearchActionConverter<T> createSearchActionConverter(
+            @NonNull TypeSpec<T> nestedTypeSpec) {
+        final TypeSpec<SearchAction<T>> typeSpec = createSearchActionTypeSpec(nestedTypeSpec);
+        return (paramValue) -> typeSpec.fromStruct(paramValue.getStructValue());
+    }
+
+    /** Converts a string to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull String value) {
+        return ParamValue.newBuilder().setStringValue(value).build();
+    }
+
+    /** Converts an EntityValue to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull EntityValue value) {
+        ParamValue.Builder builder = ParamValue.newBuilder().setStringValue(value.getValue());
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts a ItemList to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull ItemList value) {
+        ParamValue.Builder builder =
+                ParamValue.newBuilder().setStructValue(ITEM_LIST_TYPE_SPEC.toStruct(value));
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts a ListItem to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull ListItem value) {
+        ParamValue.Builder builder =
+                ParamValue.newBuilder().setStructValue(LIST_ITEM_TYPE_SPEC.toStruct(value));
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts an Order to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Order value) {
+        ParamValue.Builder builder =
+                ParamValue.newBuilder().setStructValue(ORDER_TYPE_SPEC.toStruct(value));
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts an Alarm to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Alarm value) {
+        ParamValue.Builder builder =
+                ParamValue.newBuilder().setStructValue(ALARM_TYPE_SPEC.toStruct(value));
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts an SafetyCheck to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull SafetyCheck value) {
+        ParamValue.Builder builder =
+                ParamValue.newBuilder().setStructValue(SAFETY_CHECK_TYPE_SPEC.toStruct(value));
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts a Participant to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Participant value) {
+        ParticipantTypeSpec typeSpec = new ParticipantTypeSpec();
+        ParamValue.Builder builder = ParamValue.newBuilder().setStructValue(
+                typeSpec.toStruct(value));
+        typeSpec.getId(value).ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts a Recipient to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Recipient value) {
+        RecipientTypeSpec typeSpec = new RecipientTypeSpec();
+        ParamValue.Builder builder = ParamValue.newBuilder().setStructValue(
+                typeSpec.toStruct(value));
+        typeSpec.getId(value).ifPresent(builder::setIdentifier);
+        return builder.build();
+    }
+
+    /** Converts a Call to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Call value) {
+        ParamValue.Builder builder = ParamValue.newBuilder();
+        Map<String, Value> fieldsMap = new HashMap<>();
+        fieldsMap.put(FIELD_NAME_TYPE,
+                Value.newBuilder().setStringValue(FIELD_NAME_TYPE_CALL).build());
+        if (value.getCallFormat().isPresent()) {
+            fieldsMap.put(
+                    FIELD_NAME_CALL_FORMAT,
+                    Value.newBuilder().setStringValue(
+                            value.getCallFormat().get().toString()).build());
+        }
+        ListValue.Builder participantListBuilder = ListValue.newBuilder();
+        for (Participant participant : value.getParticipantList()) {
+            if (participant.asPerson().isPresent()) {
+                participantListBuilder.addValues(
+                        Value.newBuilder()
+                                .setStructValue(
+                                        PERSON_TYPE_SPEC.toStruct(participant.asPerson().get()))
+                                .build());
+            }
+        }
+        if (!participantListBuilder.getValuesList().isEmpty()) {
+            fieldsMap.put(
+                    FIELD_NAME_PARTICIPANT,
+                    Value.newBuilder().setListValue(participantListBuilder.build()).build());
+        }
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.setStructValue(Struct.newBuilder().putAllFields(fieldsMap).build()).build();
+    }
+
+    /** Converts a Message to a ParamValue. */
+    @NonNull
+    public static ParamValue toParamValue(@NonNull Message value) {
+        ParamValue.Builder builder = ParamValue.newBuilder();
+        Map<String, Value> fieldsMap = new HashMap<>();
+        fieldsMap.put(
+                FIELD_NAME_TYPE,
+                Value.newBuilder().setStringValue(FIELD_NAME_TYPE_MESSAGE).build());
+        if (value.getMessageText().isPresent()) {
+            fieldsMap.put(
+                    FIELD_NAME_TEXT,
+                    Value.newBuilder().setStringValue(value.getMessageText().get()).build());
+        }
+        ListValue.Builder recipientListBuilder = ListValue.newBuilder();
+        for (Recipient recipient : value.getRecipientList()) {
+            if (recipient.asPerson().isPresent()) {
+                recipientListBuilder.addValues(
+                        Value.newBuilder()
+                                .setStructValue(
+                                        PERSON_TYPE_SPEC.toStruct(recipient.asPerson().get()))
+                                .build());
+            }
+        }
+        if (!recipientListBuilder.getValuesList().isEmpty()) {
+            fieldsMap.put(
+                    FIELD_NAME_RECIPIENT,
+                    Value.newBuilder().setListValue(recipientListBuilder.build()).build());
+        }
+        value.getId().ifPresent(builder::setIdentifier);
+        return builder.setStructValue(Struct.newBuilder().putAllFields(fieldsMap).build()).build();
+    }
+
+    private 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()) {
+            throw new StructConversionException("There is no type specified.");
+        }
+        return fieldsMap.get(FIELD_NAME_TYPE).getStringValue();
+    }
+
+    /** {@link TypeSpec} for {@link Participant}. */
+    public static class ParticipantTypeSpec implements TypeSpec<Participant> {
+        @Override
+        @NonNull
+        public Struct toStruct(@NonNull Participant object) {
+            if (object.asPerson().isPresent()) {
+                return PERSON_TYPE_SPEC.toStruct(object.asPerson().get());
+            }
+            return Struct.getDefaultInstance();
+        }
+
+        @Override
+        @NonNull
+        public Participant fromStruct(@NonNull Struct struct) throws StructConversionException {
+            if (FIELD_NAME_TYPE_PERSON.equals(getStructType(struct))) {
+                return new Participant(PERSON_TYPE_SPEC.fromStruct(struct));
+            }
+            throw new StructConversionException(
+                    String.format(
+                            "Unexpected type, expected type is %s while actual type is %s",
+                            FIELD_NAME_TYPE_PERSON, getStructType(struct)));
+        }
+
+        /**
+         * Retrieves identifier from the object within union value.
+         *
+         * @param object
+         * @return
+         */
+        @NonNull
+        public Optional<String> getId(@NonNull Participant object) {
+            return object.asPerson().isPresent() ? object.asPerson().get().getId()
+                    : Optional.empty();
+        }
+    }
+
+    /** {@link TypeSpec} for {@link Recipient}. */
+    public static class RecipientTypeSpec implements TypeSpec<Recipient> {
+        @NonNull
+        @Override
+        public Struct toStruct(@NonNull Recipient object) {
+            if (object.asPerson().isPresent()) {
+                return PERSON_TYPE_SPEC.toStruct(object.asPerson().get());
+            }
+            return Struct.getDefaultInstance();
+        }
+
+        @Override
+        @NonNull
+        public Recipient fromStruct(@NonNull Struct struct) throws StructConversionException {
+            if (FIELD_NAME_TYPE_PERSON.equals(getStructType(struct))) {
+                return new Recipient(PERSON_TYPE_SPEC.fromStruct(struct));
+            }
+            throw new StructConversionException(
+                    String.format(
+                            "Unexpected type, expected type is %s while actual type is %s",
+                            FIELD_NAME_TYPE_PERSON, getStructType(struct)));
+        }
+
+        /**
+         * Retrieves identifier from the object within union value.
+         *
+         * @param object
+         * @return
+         */
+        @NonNull
+        public Optional<String> getId(@NonNull Recipient object) {
+            return object.asPerson().isPresent() ? object.asPerson().get().getId()
+                    : Optional.empty();
+        }
+    }
+
+    /** {@link TypeSpec} for {@link Attendee}. */
+    public static class AttendeeTypeSpec implements TypeSpec<Attendee> {
+        @Override
+        @NonNull
+        public Struct toStruct(@NonNull Attendee object) {
+            if (object.asPerson().isPresent()) {
+                return PERSON_TYPE_SPEC.toStruct(object.asPerson().get());
+            }
+            return Struct.getDefaultInstance();
+        }
+
+        @NonNull
+        @Override
+        public Attendee fromStruct(@NonNull Struct struct) throws StructConversionException {
+            if (FIELD_NAME_TYPE_PERSON.equals(getStructType(struct))) {
+                return new Attendee(TypeConverters.PERSON_TYPE_SPEC.fromStruct(struct));
+            }
+            throw new StructConversionException(
+                    String.format(
+                            "Unexpected type, expected type is %s while actual type is %s",
+                            FIELD_NAME_TYPE_PERSON, getStructType(struct)));
+        }
+
+        /**
+         * Retrieves identifier from the object within union value.
+         *
+         * @param object
+         * @return
+         */
+        @NonNull
+        public Optional<String> getId(@NonNull Attendee object) {
+            return object.asPerson().isPresent() ? object.asPerson().get().getId()
+                    : Optional.empty();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpec.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpec.java
new file mode 100644
index 0000000..6781668
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpec.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+
+import com.google.protobuf.Struct;
+
+/**
+ * TypeSpec is used to convert between java objects in capabilities/values and Struct proto.
+ *
+ * @param <T>
+ */
+public interface TypeSpec<T> {
+
+    /** Converts a java object into a Struct proto. */
+    @NonNull
+    Struct toStruct(@NonNull T object);
+
+    /**
+     * Converts a Struct into java object.
+     *
+     * @throws StructConversionException if the Struct is malformed.
+     */
+    @NonNull
+    T fromStruct(@NonNull Struct struct) throws StructConversionException;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecBuilder.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecBuilder.java
new file mode 100644
index 0000000..38bab1f
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecBuilder.java
@@ -0,0 +1,308 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.values.Thing;
+
+import com.google.protobuf.ListValue;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import java.time.Duration;
+import java.time.OffsetDateTime;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeParseException;
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/** Builder for {@link TypeSpec}. */
+final class TypeSpecBuilder<T, BuilderT extends BuilderOf<T>> {
+    private final List<FieldBinding<T, BuilderT>> mBindings = new ArrayList<>();
+    private final Supplier<BuilderT> mBuilderSupplier;
+    private CheckedInterfaces.Consumer<Struct> mStructValidator;
+
+    private TypeSpecBuilder(Supplier<BuilderT> builderSupplier) {
+        this.mBuilderSupplier = builderSupplier;
+    }
+
+    private static Value getStringValue(String string) {
+        return Value.newBuilder().setStringValue(string).build();
+    }
+
+    private static Value getStructValue(Struct struct) {
+        return Value.newBuilder().setStructValue(struct).build();
+    }
+
+    private static Value getListValue(List<Value> values) {
+        return Value.newBuilder()
+                .setListValue(ListValue.newBuilder().addAllValues(values).build())
+                .build();
+    }
+
+    /**
+     * Returns a Value in a Struct, IllegalArgumentException is caught and wrapped in
+     * StructConversionException.
+     *
+     * @param struct the Struct to get values from.
+     * @param key    the String key of the field to retrieve.
+     */
+    private static Value getFieldFromStruct(Struct struct, String key)
+            throws StructConversionException {
+        try {
+            return struct.getFieldsOrThrow(key);
+        } catch (IllegalArgumentException e) {
+            throw new StructConversionException(String.format("%s does not exist in Struct", key),
+                    e);
+        }
+    }
+
+    static <T, BuilderT extends BuilderOf<T>> TypeSpecBuilder<T, BuilderT> newBuilder(
+            String typeName, Supplier<BuilderT> builderSupplier) {
+        return new TypeSpecBuilder<>(builderSupplier)
+                .bindStringField("@type", (unused) -> Optional.of(typeName), (builder, val) -> {
+                })
+                .setStructValidator(
+                        struct -> {
+                            if (!getFieldFromStruct(struct, "@type").getStringValue().equals(
+                                    typeName)) {
+                                throw new StructConversionException(
+                                        String.format("Struct @type field must be equal to %s.",
+                                                typeName));
+                            }
+                        });
+    }
+
+    /**
+     * Creates a new TypeSpecBuilder for a child class of Thing.
+     *
+     * <p>Comes with bindings for Thing fields.
+     */
+    static <T extends Thing, BuilderT extends Thing.Builder<BuilderT> & BuilderOf<T>>
+            TypeSpecBuilder<T, BuilderT> newBuilderForThing(
+                    String typeName, Supplier<BuilderT> builderSupplier) {
+        return newBuilder(typeName, builderSupplier)
+                .bindStringField("identifier", T::getId, BuilderT::setId)
+                .bindStringField("name", T::getName, BuilderT::setName);
+    }
+
+    private TypeSpecBuilder<T, BuilderT> setStructValidator(
+            CheckedInterfaces.Consumer<Struct> structValidator) {
+        this.mStructValidator = structValidator;
+        return this;
+    }
+
+    private TypeSpecBuilder<T, BuilderT> bindFieldInternal(
+            String name,
+            Function<T, Optional<Value>> valueGetter,
+            CheckedInterfaces.BiConsumer<BuilderT, Optional<Value>> valueSetter) {
+        mBindings.add(FieldBinding.create(name, valueGetter, valueSetter));
+        return this;
+    }
+
+    private <V> TypeSpecBuilder<T, BuilderT> bindRepeatedFieldInternal(
+            String name,
+            Function<T, List<V>> valueGetter,
+            BiConsumer<BuilderT, List<V>> valueSetter,
+            Function<V, Optional<Value>> toValue,
+            CheckedInterfaces.Function<Value, V> fromValue) {
+        return bindFieldInternal(
+                name,
+                /** valueGetter= */
+                object -> {
+                    if (valueGetter.apply(object).isEmpty()) {
+                        return Optional.empty();
+                    }
+                    return Optional.of(
+                            getListValue(
+                                    valueGetter.apply(object).stream()
+                                            .map(toValue)
+                                            .filter(Optional::isPresent)
+                                            .map(Optional::get)
+                                            .collect(toImmutableList())));
+                },
+                /** valueSetter= */
+                (builder, repeatedValue) -> {
+                    List<Value> values =
+                            repeatedValue
+                                    .map(Value::getListValue)
+                                    .map(ListValue::getValuesList)
+                                    .orElseGet(Collections::emptyList);
+                    List<V> convertedValues = new ArrayList<>();
+                    for (Value value : values) {
+                        convertedValues.add(fromValue.apply(value));
+                    }
+                    valueSetter.accept(builder, Collections.unmodifiableList(convertedValues));
+                });
+    }
+
+    /** binds a String field to read from / write to Struct */
+    TypeSpecBuilder<T, BuilderT> bindStringField(
+            String name,
+            Function<T, Optional<String>> stringGetter,
+            BiConsumer<BuilderT, String> stringSetter) {
+        return bindFieldInternal(
+                name,
+                (object) -> stringGetter.apply(object).map(TypeSpecBuilder::getStringValue),
+                (builder, value) ->
+                        value
+                                .map(Value::getStringValue)
+                                .ifPresent(
+                                        stringValue -> stringSetter.accept(builder, stringValue)));
+    }
+
+    /**
+     * Binds an enum field to read from / write to Struct. The enum will be represented as a string
+     * when converted to a Struct proto.
+     */
+    <E extends Enum<E>> TypeSpecBuilder<T, BuilderT> bindEnumField(
+            String name,
+            Function<T, Optional<E>> valueGetter,
+            BiConsumer<BuilderT, E> valueSetter,
+            Class<E> enumClass) {
+        return bindFieldInternal(
+                name,
+                (object) ->
+                        valueGetter.apply(object).map(Enum::toString).map(
+                                TypeSpecBuilder::getStringValue),
+                (builder, value) -> {
+                    if (value.isPresent()) {
+                        String stringValue = value.get().getStringValue();
+                        E[] enumValues = enumClass.getEnumConstants();
+                        if (enumValues != null) {
+                            for (E enumValue : enumValues) {
+                                if (enumValue.toString().equals(stringValue)) {
+                                    valueSetter.accept(builder, enumValue);
+                                    return;
+                                }
+                            }
+                        }
+                        throw new StructConversionException(
+                                String.format("Failed to get enum from string %s", stringValue));
+                    }
+                });
+    }
+
+    /**
+     * Binds a Duration field to read from / write to Struct. The Duration will be represented as an
+     * ISO 8601 string when converted to a Struct proto.
+     */
+    TypeSpecBuilder<T, BuilderT> bindDurationField(
+            String name,
+            Function<T, Optional<Duration>> valueGetter,
+            BiConsumer<BuilderT, Duration> valueSetter) {
+        return bindFieldInternal(
+                name,
+                (object) ->
+                        valueGetter.apply(object).map(Duration::toString).map(
+                                TypeSpecBuilder::getStringValue),
+                (builder, value) -> {
+                    if (value.isPresent()) {
+                        try {
+                            valueSetter.accept(builder,
+                                    Duration.parse(value.get().getStringValue()));
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to Duration", e);
+                        }
+                    }
+                });
+    }
+
+    /**
+     * Binds a ZonedDateTime field to read from / write to Struct. The ZonedDateTime will be
+     * represented as an ISO 8601 string when converted to a Struct proto.
+     */
+    TypeSpecBuilder<T, BuilderT> bindZonedDateTimeField(
+            String name,
+            Function<T, Optional<ZonedDateTime>> valueGetter,
+            BiConsumer<BuilderT, ZonedDateTime> valueSetter) {
+        return bindFieldInternal(
+                name,
+                (object) ->
+                        valueGetter
+                                .apply(object)
+                                .map(ZonedDateTime::toOffsetDateTime)
+                                .map(OffsetDateTime::toString)
+                                .map(TypeSpecBuilder::getStringValue),
+                (builder, value) -> {
+                    if (value.isPresent()) {
+                        try {
+                            valueSetter.accept(builder,
+                                    ZonedDateTime.parse(value.get().getStringValue()));
+                        } catch (DateTimeParseException e) {
+                            throw new StructConversionException(
+                                    "Failed to parse ISO 8601 string to ZonedDateTime", e);
+                        }
+                    }
+                });
+    }
+
+    /** Binds a spec field to read from / write to Struct. */
+    <V> TypeSpecBuilder<T, BuilderT> bindSpecField(
+            String name,
+            Function<T, Optional<V>> valueGetter,
+            BiConsumer<BuilderT, V> valueSetter,
+            TypeSpec<V> spec) {
+        return bindFieldInternal(
+                name,
+                (object) ->
+                        valueGetter
+                                .apply(object)
+                                .map(
+                                        Function
+                                                .identity()) // Static analyzer incorrectly
+                                // throws error stating that the
+                                // input to toStruct is nullable. This is a workaround to avoid
+                                // the error from the analyzer.
+                                .map(spec::toStruct)
+                                .map(TypeSpecBuilder::getStructValue),
+                (builder, value) -> {
+                    if (value.isPresent()) {
+                        valueSetter.accept(builder, spec.fromStruct(value.get().getStructValue()));
+                    }
+                });
+    }
+
+    /** binds a repeated spec field to read from / write to Struct. */
+    <V> TypeSpecBuilder<T, BuilderT> bindRepeatedSpecField(
+            String name,
+            Function<T, List<V>> valueGetter,
+            BiConsumer<BuilderT, List<V>> valueSetter,
+            TypeSpec<V> spec) {
+        return bindRepeatedFieldInternal(
+                name,
+                valueGetter,
+                valueSetter,
+                (element) ->
+                        Optional.ofNullable(element).map(
+                                value -> getStructValue(spec.toStruct(value))),
+                (value) -> spec.fromStruct(value.getStructValue()));
+    }
+
+    TypeSpec<T> build() {
+        return new TypeSpecImpl<>(mBindings, mBuilderSupplier, Optional.ofNullable(mStructValidator));
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImpl.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImpl.java
new file mode 100644
index 0000000..13d36ed
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImpl.java
@@ -0,0 +1,87 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.converters;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Supplier;
+
+/** TypeSpecImpl is used to convert between java objects in capabilities/values and Struct proto. */
+final class TypeSpecImpl<T, BuilderT extends BuilderOf<T>> implements TypeSpec<T> {
+
+    /** The list of FieldBinding objects. */
+    final List<FieldBinding<T, BuilderT>> mBindings;
+
+    /** Validates the Struct during conversion to java object. */
+    final Optional<CheckedInterfaces.Consumer<Struct>> mStructValidator;
+
+    /** Supplies BuilderT instances. */
+    final Supplier<BuilderT> mBuilderSupplier;
+
+    TypeSpecImpl(
+            List<FieldBinding<T, BuilderT>> bindings,
+            Supplier<BuilderT> builderSupplier,
+            Optional<CheckedInterfaces.Consumer<Struct>> structValidator) {
+        this.mBindings = Collections.unmodifiableList(bindings);
+        this.mBuilderSupplier = builderSupplier;
+        this.mStructValidator = structValidator;
+    }
+
+    /** Converts a java object into a Struct proto using List of FieldBinding. */
+    @NonNull
+    @Override
+    public Struct toStruct(@NonNull T object) {
+        Struct.Builder builder = Struct.newBuilder();
+        for (FieldBinding<T, BuilderT> binding : mBindings) {
+            binding
+                    .valueGetter()
+                    .apply(object)
+                    .ifPresent(value -> builder.putFields(binding.name(), value));
+        }
+        return builder.build();
+    }
+
+    /**
+     * Converts a Struct back into java object.
+     *
+     * @throws StructConversionException if the Struct is malformed.
+     */
+    @NonNull
+    @Override
+    public T fromStruct(@NonNull Struct struct) throws StructConversionException {
+        if (mStructValidator.isPresent()) {
+            mStructValidator.get().accept(struct);
+        }
+
+        BuilderT builder = mBuilderSupplier.get();
+        Map<String, Value> fieldsMap = struct.getFieldsMap();
+        for (FieldBinding<T, BuilderT> binding : mBindings) {
+            Optional<Value> value = Optional.ofNullable(fieldsMap.get(binding.name()));
+            binding.valueSetter().accept(builder, value);
+        }
+        return builder.build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/StructConversionException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/StructConversionException.java
new file mode 100644
index 0000000..be13634
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/exceptions/StructConversionException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.exceptions;
+
+import androidx.annotation.Nullable;
+
+/** Represents exceptions that happen during object conversion to/from Struct proto. */
+public class StructConversionException extends Exception {
+    public StructConversionException(@Nullable String message) {
+        super(message);
+    }
+
+    public StructConversionException(@Nullable String message, @Nullable Throwable cause) {
+        super(message, cause);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImpl.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImpl.java
new file mode 100644
index 0000000..b4f281b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImpl.java
@@ -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.appactions.interaction.capabilities.core.impl.spec;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableMap;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ActionExecutor;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilityInternal;
+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.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger;
+import androidx.appactions.interaction.capabilities.core.impl.utils.LoggerInternal;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentValue;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+
+/**
+ * The implementation of the {@link ActionCapability} interface.
+ *
+ * @param <PropertyT>
+ * @param <ArgumentT>
+ * @param <OutputT>
+ */
+public final class ActionCapabilityImpl<PropertyT, ArgumentT, OutputT>
+        implements ActionCapabilityInternal {
+
+    private static final String LOG_TAG = "ActionCapability";
+    private final ActionSpec<PropertyT, ArgumentT, OutputT> mActionSpec;
+    private final Optional<String> mIdentifier;
+    private final PropertyT mProperty;
+    private final ActionExecutor<ArgumentT, OutputT> mActionExecutor;
+
+    public ActionCapabilityImpl(
+            @NonNull ActionSpec<PropertyT, ArgumentT, OutputT> actionSpec,
+            @NonNull Optional<String> identifier,
+            @NonNull PropertyT property,
+            @NonNull ActionExecutor<ArgumentT, OutputT> actionExecutor) {
+        this.mActionSpec = actionSpec;
+        this.mIdentifier = identifier;
+        this.mProperty = property;
+        this.mActionExecutor = actionExecutor;
+    }
+
+    @NonNull
+    @Override
+    public Optional<String> getId() {
+        return mIdentifier;
+    }
+
+    @NonNull
+    @Override
+    public AppAction getAppAction() {
+        AppAction appAction = mActionSpec.convertPropertyToProto(mProperty);
+        if (mIdentifier.isPresent()) {
+            appAction = appAction.toBuilder().setIdentifier(mIdentifier.get()).build();
+        }
+        return appAction;
+    }
+
+    @Override
+    public void execute(
+            @NonNull ArgumentsWrapper argumentsWrapper, @NonNull CallbackInternal callback) {
+        // Filter out the task parts of ArgumentsWrapper. Send the raw arguments for one-shot
+        // capabilities.
+        Map<String, List<ParamValue>> args =
+                argumentsWrapper.paramValues().entrySet().stream()
+                        .collect(
+                                toImmutableMap(
+                                        Map.Entry::getKey,
+                                        e ->
+                                                e.getValue().stream()
+                                                        .filter(FulfillmentValue::hasValue)
+                                                        .map(FulfillmentValue::getValue)
+                                                        .collect(toImmutableList())));
+        try {
+            mActionExecutor.execute(mActionSpec.buildArgument(args), convertCallback(callback));
+        } catch (StructConversionException e) {
+            if (e.getMessage() != null) {
+                LoggerInternal.log(CapabilityLogger.LogLevel.ERROR, LOG_TAG, e.getMessage());
+            }
+            callback.onError(ErrorStatusInternal.STRUCT_CONVERSION_FAILURE);
+        }
+    }
+
+    /** Converts {@link CallbackInternal} to {@link ActionExecutor.ActionCallback}. */
+    private ActionExecutor.ActionCallback<OutputT> convertCallback(CallbackInternal callback) {
+        return new ActionExecutor.ActionCallback<OutputT>() {
+            @Override
+            public void onSuccess(ExecutionResult<OutputT> executionResult) {
+                callback.onSuccess(convertToFulfillmentResponse(executionResult));
+            }
+
+            @Override
+            public void onError(ActionExecutor.ErrorStatus errorStatus) {
+                switch (errorStatus) {
+                    case CANCELLED:
+                        callback.onError(ErrorStatusInternal.CANCELLED);
+                        break;
+                    case TIMEOUT:
+                        callback.onError(ErrorStatusInternal.TIMEOUT);
+                }
+            }
+        };
+    }
+
+    /** Converts typed {@link ExecutionResult} to {@link FulfillmentResponse} proto. */
+    FulfillmentResponse convertToFulfillmentResponse(ExecutionResult<OutputT> executionResult) {
+        FulfillmentResponse.Builder fulfillmentResponseBuilder =
+                FulfillmentResponse.newBuilder().setStartDictation(
+                        executionResult.getStartDictation());
+        OutputT output = executionResult.getOutput();
+        if (output != null && !(output instanceof Void)) {
+            fulfillmentResponseBuilder.setExecutionOutput(mActionSpec.convertOutputToProto(output));
+        }
+        return fulfillmentResponseBuilder.build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.java
new file mode 100644
index 0000000..182cef8
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpec.java
@@ -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.appactions.interaction.capabilities.core.impl.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * A specification for an action, describing it from the app's point of view.
+ *
+ * @param <PropertyT> typed description of action's characteristics.
+ * @param <ArgumentT> typed representation of action's arguments.
+ * @param <OutputT>   typed action's execution output.
+ */
+public interface ActionSpec<PropertyT, ArgumentT, OutputT> {
+
+    /** Converts the property to the {@code AppAction} proto. */
+    @NonNull
+    AppAction convertPropertyToProto(PropertyT property);
+
+    /** Builds this action's arguments from an ArgumentsWrapper instance. */
+    @NonNull
+    ArgumentT buildArgument(@NonNull Map<String, List<ParamValue>> args)
+            throws StructConversionException;
+
+    /** Converts the output to the {@code StructuredOutput} proto. */
+    @NonNull
+    StructuredOutput convertOutputToProto(OutputT output);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.java
new file mode 100644
index 0000000..209d7ff
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecBuilder.java
@@ -0,0 +1,611 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.spec;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.PropertyConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.SlotTypeConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ParamBinding.ArgumentSetter;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.properties.EnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.IntegerProperty;
+import androidx.appactions.interaction.capabilities.core.properties.SimpleProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringOrEnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+import androidx.appactions.interaction.capabilities.core.values.StringOrEnumValue;
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.EnumSet;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.BiConsumer;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * A builder for the {@code ActionSpec}.
+ *
+ * @param <PropertyT>
+ * @param <ArgumentT>
+ * @param <ArgumentBuilderT>
+ * @param <OutputT>
+ */
+public final class ActionSpecBuilder<
+        PropertyT, ArgumentT, ArgumentBuilderT extends BuilderOf<ArgumentT>, OutputT> {
+
+    private final String mCapabilityName;
+    private final Supplier<ArgumentBuilderT> mArgumentBuilderSupplier;
+    private final ArrayList<ParamBinding<PropertyT, ArgumentT, ArgumentBuilderT>>
+            mParamBindingList = new ArrayList<>();
+    private final Map<String, Function<OutputT, List<ParamValue>>> mOutputBindings =
+            new HashMap<>();
+
+    private ActionSpecBuilder(
+            String capabilityName, Supplier<ArgumentBuilderT> argumentBuilderSupplier) {
+        this.mCapabilityName = capabilityName;
+        this.mArgumentBuilderSupplier = argumentBuilderSupplier;
+    }
+
+    /**
+     * Creates an empty {@code ActionSpecBuilder} with the given capability name. ArgumentT is set
+     * to Object as a placeholder, which must be replaced by calling setArgument.
+     */
+    @NonNull
+    public static ActionSpecBuilder<Void, Object, BuilderOf<Object>, Void> ofCapabilityNamed(
+            @NonNull String capabilityName) {
+        return new ActionSpecBuilder<>(capabilityName, () -> Object::new);
+    }
+
+    /** Sets the property type and returns a new {@code ActionSpecBuilder}. */
+    @NonNull
+    public <NewPropertyT>
+            ActionSpecBuilder<NewPropertyT, ArgumentT, ArgumentBuilderT, OutputT> setDescriptor(
+                    @NonNull Class<NewPropertyT> unused) {
+        return new ActionSpecBuilder<>(this.mCapabilityName, this.mArgumentBuilderSupplier);
+    }
+
+    /** Sets the argument type and its builder and returns a new {@code ActionSpecBuilder}. */
+    @NonNull
+    public <NewArgumentT, NewArgumentBuilderT extends BuilderOf<NewArgumentT>>
+            ActionSpecBuilder<PropertyT, NewArgumentT, NewArgumentBuilderT, OutputT> setArgument(
+                    @NonNull Class<NewArgumentT> unused,
+                    @NonNull Supplier<NewArgumentBuilderT> argumentBuilderSupplier) {
+        return new ActionSpecBuilder<>(this.mCapabilityName, argumentBuilderSupplier);
+    }
+
+    @NonNull
+    public <NewOutputT>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, NewOutputT> setOutput(
+                    @NonNull Class<NewOutputT> unused) {
+        return new ActionSpecBuilder<>(this.mCapabilityName, this.mArgumentBuilderSupplier);
+    }
+
+    /**
+     * Binds the parameter name, getter and setter.
+     *
+     * @param paramName the name of this action' parameter.
+     * @param paramGetter a getter of the param-specific info from the property.
+     * @param argumentSetter a setter to the argument with the input from {@code ParamValue}.
+     * @return the builder itself.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindParameter(
+            @NonNull String paramName,
+            @NonNull Function<? super PropertyT, Optional<IntentParameter>> paramGetter,
+            @NonNull ArgumentSetter<ArgumentBuilderT> argumentSetter) {
+        mParamBindingList.add(ParamBinding.create(paramName, paramGetter, argumentSetter));
+        return this;
+    }
+
+    /**
+     * Binds the parameter name, getter, and setter for a {@link EntityProperty}.
+     *
+     * <p>This parameter is required for any capability built from the generated {@link ActionSpec}.
+     *
+     * @param paramName the name of this action' parameter.
+     * @param propertyGetter a getter of the EntityProperty from the property, which must be able to
+     *     fetch a non-null {@code EntityProperty} from {@code PropertyT}.
+     * @param paramConsumer a setter to set the string value in the argument builder.
+     * @return the builder itself.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindRequiredEntityParameter(
+                    @NonNull String paramName,
+                    @NonNull Function<? super PropertyT, EntityProperty> propertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, EntityValue> paramConsumer) {
+        return bindEntityParameter(
+                paramName,
+                property ->
+                        Optional.of(
+                                PropertyConverter.getIntentParameter(
+                                        paramName, propertyGetter.apply(property))),
+                paramConsumer);
+    }
+
+    /**
+     * Binds the parameter name, getter, and setter for a {@link EntityProperty}.
+     *
+     * <p>This parameter is optional for any capability built from the generated {@link ActionSpec}.
+     * If the Property Optional is not set, this parameter will not exist in the parameter
+     * definition of the capability.
+     *
+     * @param paramName the name of this action' parameter.
+     * @param optionalPropertyGetter an optional getter of the EntityProperty from the property,
+     *     which may be able to fetch a non-null {@code EntityProperty} from {@code PropertyT}, or
+     *     get {@link Optional#empty}.
+     * @param paramConsumer a setter to set the string value in the argument builder.
+     * @return the builder itself.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindOptionalEntityParameter(
+                    @NonNull String paramName,
+                    @NonNull
+                            Function<? super PropertyT, Optional<EntityProperty>>
+                                    optionalPropertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, EntityValue> paramConsumer) {
+        return bindEntityParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(p -> PropertyConverter.getIntentParameter(paramName, p)),
+                paramConsumer);
+    }
+
+    /**
+     * This is similar to {@link ActionSpectBuilder#bindOptionalEntityParameter} but for setting a
+     * list of entities instead.
+     *
+     * <p>This parameter is optional for any capability built from the generated {@link ActionSpec}.
+     * If the Property Optional is not set, this parameter will not exist in the parameter
+     * definition of the capability.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindRepeatedEntityParameter(
+                    @NonNull String paramName,
+                    @NonNull
+                            Function<? super PropertyT, Optional<EntityProperty>>
+                                    optionalPropertyGetter,
+                    @NonNull
+                            BiConsumer<? super ArgumentBuilderT, List<EntityValue>> paramConsumer) {
+        return bindParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(p -> PropertyConverter.getIntentParameter(paramName, p)),
+                (argBuilder, paramList) ->
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofRepeated(TypeConverters::toEntityValue)
+                                        .convert(paramList)));
+    }
+
+    private ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindEntityParameter(
+            String paramName,
+            Function<? super PropertyT, Optional<IntentParameter>> propertyGetter,
+            BiConsumer<? super ArgumentBuilderT, EntityValue> paramConsumer) {
+        return bindParameter(
+                paramName,
+                propertyGetter,
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofSingular(TypeConverters::toEntityValue)
+                                        .convert(paramList));
+                    }
+                });
+    }
+
+    @NonNull
+    public <T>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindStructParameter(
+                    @NonNull String paramName,
+                    @NonNull
+                            Function<? super PropertyT, Optional<SimpleProperty>>
+                                    optionalPropertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, T> paramConsumer,
+                    @NonNull ParamValueConverter<T> paramValueConverter) {
+        return bindParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(p -> PropertyConverter.getIntentParameter(paramName, p)),
+                (argBuilder, paramList) ->
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofSingular(paramValueConverter)
+                                        .convert(paramList)));
+    }
+
+    @NonNull
+    public <T>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+                    bindRepeatedStructParameter(
+                            @NonNull String paramName,
+                            @NonNull
+                                    Function<? super PropertyT, Optional<SimpleProperty>>
+                                            optionalPropertyGetter,
+                            @NonNull BiConsumer<? super ArgumentBuilderT, List<T>> paramConsumer,
+                            @NonNull ParamValueConverter<T> paramValueConverter) {
+        return bindParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(p -> PropertyConverter.getIntentParameter(paramName, p)),
+                (argBuilder, paramList) ->
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofRepeated(paramValueConverter)
+                                        .convert(paramList)));
+    }
+
+    /**
+     * Binds an optional string parameter.
+     *
+     * @param paramName the BII slot name of this parameter.
+     * @param propertyGetter a function that returns a {@code Optional<StringProperty>} given a
+     *     {@code PropertyT} instance
+     * @param paramConsumer a function that accepts a String into the argument builder.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindOptionalStringParameter(
+                    @NonNull String paramName,
+                    @NonNull Function<? super PropertyT, Optional<StringProperty>> propertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, String> paramConsumer) {
+        return bindParameter(
+                paramName,
+                property ->
+                        propertyGetter
+                                .apply(property)
+                                .map(
+                                        stringProperty ->
+                                                PropertyConverter.getIntentParameter(
+                                                        paramName, stringProperty)),
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofSingular(TypeConverters::toStringValue)
+                                        .convert(paramList));
+                    }
+                });
+    }
+
+    /**
+     * Binds an required string parameter.
+     *
+     * @param paramName the BII slot name of this parameter.
+     * @param propertyGetter a function that returns a {@code StringProperty} given a {@code
+     *     PropertyT} instance
+     * @param paramConsumer a function that accepts a String into the argument builder.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindRequiredStringParameter(
+                    @NonNull String paramName,
+                    @NonNull Function<? super PropertyT, StringProperty> propertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, String> paramConsumer) {
+        return bindOptionalStringParameter(
+                paramName, property -> Optional.of(propertyGetter.apply(property)), paramConsumer);
+    }
+
+    /**
+     * Binds an repeated string parameter.
+     *
+     * @param paramName the BII slot name of this parameter.
+     * @param propertyGetter a function that returns a {@code Optional<StringProperty>} given a
+     *     {@code PropertyT} instance
+     * @param paramConsumer a function that accepts a {@code List<String>} into the argument
+     *     builder.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindRepeatedStringParameter(
+                    @NonNull String paramName,
+                    @NonNull Function<? super PropertyT, Optional<StringProperty>> propertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, List<String>> paramConsumer) {
+        return bindParameter(
+                paramName,
+                property ->
+                        propertyGetter
+                                .apply(property)
+                                .map(
+                                        stringProperty ->
+                                                PropertyConverter.getIntentParameter(
+                                                        paramName, stringProperty)),
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofRepeated(TypeConverters::toStringValue)
+                                        .convert(paramList));
+                    }
+                });
+    }
+
+    /**
+     * Binds the parameter name, getter, and setter for a {@link EnumProperty}.
+     *
+     * <p>This parameter is optional for any capability built from the generated {@link ActionSpec}.
+     * If the Property Optional is not set, this parameter will not exist in the parameter
+     * definition of the capability.
+     *
+     * @param paramName the name of this action parameter.
+     * @param enumType
+     * @param optionalPropertyGetter an optional getter of the EntityProperty from the property,
+     *     which may be able to fetch a non-null {@code EnumProperty} from {@code PropertyT}, or get
+     *     {@link Optional#empty}.
+     * @param paramConsumer a setter to set the enum value in the argument builder.
+     * @return the builder itself.
+     * @param <EnumT>
+     */
+    @NonNull
+    public <EnumT extends Enum<EnumT>>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+                    bindOptionalEnumParameter(
+                            @NonNull String paramName,
+                            @NonNull Class<EnumT> enumType,
+                            @NonNull
+                                    Function<? super PropertyT, Optional<EnumProperty<EnumT>>>
+                                            optionalPropertyGetter,
+                            @NonNull BiConsumer<? super ArgumentBuilderT, EnumT> paramConsumer) {
+        return bindEnumParameter(
+                paramName,
+                enumType,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(e -> PropertyConverter.getIntentParameter(paramName, e)),
+                paramConsumer);
+    }
+
+    private <EnumT extends Enum<EnumT>>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindEnumParameter(
+                    String paramName,
+                    Class<EnumT> enumType,
+                    Function<? super PropertyT, Optional<IntentParameter>> enumParamGetter,
+                    BiConsumer<? super ArgumentBuilderT, EnumT> paramConsumer) {
+        return bindParameter(
+                paramName,
+                enumParamGetter,
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        Optional<EnumT> enumValue =
+                                EnumSet.allOf(enumType).stream()
+                                        .filter(
+                                                element ->
+                                                        element.toString()
+                                                                .equals(
+                                                                        paramList
+                                                                                .get(0)
+                                                                                .getIdentifier()))
+                                        .findFirst();
+                        if (enumValue.isPresent()) {
+                            paramConsumer.accept(argBuilder, enumValue.get());
+                        }
+                    }
+                });
+    }
+
+    @NonNull
+    public <EnumT extends Enum<EnumT>>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+                    bindRequiredStringOrEnumParameter(
+                            @NonNull String paramName,
+                            @NonNull Class<EnumT> enumType,
+                            @NonNull
+                                    Function<? super PropertyT, StringOrEnumProperty<EnumT>>
+                                            propertyGetter,
+                            @NonNull
+                                    BiConsumer<? super ArgumentBuilderT, StringOrEnumValue<EnumT>>
+                                            paramConsumer) {
+        return bindStringOrEnumParameter(
+                paramName,
+                enumType,
+                property ->
+                        Optional.of(
+                                PropertyConverter.getIntentParameter(
+                                        paramName, propertyGetter.apply(property))),
+                paramConsumer);
+    }
+
+    private <EnumT extends Enum<EnumT>>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+                    bindStringOrEnumParameter(
+                            String paramName,
+                            Class<EnumT> enumType,
+                            Function<? super PropertyT, Optional<IntentParameter>> propertyGetter,
+                            BiConsumer<? super ArgumentBuilderT, StringOrEnumValue<EnumT>>
+                                    paramConsumer) {
+        return bindParameter(
+                paramName,
+                propertyGetter,
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        ParamValue param = paramList.get(0);
+                        if (param.hasIdentifier()) {
+                            Optional<EnumT> enumValue =
+                                    EnumSet.allOf(enumType).stream()
+                                            .filter(
+                                                    element ->
+                                                            element.toString()
+                                                                    .equals(param.getIdentifier()))
+                                            .findFirst();
+                            if (enumValue.isPresent()) {
+                                paramConsumer.accept(
+                                        argBuilder, StringOrEnumValue.ofEnumValue(enumValue.get()));
+                                return;
+                            }
+                        }
+                        paramConsumer.accept(
+                                argBuilder,
+                                StringOrEnumValue.ofStringValue(
+                                        SlotTypeConverter.ofSingular(TypeConverters::toStringValue)
+                                                .convert(paramList)));
+                    }
+                });
+    }
+
+    /**
+     * Binds the integer parameter name and setter for a {@link IntegerProperty}.
+     *
+     * <p>This parameter is optional for any capability built from the generated {@link ActionSpec}.
+     * If the Property Optional is not set, this parameter will not exist in the parameter
+     * definition of the capability.
+     *
+     * @param paramName the name of this action' parameter.
+     * @param optionalPropertyGetter
+     * @param paramConsumer a setter to set the int value in the argument builder.
+     * @return the builder itself.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindOptionalIntegerParameter(
+                    @NonNull String paramName,
+                    @NonNull
+                            Function<? super PropertyT, Optional<IntegerProperty>>
+                                    optionalPropertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, Integer> paramConsumer) {
+        return bindParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(e -> PropertyConverter.getIntentParameter(paramName, e)),
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofSingular(TypeConverters::toIntegerValue)
+                                        .convert(paramList));
+                    }
+                });
+    }
+
+    /**
+     * Binds a Boolean parameter.
+     *
+     * <p>This parameter is optional for any capability built from the generated {@link ActionSpec}.
+     * If the Property Optional is not set, this parameter will not exist in the parameter
+     * definition of the capability.
+     *
+     * @param paramName the name of this action' parameter.
+     * @param paramConsumer a setter to set the boolean value in the argument builder.
+     * @param optionalPropertyGetter an optional getter of the EntityProperty from the property,
+     *     which may be able to fetch a non-null {@code SimpleProperty} from {@code PropertyT}, or
+     *     get {@link Optional#empty}.
+     * @return the builder itself.
+     */
+    @NonNull
+    public ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT>
+            bindOptionalBooleanParameter(
+                    @NonNull String paramName,
+                    @NonNull
+                            Function<? super PropertyT, Optional<SimpleProperty>>
+                                    optionalPropertyGetter,
+                    @NonNull BiConsumer<? super ArgumentBuilderT, Boolean> paramConsumer) {
+        return bindParameter(
+                paramName,
+                property ->
+                        optionalPropertyGetter
+                                .apply(property)
+                                .map(e -> PropertyConverter.getIntentParameter(paramName, e)),
+                (argBuilder, paramList) -> {
+                    if (!paramList.isEmpty()) {
+                        paramConsumer.accept(
+                                argBuilder,
+                                SlotTypeConverter.ofSingular(TypeConverters::toBooleanValue)
+                                        .convert(paramList));
+                    }
+                });
+    }
+
+    /**
+     * Binds an optional output.
+     *
+     * @param name the BII output slot name of this parameter.
+     * @param outputGetter a getter of the output from the {@code OutputT} instance.
+     * @param converter a converter from an output object to a ParamValue.
+     */
+    @NonNull
+    @SuppressWarnings("JdkCollectors")
+    public <T>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindOptionalOutput(
+                    @NonNull String name,
+                    @NonNull Function<OutputT, Optional<T>> outputGetter,
+                    @NonNull Function<T, ParamValue> converter) {
+        mOutputBindings.put(
+                name,
+                output ->
+                        outputGetter.apply(output).stream()
+                                .map(converter)
+                                .collect(toImmutableList()));
+        return this;
+    }
+
+    /**
+     * Binds a repeated output.
+     *
+     * @param name the BII output slot name of this parameter.
+     * @param outputGetter a getter of the output from the {@code OutputT} instance.
+     * @param converter a converter from an output object to a ParamValue.
+     */
+    @NonNull
+    @SuppressWarnings("JdkCollectors")
+    public <T>
+            ActionSpecBuilder<PropertyT, ArgumentT, ArgumentBuilderT, OutputT> bindRepeatedOutput(
+                    @NonNull String name,
+                    @NonNull Function<OutputT, List<T>> outputGetter,
+                    @NonNull Function<T, ParamValue> converter) {
+        mOutputBindings.put(
+                name,
+                output ->
+                        outputGetter.apply(output).stream()
+                                .map(converter)
+                                .collect(toImmutableList()));
+        return this;
+    }
+
+    /** Builds an {@code ActionSpec} from this builder. */
+    @NonNull
+    public ActionSpec<PropertyT, ArgumentT, OutputT> build() {
+        return new ActionSpecImpl<>(
+                mCapabilityName,
+                mArgumentBuilderSupplier,
+                Collections.unmodifiableList(mParamBindingList),
+                mOutputBindings);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.java
new file mode 100644
index 0000000..de3aa6a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecImpl.java
@@ -0,0 +1,106 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.spec;
+
+import static androidx.appactions.interaction.capabilities.core.impl.utils.ImmutableCollectors.toImmutableList;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/** The implementation of {@code ActionSpec} interface. */
+final class ActionSpecImpl<
+        PropertyT, ArgumentT, ArgumentBuilderT extends BuilderOf<ArgumentT>, OutputT>
+        implements ActionSpec<PropertyT, ArgumentT, OutputT> {
+
+    private final String mCapabilityName;
+    private final Supplier<ArgumentBuilderT> mArgumentBuilderSupplier;
+    private final List<ParamBinding<PropertyT, ArgumentT, ArgumentBuilderT>> mParamBindingList;
+    private final Map<String, Function<OutputT, List<ParamValue>>> mOutputBindings;
+
+    ActionSpecImpl(
+            String capabilityName,
+            Supplier<ArgumentBuilderT> argumentBuilderSupplier,
+            List<ParamBinding<PropertyT, ArgumentT, ArgumentBuilderT>> paramBindingList,
+            Map<String, Function<OutputT, List<ParamValue>>> outputBindings) {
+        this.mCapabilityName = capabilityName;
+        this.mArgumentBuilderSupplier = argumentBuilderSupplier;
+        this.mParamBindingList = paramBindingList;
+        this.mOutputBindings = outputBindings;
+    }
+
+    @NonNull
+    @Override
+    public AppAction convertPropertyToProto(PropertyT property) {
+        return AppAction.newBuilder()
+                .setName(mCapabilityName)
+                .addAllParams(
+                        mParamBindingList.stream()
+                                .map(binding -> binding.paramGetter().apply(property))
+                                .filter(Optional::isPresent)
+                                .map(Optional::get)
+                                .collect(toImmutableList()))
+                .build();
+    }
+
+    @NonNull
+    @Override
+    public ArgumentT buildArgument(Map<String, List<ParamValue>> args)
+            throws StructConversionException {
+        ArgumentBuilderT argumentBuilder = mArgumentBuilderSupplier.get();
+        for (ParamBinding<PropertyT, ArgumentT, ArgumentBuilderT> binding : mParamBindingList) {
+            List<ParamValue> paramValues = args.get(binding.name());
+            if (paramValues == null) {
+                continue;
+            }
+            try {
+                binding.argumentSetter().setArgument(argumentBuilder, paramValues);
+            } catch (StructConversionException e) {
+                // Wrap the exception with a more meaningful error message.
+                throw new StructConversionException(
+                        String.format(
+                                "Failed to parse parameter '%s' from assistant because of "
+                                        + "failure: %s",
+                                binding.name(), e.getMessage()));
+            }
+        }
+        return argumentBuilder.build();
+    }
+
+    @Override
+    public StructuredOutput convertOutputToProto(OutputT output) {
+        StructuredOutput.Builder outputBuilder = StructuredOutput.newBuilder();
+        for (Map.Entry<String, Function<OutputT, List<ParamValue>>> entry :
+                mOutputBindings.entrySet()) {
+            outputBuilder.addOutputValues(
+                    StructuredOutput.OutputValue.newBuilder()
+                            .setName(entry.getKey())
+                            .addAllValues(entry.getValue().apply(output))
+                            .build());
+        }
+        return outputBuilder.build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.java
new file mode 100644
index 0000000..bdd5e08
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/spec/ParamBinding.java
@@ -0,0 +1,79 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+import java.util.Optional;
+import java.util.function.Function;
+
+/**
+ * A binding between a parameter and its Property converter / Argument setter.
+ *
+ * @param <PropertyT>
+ * @param <ArgumentT>
+ * @param <ArgumentBuilderT>
+ */
+@AutoValue
+public abstract class ParamBinding<
+        PropertyT, ArgumentT, ArgumentBuilderT extends BuilderOf<ArgumentT>> {
+
+    static <PropertyT, ArgumentT, ArgumentBuilderT extends BuilderOf<ArgumentT>>
+            ParamBinding<PropertyT, ArgumentT, ArgumentBuilderT> create(
+                    String name,
+                    Function<? super PropertyT, Optional<IntentParameter>> paramGetter,
+                    ArgumentSetter<ArgumentBuilderT> argumentSetter) {
+        return new AutoValue_ParamBinding<>(name, paramGetter, argumentSetter);
+    }
+
+    /** Returns the name of this param. */
+    @NonNull
+    public abstract String name();
+
+    /**
+     * Converts a {@code PropertyT} to an {@code IntentParameter} proto. The resulting proto is the
+     * format which we send the current params to Assistant (via. app actions context).
+     */
+    @NonNull
+    public abstract Function<? super PropertyT, Optional<IntentParameter>> paramGetter();
+
+    /**
+     * Populates the {@code ArgumentBuilderT} for this param with the {@code ParamValue} sent from
+     * Assistant in Fulfillment.
+     */
+    @NonNull
+    public abstract ArgumentSetter<ArgumentBuilderT> argumentSetter();
+
+    /**
+     * Givne a {@code List<ParamValue>}, convert it to user-visible type and set it into
+     * ArgumentBuilder.
+     *
+     * @param <ArgumentBuilderT>
+     */
+    @FunctionalInterface
+    public interface ArgumentSetter<ArgumentBuilderT> {
+        void setArgument(@NonNull ArgumentBuilderT builder, @NonNull List<ParamValue> paramValues)
+                throws StructConversionException;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CapabilityLogger.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CapabilityLogger.java
new file mode 100644
index 0000000..48ff8a4
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/CapabilityLogger.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.utils;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Define the logger interface to hide specific logger implementations from the capability library.
+ * This is needed because the capabilities library cannot depend on android deps directly.
+ */
+public interface CapabilityLogger {
+
+    void log(@NonNull LogLevel logLevel, @NonNull String logTag, @NonNull String message);
+
+    void log(
+            @NonNull LogLevel logLevel,
+            @NonNull String logTag,
+            @NonNull String message,
+            @NonNull Throwable throwable);
+
+    /** Log levels to match the visibility of log errors from android.util.Log. */
+    enum LogLevel {
+        INFO,
+        WARN,
+        ERROR,
+    }
+
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/ImmutableCollectors.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/ImmutableCollectors.java
new file mode 100644
index 0000000..8f30f68
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/ImmutableCollectors.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.appactions.interaction.capabilities.core.impl.utils;
+
+import static java.util.stream.Collectors.collectingAndThen;
+
+import androidx.annotation.NonNull;
+
+import java.util.Collections;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.function.Function;
+import java.util.stream.Collector;
+import java.util.stream.Collectors;
+
+/**
+ * Immutable collectors without guava dependencies. Collectors.toUnmodifiable*() function calls
+ * should not be used because they are only available on API 33+.
+ */
+public final class ImmutableCollectors {
+
+    private ImmutableCollectors() {
+    }
+
+    /** Collecting to immutable list. */
+    @NonNull
+    public static <E> Collector<E, ?, List<E>> toImmutableList() {
+        return collectingAndThen(Collectors.toList(), Collections::unmodifiableList);
+    }
+
+    /** Collecting to immutable set. */
+    @NonNull
+    public static <E> Collector<E, ?, Set<E>> toImmutableSet() {
+        return collectingAndThen(Collectors.toSet(), Collections::unmodifiableSet);
+    }
+
+    /** Collecting to immutable map. */
+    @NonNull
+    public static <T, K, V> Collector<T, ?, Map<K, V>> toImmutableMap(
+            @NonNull Function<? super T, ? extends K> keyFunction,
+            @NonNull Function<? super T, ? extends V> valueFunction) {
+        return collectingAndThen(
+                Collectors.toMap(keyFunction, valueFunction), Collections::unmodifiableMap);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/LoggerInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/LoggerInternal.java
new file mode 100644
index 0000000..7a9d7d6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/impl/utils/LoggerInternal.java
@@ -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.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.utils;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.impl.utils.CapabilityLogger.LogLevel;
+
+/**
+ * A singleton class to define the logger allows logger implementations to be defined outside of the
+ * capabilities classes, where it is permitted to depend on Android Logger(android.util.Log).
+ */
+public final class LoggerInternal {
+
+    @Nullable
+    private static CapabilityLogger sDelegate = null;
+
+    private LoggerInternal() {
+    }
+
+    public static void setLogger(@NonNull CapabilityLogger logger) {
+        sDelegate = logger;
+    }
+
+    public static void log(
+            @NonNull LogLevel logLevel, @NonNull String logTag, @NonNull String message) {
+        if (sDelegate != null) {
+            sDelegate.log(logLevel, logTag, message);
+        }
+    }
+
+    public static void log(
+            @NonNull LogLevel logLevel,
+            @NonNull String logTag,
+            @NonNull String message,
+            @NonNull Throwable t) {
+        if (sDelegate != null) {
+            sDelegate.log(logLevel, logTag, message, t);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/package-info.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/package-info.java
new file mode 100644
index 0000000..ee3d3fa
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/package-info.java
@@ -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.
+ */
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+package androidx.appactions.interaction.capabilities.core;
+
+import androidx.annotation.RestrictTo;
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.java
new file mode 100644
index 0000000..a40eae1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/Entity.java
@@ -0,0 +1,111 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.properties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+import java.util.Optional;
+
+/**
+ * Entities are used when defining ActionCapability for defining possible values for ParamProperty.
+ */
+public final class Entity {
+    private final Optional<String> mId;
+    private final Optional<String> mName;
+    private final List<String> mAlternateNames;
+
+    private Entity(Builder builder) {
+        this.mId = builder.mId;
+        this.mName = builder.mName;
+        this.mAlternateNames = builder.mAlternateNames;
+    }
+
+    /** Returns a new Builder to build an Entity instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** The id of this Entity. */
+    @NonNull
+    public Optional<String> getId() {
+        return mId;
+    }
+
+    /** The name of this entity. The name is what a user may say to refer to this Entity. */
+    @NonNull
+    public Optional<String> getName() {
+        return mName;
+    }
+
+    /**
+     * The alternate names of this entity. These are alternate names a user may say to refer to
+     * this
+     * Entity.
+     */
+    @NonNull
+    public List<String> getAlternateNames() {
+        if (mAlternateNames == null) {
+            return Collections.emptyList();
+        }
+        return mAlternateNames;
+    }
+
+    /** Builder class for Entity. */
+    public static class Builder {
+        private Optional<String> mId = Optional.empty();
+        private Optional<String> mName = Optional.empty();
+        private @Nullable List<String> mAlternateNames = null;
+
+        /** Sets the id of the Entity to be built. */
+        @NonNull
+        public Builder setId(@NonNull String id) {
+            this.mId = Optional.of(id);
+            return this;
+        }
+
+        /** Sets the name of the Entity to be built. */
+        @NonNull
+        public Builder setName(@NonNull String name) {
+            this.mName = Optional.of(name);
+            return this;
+        }
+
+        /** Sets the list of alternate names of the Entity to be built. */
+        @NonNull
+        public Builder setAlternateNames(@NonNull List<String> alternateNames) {
+            this.mAlternateNames = alternateNames;
+            return this;
+        }
+
+        /** Sets the list of alternate names of the Entity to be built. */
+        @NonNull
+        public final Builder setAlternateNames(@NonNull String... alternateNames) {
+            return setAlternateNames(Collections.unmodifiableList(Arrays.asList(alternateNames)));
+        }
+
+        /** Builds and returns an Entity. */
+        @NonNull
+        public Entity build() {
+            return new Entity(this);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EntityProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EntityProperty.java
new file mode 100644
index 0000000..274a91d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EntityProperty.java
@@ -0,0 +1,109 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.properties;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/** The property which describes a entity parameter for {@code ActionCapability}. */
+@AutoValue
+public abstract class EntityProperty extends ParamProperty<Entity> {
+    /** A default EntityProperty instance. This property will accept any entity value. */
+    public static final EntityProperty EMPTY = EntityProperty.newBuilder().build();
+
+    /**
+     * Using EntityProperty.PROHIBITED will ensure no Argument containing a value for this field
+     * will
+     * be received by this capability.
+     */
+    public static final EntityProperty PROHIBITED =
+            EntityProperty.newBuilder().setIsProhibited(true).build();
+
+    // TODO (b/260137899)
+
+    /** Returns a Builder instance for EntityProperty. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** Builder for {@link EntityProperty}. */
+    public static class Builder {
+
+        private final ArrayList<Entity> mPossibleEntityList = new ArrayList<>();
+        private boolean mIsRequired;
+        private boolean mEntityMatchRequired;
+        private boolean mIsProhibited;
+
+        private Builder() {
+        }
+
+        /**
+         * Adds one or more possible entities for this entity parameter.
+         *
+         * @param entities the possible entities.
+         */
+        @NonNull
+        public Builder addPossibleEntity(@NonNull Entity... entities) {
+            Collections.addAll(mPossibleEntityList, entities);
+            return this;
+        }
+
+        /** Sets whether or not this property requires a value for fulfillment. */
+        @NonNull
+        public Builder setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /**
+         * Sets whether or not this property requires that the value for this property must match
+         * one of
+         * the Entity in the defined possible entities.
+         */
+        @NonNull
+        public Builder setValueMatchRequired(boolean valueMatchRequired) {
+            this.mEntityMatchRequired = valueMatchRequired;
+            return this;
+        }
+
+        /**
+         * Sets whether this property is prohibited in the response.
+         *
+         * @param isProhibited Whether this property is prohibited in the response.
+         */
+        @NonNull
+        public Builder setIsProhibited(boolean isProhibited) {
+            this.mIsProhibited = isProhibited;
+            return this;
+        }
+
+        /** Builds the property for this entity parameter. */
+        @NonNull
+        public EntityProperty build() {
+            return new AutoValue_EntityProperty(
+                    Collections.unmodifiableList(mPossibleEntityList),
+                    mIsRequired,
+                    mEntityMatchRequired,
+                    mIsProhibited);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EnumProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EnumProperty.java
new file mode 100644
index 0000000..eddd714
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/EnumProperty.java
@@ -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.appactions.interaction.capabilities.core.properties;
+
+import static java.util.Arrays.stream;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * The property which describes an Enum parameter for {@code ActionCapability}.
+ *
+ * @param <EnumT>
+ */
+@AutoValue
+public abstract class EnumProperty<EnumT extends Enum<EnumT>> extends ParamProperty<EnumT> {
+
+    /**
+     * Returns a Builder to build an EnumProperty instance.
+     *
+     * @param enumType the class of the enum.
+     */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> Builder<EnumT> newBuilder(
+            @NonNull Class<EnumT> enumType) {
+        return new Builder<>(enumType);
+    }
+
+    // TODO (b/260137899)
+
+    abstract Class<EnumT> enumType();
+
+    /**
+     * Builder for {@link EnumProperty}.
+     *
+     * @param <EnumT>
+     */
+    public static final class Builder<EnumT extends Enum<EnumT>> {
+
+        private final ArrayList<EnumT> mPossibleEnumList = new ArrayList<>();
+        private final Class<EnumT> mEnumType;
+        private boolean mIsRequired;
+        private boolean mEntityMatchRequired;
+
+        private Builder(Class<EnumT> enumType) {
+            this.mEnumType = enumType;
+        }
+
+        /**
+         * Adds all app supported entity for this enum parameter. If any supported enum value is
+         * added
+         * then the entity matched is reuqired.
+         *
+         * @param supportedEnumValue supported enum values.
+         */
+        @NonNull
+        @SuppressWarnings("unchecked")
+        public Builder<EnumT> addSupportedEnumValues(@NonNull EnumT... supportedEnumValue) {
+            stream(supportedEnumValue).forEach(mPossibleEnumList::add);
+            this.mEntityMatchRequired = true;
+            return this;
+        }
+
+        /** Sets whether or not this property requires a value for fulfillment. */
+        @NonNull
+        public Builder<EnumT> setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /** Builds the property for this Enum parameter. */
+        @NonNull
+        public EnumProperty<EnumT> build() {
+            return new AutoValue_EnumProperty<>(
+                    Collections.unmodifiableList(mPossibleEnumList),
+                    mIsRequired,
+                    mEntityMatchRequired,
+                    /* isProhibited= */ false,
+                    mEnumType);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/IntegerProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/IntegerProperty.java
new file mode 100644
index 0000000..4145d76
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/IntegerProperty.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.appactions.interaction.capabilities.core.properties;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collections;
+
+/** The property which describes an integer parameter for {@code ActionCapability}. */
+@AutoValue
+public abstract class IntegerProperty extends ParamProperty<Integer> {
+    // The default instance of IntegerProperty. When add EMPTY IntegerProperty to capability, the
+    // intent param will be set with default values.
+    public static final IntegerProperty EMPTY = IntegerProperty.newBuilder().build();
+
+    /**
+     * Using IntegerProperty.REQUIRED ensures that any FulfillmentRequest to this capability will
+     * contain a value for this property.
+     */
+    public static final IntegerProperty REQUIRED =
+            IntegerProperty.newBuilder().setIsRequired(true).build();
+
+    // TODO (b/260137899)
+
+    /** Creates a new Builder for an IntegerProperty. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** Builder for {@link IntegerProperty}. */
+    public static class Builder {
+        private boolean mIsRequired;
+
+        private Builder() {
+        }
+
+        /** Sets whether this property is required for fulfillment. */
+        @NonNull
+        public Builder setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /** Builds the property for this integer parameter. */
+        @NonNull
+        public IntegerProperty build() {
+            return new AutoValue_IntegerProperty(
+                    /** getPossibleValues */
+                    Collections.unmodifiableList(Collections.emptyList()),
+                    mIsRequired,
+                    /* valueMatchRequired= */ false,
+                    /* prohibited= */ false);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/ParamProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/ParamProperty.java
new file mode 100644
index 0000000..3625a46
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/ParamProperty.java
@@ -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.interaction.capabilities.core.properties;
+
+import androidx.annotation.NonNull;
+
+import java.util.List;
+
+/**
+ * Base class for the property which describes a parameter for {@code ActionCapability}. This class
+ * should not be used directly. Instead, use the typed property classes such as {@link
+ * StringProperty}, etc.
+ *
+ * @param <V>
+ */
+public abstract class ParamProperty<V> {
+
+    /** The list of added possible values for this parameter. */
+    @NonNull
+    public abstract List<V> getPossibleValues();
+
+    /** Indicates that a value for this property is required to be present for fulfillment. */
+    public abstract boolean isRequired();
+
+    /**
+     * Indicates that a match of possible value for the given property must be present. Defaults to
+     * false.
+     *
+     * <p>If true, Assistant skips the capability if there is no match.
+     */
+    public abstract boolean isValueMatchRequired();
+
+    /**
+     * If true, the {@code ActionCapability} will be rejected by assistant if corresponding param is
+     * set in argument. And the value of |isRequired| and |entityMatchRequired| will also be ignored
+     * by assistant.
+     */
+    public abstract boolean isProhibited();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/SimpleProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/SimpleProperty.java
new file mode 100644
index 0000000..fb8f9d2
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/SimpleProperty.java
@@ -0,0 +1,75 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.properties;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collections;
+
+/**
+ * A simple property which describes a parameter for {@code ActionCapability}. This property has
+ * simple configurations available and is not tied to a specific type.
+ */
+@AutoValue
+public abstract class SimpleProperty extends ParamProperty<Void> {
+    /** Returns a default SimpleProperty instance. */
+    public static final SimpleProperty DEFAULT = SimpleProperty.newBuilder().build();
+
+    /**
+     * Using SimpleProperty.REQUIRED ensures that the corresponding Argument will contain a
+     * value.
+     */
+    public static final SimpleProperty REQUIRED =
+            SimpleProperty.newBuilder().setIsRequired(true).build();
+
+    // TODO (b/260137899)
+
+    /** Returns a Builder instance for SimpleProperty. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    /** Builder for {@link SimpleProperty}. */
+    public static class Builder {
+
+        private boolean mIsRequired;
+
+        private Builder() {
+        }
+
+        /** Sets whether or not this property requires a value for fulfillment. */
+        @NonNull
+        public Builder setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /** Builds the property for this string parameter. */
+        @NonNull
+        public SimpleProperty build() {
+            return new AutoValue_SimpleProperty(
+                    /** getPossibleValues */
+                    Collections.unmodifiableList(Collections.emptyList()),
+                    mIsRequired,
+                    /* valueMatchRequired= */ false,
+                    /* prohibited= */ false);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringOrEnumProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringOrEnumProperty.java
new file mode 100644
index 0000000..61c1b0b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringOrEnumProperty.java
@@ -0,0 +1,188 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.properties;
+
+import static java.util.Arrays.stream;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoOneOf;
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+
+/**
+ * The property which describes a parameter with String or Enum entity for {@code ActionCapability}.
+ *
+ * @param <EnumT>
+ */
+@AutoValue
+public abstract class StringOrEnumProperty<EnumT extends Enum<EnumT>>
+        extends ParamProperty<StringOrEnumProperty.PossibleValue<EnumT>> {
+
+    /** Returns a Builder for StringOrEnumProperty for the given enumType. */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> Builder<EnumT> newBuilder(
+            @NonNull Class<EnumT> enumType) {
+        return new Builder<>(enumType);
+    }
+
+    @NonNull
+    abstract Class<EnumT> enumType();
+
+    // TODO (b/260137899)
+
+    /**
+     * Returns a new Builder that contains all the existing configuration in this
+     * StringOrEnumProperty.
+     */
+    @NonNull
+    @SuppressWarnings("unchecked")
+    public Builder<EnumT> toBuilder() {
+        Builder<EnumT> builder = new Builder<>(enumType());
+        if (!getPossibleValues().isEmpty()) {
+            for (PossibleValue<EnumT> possibleValue : getPossibleValues()) {
+                if (possibleValue.getKind() == PossibleValue.Kind.STRING_VALUE) {
+                    builder.addPossibleValue(
+                            possibleValue.stringValue().getName(),
+                            possibleValue.stringValue().getAlternateNames().toArray(String[]::new));
+                } else if (possibleValue.getKind() == PossibleValue.Kind.ENUM_VALUE) {
+                    builder.addPossibleValue(possibleValue.enumValue());
+                }
+            }
+        }
+        return builder.setIsRequired(isRequired());
+    }
+
+    /**
+     * Represents a single possible value in StringOrEnumProperty.
+     *
+     * @param <EnumT>
+     */
+    @AutoOneOf(PossibleValue.Kind.class)
+    public abstract static class PossibleValue<EnumT extends Enum<EnumT>> {
+        /** Create a new StringOrEnumProperty.PossibleValue for Kind.STRING_VALUE. */
+        @NonNull
+        public static <EnumT extends Enum<EnumT>> PossibleValue<EnumT> of(
+                @NonNull String name, @NonNull String... alternateNames) {
+            return AutoOneOf_StringOrEnumProperty_PossibleValue.stringValue(
+                    StringProperty.PossibleValue.of(name, alternateNames));
+        }
+
+        /** Create a new StringOrEnumProperty.PossibleValue for Kind.ENUM_VALUE. */
+        @NonNull
+        public static <EnumT extends Enum<EnumT>> PossibleValue<EnumT> of(
+                @NonNull EnumT possibleValue) {
+            return AutoOneOf_StringOrEnumProperty_PossibleValue.enumValue(possibleValue);
+        }
+
+        /** The Kind of this possible value. */
+        @NonNull
+        public abstract Kind getKind();
+
+        /** Returns the StringProperty.PossibleValue, corresponds to Kind.STRING_VALUE. */
+        @NonNull
+        public abstract StringProperty.PossibleValue stringValue();
+
+        /** Returns the StringProperty.PossibleValue, corresponds to Kind.ENUM_VALUE. */
+        @NonNull
+        public abstract EnumT enumValue();
+
+        /** The Kind of PossibleValue. */
+        public enum Kind {
+            STRING_VALUE,
+            ENUM_VALUE,
+        }
+    }
+
+    /**
+     * Builder for {@link StringOrEnumProperty}.
+     *
+     * @param <EnumT>
+     */
+    public static class Builder<EnumT extends Enum<EnumT>> {
+
+        private final ArrayList<PossibleValue<EnumT>> mPossibleValueList = new ArrayList<>();
+        private final Class<EnumT> mEnumType;
+        private boolean mIsRequired;
+        private boolean mEntityMatchRequired;
+
+        private Builder(Class<EnumT> enumType) {
+            this.mEnumType = enumType;
+        }
+
+        /**
+         * Adds a possible string value for this property.
+         *
+         * @param name           the possible string value.
+         * @param alternateNames the alternative names for this value.
+         */
+        @NonNull
+        public Builder<EnumT> addPossibleValue(
+                @NonNull String name, @NonNull String... alternateNames) {
+            mPossibleValueList.add(PossibleValue.of(name, alternateNames));
+            this.mEntityMatchRequired = true;
+            return this;
+        }
+
+        /**
+         * Adds possible Enum values for this parameter.
+         *
+         * @param enumValues possible enum entity values.
+         */
+        @NonNull
+        @SuppressWarnings("unchecked")
+        public Builder<EnumT> addPossibleValue(@NonNull EnumT... enumValues) {
+            stream(enumValues).forEach(
+                    enumValue -> mPossibleValueList.add(PossibleValue.of(enumValue)));
+            this.mEntityMatchRequired = true;
+            return this;
+        }
+
+        /** Sets whether or not this property requires a value for fulfillment. */
+        @NonNull
+        public Builder<EnumT> setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /**
+         * Sets whether matching a possible value is required for this parameter. Note that this
+         * value
+         * can be overrided by assistant.
+         *
+         * @param valueMatchRequired whether value match is required
+         */
+        @NonNull
+        public Builder<EnumT> setValueMatchRequired(boolean valueMatchRequired) {
+            this.mEntityMatchRequired = valueMatchRequired;
+            return this;
+        }
+
+        /** Builds the property for this Entity or Enum parameter. */
+        @NonNull
+        public StringOrEnumProperty<EnumT> build() {
+            return new AutoValue_StringOrEnumProperty<>(
+                    Collections.unmodifiableList(mPossibleValueList),
+                    mIsRequired,
+                    mEntityMatchRequired,
+                    /* isProhibited= */ false,
+                    mEnumType);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringProperty.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringProperty.java
new file mode 100644
index 0000000..69c020a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/properties/StringProperty.java
@@ -0,0 +1,134 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.properties;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.Collections;
+import java.util.List;
+
+/** The property which describes a string parameter for {@code ActionCapability}. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class StringProperty extends ParamProperty<StringProperty.PossibleValue> {
+    /** A default StringProperty instance. This property will accept any string value. */
+    public static final StringProperty EMPTY = StringProperty.newBuilder().build();
+
+    /**
+     * Using StringProperty.PROHIBITED will ensure no Argument containing a value for this field
+     * will
+     * be received by this capability.
+     */
+    public static final StringProperty PROHIBITED =
+            StringProperty.newBuilder().setIsProhibited(true).build();
+
+    /** Returns a Builder instance for StringProperty. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new Builder();
+    }
+
+    // TODO (b/260137899)
+
+    /** Represents a single possible value for StringProperty. */
+    @AutoValue
+    public abstract static class PossibleValue {
+
+        /** Creates a new StringProperty.PossibleValue instance. */
+        @NonNull
+        public static final PossibleValue of(@NonNull String name,
+                @NonNull String... alternateNames) {
+            return new AutoValue_StringProperty_PossibleValue(
+                    name, Collections.unmodifiableList(Arrays.asList(alternateNames)));
+        }
+
+        /** The name of this possible value. */
+        @NonNull
+        public abstract String getName();
+
+        /** The alternate names of this possible value. */
+        @NonNull
+        public abstract List<String> getAlternateNames();
+    }
+
+    /** Builder for {@link StringProperty}. */
+    public static class Builder {
+
+        private final ArrayList<PossibleValue> mPossibleValueList = new ArrayList<>();
+        private boolean mIsRequired;
+        private boolean mEntityMatchRequired;
+        private boolean mIsProhibited;
+
+        private Builder() {
+        }
+
+        /**
+         * Adds a possible string value for this property.
+         *
+         * @param name           the possible string value.
+         * @param alternateNames the alternate names for this value.
+         */
+        @NonNull
+        public Builder addPossibleValue(@NonNull String name, @NonNull String... alternateNames) {
+            mPossibleValueList.add(PossibleValue.of(name, alternateNames));
+            return this;
+        }
+
+        /** Sets whether or not this property requires a value for fulfillment. */
+        @NonNull
+        public Builder setIsRequired(boolean isRequired) {
+            this.mIsRequired = isRequired;
+            return this;
+        }
+
+        /**
+         * Sets whether or not this property requires that the value for this property must match
+         * one of
+         * the string values in the defined possible values.
+         */
+        @NonNull
+        public Builder setValueMatchRequired(boolean valueMatchRequired) {
+            this.mEntityMatchRequired = valueMatchRequired;
+            return this;
+        }
+
+        /**
+         * Sets whether the String property is prohibited in the response.
+         *
+         * @param isProhibited Whether this property is prohibited in the response.
+         */
+        @NonNull
+        public Builder setIsProhibited(boolean isProhibited) {
+            this.mIsProhibited = isProhibited;
+            return this;
+        }
+
+        /** Builds the property for this string parameter. */
+        @NonNull
+        public StringProperty build() {
+            return new AutoValue_StringProperty(
+                    Collections.unmodifiableList(mPossibleValueList),
+                    mIsRequired,
+                    mEntityMatchRequired,
+                    mIsProhibited);
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityListResolver.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityListResolver.java
new file mode 100644
index 0000000..920c369
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityListResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Similar to ValueListener, but also need to handle grounding of ungrounded values.
+ *
+ * @param <T>
+ */
+public interface AppEntityListResolver<T> extends ValueListener<List<T>> {
+    /**
+     * Given a search criteria, looks up the inventory during runtime, renders the search result
+     * within the app's own UI and then returns it to the Assistant so that the task can be kept in
+     * sync with the app UI.
+     */
+    @NonNull
+    ListenableFuture<EntitySearchResult<T>> lookupAndRender(@NonNull SearchAction<T> searchAction);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityResolver.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityResolver.java
new file mode 100644
index 0000000..8cfa324
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/AppEntityResolver.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Similar to ValueListener, but also need to handle grounding of ungrounded values.
+ *
+ * @param <T>
+ */
+public interface AppEntityResolver<T> extends ValueListener<T> {
+    /**
+     * Given a search criteria, looks up the inventory during runtime, renders the search result
+     * within the app's own UI and then returns it to the Assistant so that the task can be kept in
+     * sync with the app UI.
+     */
+    @NonNull
+    ListenableFuture<EntitySearchResult<T>> lookupAndRender(@NonNull SearchAction<T> searchAction);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResult.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResult.java
new file mode 100644
index 0000000..98666ad
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResult.java
@@ -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.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Represents results from grounding an ungrounded value.
+ *
+ * @param <V>
+ */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class EntitySearchResult<V> {
+
+    /** Builds a EntitySearchResult with no possible values. */
+    @NonNull
+    public static <V> EntitySearchResult<V> empty() {
+        return EntitySearchResult.<V>newBuilder().build();
+    }
+
+    @NonNull
+    public static <V> Builder<V> newBuilder() {
+        return new AutoValue_EntitySearchResult.Builder<>();
+    }
+
+    /**
+     * The possible entity values for grounding. Returning exactly 1 result means the value will be
+     * immediately accepted by the task. Returning multiple values will leave the argument in a
+     * disambiguation state, and Assistant should ask for clarification from the user.
+     */
+    @NonNull
+    public abstract List<V> possibleValues();
+
+    /**
+     * Builder for the EntitySearchResult.
+     *
+     * @param <V>
+     */
+    @AutoValue.Builder
+    public abstract static class Builder<V> {
+        private final List<V> mPossibleValues = new ArrayList<>();
+
+        @NonNull
+        abstract Builder<V> setPossibleValues(@NonNull List<V> possibleValues);
+
+        @NonNull
+        public final Builder<V> addPossibleValue(@NonNull V value) {
+            mPossibleValues.add(value);
+            return this;
+        }
+
+        abstract EntitySearchResult<V> autoBuild();
+
+        @NonNull
+        public EntitySearchResult<V> build() {
+            setPossibleValues(mPossibleValues);
+            return autoBuild();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InvalidTaskException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InvalidTaskException.java
new file mode 100644
index 0000000..c277143
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InvalidTaskException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Thrown when the task is not configured correctly for a particular capability instance. One
+ * example is when a capability requires user confirmation support, but the capability instance does
+ * not set a value for the {@code OnReadyToConfirmListener}.
+ */
+public final class InvalidTaskException extends RuntimeException {
+
+    public InvalidTaskException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryListResolver.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryListResolver.java
new file mode 100644
index 0000000..b152a17
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryListResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Repeated version of {@code InventoryResolver}.
+ *
+ * @param <T>
+ */
+public interface InventoryListResolver<T> extends ValueListener<List<T>> {
+    /**
+     * Renders the provided entities in the app UI for dismabiguation.
+     *
+     * <p>The app should not modify the entity contents or their orders during the rendering.
+     * Otherwise, the Assistant task will be out of sync with the app UI.
+     */
+    @NonNull
+    ListenableFuture<Void> renderChoices(@NonNull List<String> entityIDs);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryResolver.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryResolver.java
new file mode 100644
index 0000000..47674c1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/InventoryResolver.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * Similar to ValueListener, but also need to handle entity rendering.
+ *
+ * @param <T>
+ */
+public interface InventoryResolver<T> extends ValueListener<T> {
+    /**
+     * Renders the provided entities in the app UI for dismabiguation.
+     *
+     * <p>The app should not modify the entity contents or their orders during the rendering.
+     * Otherwise, the Assistant task will be out of sync with the app UI.
+     */
+    @NonNull
+    ListenableFuture<Void> renderChoices(@NonNull List<String> entityIDs);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnDialogFinishListener.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnDialogFinishListener.java
new file mode 100644
index 0000000..d79a2e1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnDialogFinishListener.java
@@ -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.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Listener for when a task reaches a valid argument state, and can finish fulfillment.
+ *
+ * @param <ArgumentT>
+ * @param <OutputT>
+ */
+public interface OnDialogFinishListener<ArgumentT, OutputT> {
+
+    /** Called when a task finishes, with the final Argument instance. */
+    @NonNull
+    ListenableFuture<ExecutionResult<OutputT>> onFinish(@NonNull ArgumentT finalArgument);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnInitListener.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnInitListener.java
new file mode 100644
index 0000000..ff04662
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnInitListener.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Listener for when a task is initialized.
+ *
+ * @param <TaskUpdaterT>
+ */
+public interface OnInitListener<TaskUpdaterT> {
+
+    /**
+     * Called when a task is initiated. This method should perform initialization if necessary, and
+     * return a future that represents when initialization is finished.
+     *
+     * @param arg depending on the BII, the SDK may pass a utility object to this method, such as
+     *            TaskUpdater.
+     */
+    @NonNull
+    ListenableFuture<Void> onInit(TaskUpdaterT arg);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnReadyToConfirmListener.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnReadyToConfirmListener.java
new file mode 100644
index 0000000..eb6c3bd9
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/OnReadyToConfirmListener.java
@@ -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.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ConfirmationOutput;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Listener for when a task reaches a valid argument state, and can confirm fulfillment.
+ *
+ * @param <ArgumentT>
+ * @param <ConfirmationT>
+ */
+public interface OnReadyToConfirmListener<ArgumentT, ConfirmationT> {
+
+    /** Called when a task is ready to confirm, with the final Argument instance. */
+    @NonNull
+    ListenableFuture<ConfirmationOutput<ConfirmationT>> onReadyToConfirm(ArgumentT finalArgument);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValidationResult.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValidationResult.java
new file mode 100644
index 0000000..e80d9a7
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValidationResult.java
@@ -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.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoOneOf;
+
+/** Result from validating a single argument value. */
+@AutoOneOf(ValidationResult.Kind.class)
+public abstract class ValidationResult {
+    /** Creates a new ACCEPTED ValidationResult. */
+    @NonNull
+    public static ValidationResult newAccepted() {
+        return AutoOneOf_ValidationResult.accepted("accepted");
+    }
+
+    /** Creates a new REJECTED ValidationResult. */
+    @NonNull
+    public static ValidationResult newRejected() {
+        return AutoOneOf_ValidationResult.rejected("rejected");
+    }
+
+    @NonNull
+    public abstract Kind getKind();
+
+    abstract String accepted();
+
+    abstract String rejected();
+
+    /** The state of the argument value after performing validation. */
+    public enum Kind {
+        ACCEPTED,
+        REJECTED,
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListListener.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListListener.java
new file mode 100644
index 0000000..f1b6af7
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListListener.java
@@ -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.appactions.interaction.capabilities.core.task;
+
+import java.util.List;
+
+/**
+ * Similar to {@code ValueListener} but for repeated fields.
+ *
+ * @param <T>
+ */
+public interface ValueListListener<T> extends ValueListener<List<T>> {
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.java
new file mode 100644
index 0000000..884a95a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/ValueListener.java
@@ -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.appactions.interaction.capabilities.core.task;
+
+import androidx.annotation.NonNull;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/**
+ * Provides a mechanism for the app to listen to argument updates from Assistant.
+ *
+ * @param <T>
+ */
+public interface ValueListener<T> {
+    /**
+     * Invoked when Assistant reports that an argument value has changed. This method should be
+     * idempotent, as it may be called multiple times with the same input value, not only on the
+     * 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.
+     * </ul>
+     *
+     * <p>Returns a ListenableFuture that resolves to the ValidationResult.
+     */
+    @NonNull
+    ListenableFuture<ValidationResult> onReceived(T value);
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AbstractTaskUpdater.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AbstractTaskUpdater.java
new file mode 100644
index 0000000..43c3882
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AbstractTaskUpdater.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+
+/** Base class for BII-specific TaskUpdater for manual input. */
+public abstract class AbstractTaskUpdater {
+
+    private TaskUpdateHandler mTaskUpdateHandler;
+    private boolean mIsDestroyed = false;
+
+    /** Will fail silently if task processing is in the middle of another SYNC request. */
+    protected void updateParamValues(@NonNull Map<String, List<ParamValue>> paramValuesMap) {
+        if (mIsDestroyed || mTaskUpdateHandler == null) {
+            return;
+        }
+        this.mTaskUpdateHandler.updateParamValues(paramValuesMap);
+    }
+
+    void init(@NonNull TaskUpdateHandler taskUpdateHandler) {
+        this.mTaskUpdateHandler = taskUpdateHandler;
+    }
+
+    void destroy() {
+        this.mIsDestroyed = true;
+    }
+
+    /** Returns true if the applicable task is terminated. */
+    public boolean isDestroyed() {
+        return mIsDestroyed;
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AppGroundingResult.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AppGroundingResult.java
new file mode 100644
index 0000000..ef14c02
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AppGroundingResult.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.appactions.interaction.proto.CurrentValue;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoOneOf;
+
+/**
+ * Helper object to express if the grounding step of argument processing was successul for a single
+ * ParamValue.
+ */
+@AutoOneOf(AppGroundingResult.Kind.class)
+abstract class AppGroundingResult {
+    static AppGroundingResult ofSuccess(ParamValue paramValue) {
+        return AutoOneOf_AppGroundingResult.success(paramValue);
+    }
+
+    static AppGroundingResult ofFailure(CurrentValue currentValue) {
+        return AutoOneOf_AppGroundingResult.failure(currentValue);
+    }
+
+    public abstract Kind getKind();
+
+    abstract ParamValue success();
+
+    abstract CurrentValue failure();
+
+    enum Kind {
+        SUCCESS,
+        FAILURE
+    }
+}
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
new file mode 100644
index 0000000..31570a2
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/AssistantUpdateRequest.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.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/EmptyTaskUpdater.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/EmptyTaskUpdater.java
new file mode 100644
index 0000000..4155e60
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/EmptyTaskUpdater.java
@@ -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.appactions.interaction.capabilities.core.task.impl;
+
+/** Useful for capabilities that do not support a TaskUpdater. */
+public final class EmptyTaskUpdater extends AbstractTaskUpdater {
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/GenericResolverInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/GenericResolverInternal.java
new file mode 100644
index 0000000..2f512a5
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/GenericResolverInternal.java
@@ -0,0 +1,179 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.SlotTypeConverter;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.task.AppEntityListResolver;
+import androidx.appactions.interaction.capabilities.core.task.AppEntityResolver;
+import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult;
+import androidx.appactions.interaction.capabilities.core.task.InventoryListResolver;
+import androidx.appactions.interaction.capabilities.core.task.InventoryResolver;
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult;
+import androidx.appactions.interaction.capabilities.core.task.ValueListListener;
+import androidx.appactions.interaction.capabilities.core.task.ValueListener;
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.InvalidResolverException;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoOneOf;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+
+/**
+ * A wrapper around all types of slot resolvers (value listeners + disambig resolvers).
+ *
+ * <p>This allows one type of resolver to be bound for each slot, and abstracts the details of the
+ * individual resolvers. It is also the place where repeated fields are handled.
+ *
+ * @param <ValueTypeT>
+ */
+@AutoOneOf(GenericResolverInternal.Kind.class)
+public abstract class GenericResolverInternal<ValueTypeT> {
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromValueListener(
+            @NonNull ValueListener<ValueTypeT> valueListener) {
+        return AutoOneOf_GenericResolverInternal.value(valueListener);
+    }
+
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromValueListListener(
+            @NonNull ValueListListener<ValueTypeT> valueListListener) {
+        return AutoOneOf_GenericResolverInternal.valueList(valueListListener);
+    }
+
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromAppEntityResolver(
+            @NonNull AppEntityResolver<ValueTypeT> appEntityResolver) {
+        return AutoOneOf_GenericResolverInternal.appEntityResolver(appEntityResolver);
+    }
+
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromAppEntityListResolver(
+            @NonNull AppEntityListResolver<ValueTypeT> appEntityListResolver) {
+        return AutoOneOf_GenericResolverInternal.appEntityListResolver(appEntityListResolver);
+    }
+
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromInventoryResolver(
+            @NonNull InventoryResolver<ValueTypeT> inventoryResolver) {
+        return AutoOneOf_GenericResolverInternal.inventoryResolver(inventoryResolver);
+    }
+
+    @NonNull
+    public static <ValueTypeT> GenericResolverInternal<ValueTypeT> fromInventoryListResolver(
+            @NonNull InventoryListResolver<ValueTypeT> inventoryListResolverListener) {
+        return AutoOneOf_GenericResolverInternal.inventoryListResolver(
+                inventoryListResolverListener);
+    }
+
+    /** Returns the Kind of this resolver */
+    @NonNull
+    public abstract Kind getKind();
+
+    abstract ValueListener<ValueTypeT> value();
+
+    abstract ValueListListener<ValueTypeT> valueList();
+
+    abstract AppEntityResolver<ValueTypeT> appEntityResolver();
+
+    abstract AppEntityListResolver<ValueTypeT> appEntityListResolver();
+
+    abstract InventoryResolver<ValueTypeT> inventoryResolver();
+
+    abstract InventoryListResolver<ValueTypeT> inventoryListResolver();
+
+    /** Wrapper which should invoke the `lookupAndRender` provided by the developer. */
+    @NonNull
+    public ListenableFuture<EntitySearchResult<ValueTypeT>> invokeLookup(
+            @NonNull SearchAction<ValueTypeT> searchAction) throws InvalidResolverException {
+        switch (getKind()) {
+            case APP_ENTITY_RESOLVER:
+                return appEntityResolver().lookupAndRender(searchAction);
+            case APP_ENTITY_LIST_RESOLVER:
+                return appEntityListResolver().lookupAndRender(searchAction);
+            default:
+                throw new InvalidResolverException(
+                        String.format(
+                                "invokeLookup is not supported on this resolver of type %s",
+                                getKind().name()));
+        }
+    }
+
+    /**
+     * Wrapper which should invoke the EntityRender#renderEntities method when the Assistant is
+     * prompting for disambiguation.
+     */
+    @NonNull
+    public ListenableFuture<Void> invokeEntityRender(@NonNull List<String> entityIds)
+            throws InvalidResolverException {
+        switch (getKind()) {
+            case INVENTORY_RESOLVER:
+                return inventoryResolver().renderChoices(entityIds);
+            case INVENTORY_LIST_RESOLVER:
+                return inventoryListResolver().renderChoices(entityIds);
+            default:
+                throw new InvalidResolverException(
+                        String.format(
+                                "invokeEntityRender is not supported on this resolver of type %s",
+                                getKind().name()));
+        }
+    }
+
+    /**
+     * Notifies the app that a new value for this argument has been set by Assistant. This method
+     * should only be called with completely grounded values.
+     */
+    @NonNull
+    public ListenableFuture<ValidationResult> notifyValueChange(
+            @NonNull List<ParamValue> paramValues,
+            @NonNull ParamValueConverter<ValueTypeT> converter)
+            throws StructConversionException {
+        SlotTypeConverter<ValueTypeT> singularConverter = SlotTypeConverter.ofSingular(converter);
+        SlotTypeConverter<List<ValueTypeT>> repeatedConverter = SlotTypeConverter.ofRepeated(
+                converter);
+
+        switch (getKind()) {
+            case VALUE:
+                return value().onReceived(singularConverter.convert(paramValues));
+            case VALUE_LIST:
+                return valueList().onReceived(repeatedConverter.convert(paramValues));
+            case APP_ENTITY_RESOLVER:
+                return appEntityResolver().onReceived(singularConverter.convert(paramValues));
+            case APP_ENTITY_LIST_RESOLVER:
+                return appEntityListResolver().onReceived(repeatedConverter.convert(paramValues));
+            case INVENTORY_RESOLVER:
+                return inventoryResolver().onReceived(singularConverter.convert(paramValues));
+            case INVENTORY_LIST_RESOLVER:
+                return inventoryListResolver().onReceived(repeatedConverter.convert(paramValues));
+        }
+        throw new IllegalStateException("unreachable");
+    }
+
+    /** The kind of resolver. */
+    public enum Kind {
+        VALUE,
+        VALUE_LIST,
+        APP_ENTITY_RESOLVER,
+        APP_ENTITY_LIST_RESOLVER,
+        INVENTORY_RESOLVER,
+        INVENTORY_LIST_RESOLVER
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/ManualInputUpdateRequest.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/ManualInputUpdateRequest.java
new file mode 100644
index 0000000..a869f9b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/ManualInputUpdateRequest.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/** Represents a fulfillment request coming from user tap. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+abstract class TouchEventUpdateRequest {
+
+    static TouchEventUpdateRequest create(Map<String, List<ParamValue>> paramValuesMap) {
+        return new AutoValue_TouchEventUpdateRequest(paramValuesMap);
+    }
+
+    /**
+     * merge two TouchEventUpdateRequest instances. Map entries in newRequest will take priority in
+     * case of conflict.
+     */
+    static TouchEventUpdateRequest merge(
+            TouchEventUpdateRequest oldRequest, TouchEventUpdateRequest newRequest) {
+        Map<String, List<ParamValue>> mergedParamValuesMap = new HashMap<>(
+                oldRequest.paramValuesMap());
+        mergedParamValuesMap.putAll(newRequest.paramValuesMap());
+        return TouchEventUpdateRequest.create(Collections.unmodifiableMap(mergedParamValuesMap));
+    }
+
+    /* the param values from manual input. */
+    abstract Map<String, List<ParamValue>> paramValuesMap();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OnReadyToConfirmListenerInternal.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OnReadyToConfirmListenerInternal.java
new file mode 100644
index 0000000..e4a9047
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OnReadyToConfirmListenerInternal.java
@@ -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.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ConfirmationOutput;
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.task.impl.exceptions.MissingRequiredArgException;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Generic onReadyToConfirm listener for a task capability. This is the entry point to specific
+ * onReadyToCOnfirm listeners. For example, Search/Update sub-BIIs factories may invoke specific
+ * onReadyToConfirm listeners for that BII.
+ *
+ * @param <ConfirmationT>
+ */
+public interface OnReadyToConfirmListenerInternal<ConfirmationT> {
+
+    /** onReadyToConfirm callback for a task capability. */
+    @NonNull
+    ListenableFuture<ConfirmationOutput<ConfirmationT>> onReadyToConfirm(
+            @NonNull Map<String, List<ParamValue>> args)
+            throws StructConversionException, MissingRequiredArgException;
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OperationType.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OperationType.java
new file mode 100644
index 0000000..157202d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/OperationType.java
@@ -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.capabilities.core.task.impl;
+
+/**
+ * Represents different operations possible in the Search/Update protocol.
+ */
+public enum OperationType {
+    /** Supports adding to a field of the target object, for example adding to a list. */
+    ADD("Add");
+
+    private final String mOperationType;
+
+    OperationType(String operationType) {
+        this.mOperationType = operationType;
+    }
+
+    @Override
+    public String toString() {
+        return mOperationType;
+    }
+}
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
new file mode 100644
index 0000000..6fa70e6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/SlotProcessingResult.java
@@ -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.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/TaskCapabilityImpl.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImpl.java
new file mode 100644
index 0000000..6c7ecd1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImpl.java
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilityInternal;
+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.capabilities.core.task.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+import java.util.Optional;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * Stateful horizontal task orchestrator to manage business logic for the task.
+ *
+ * @param <PropertyT>
+ * @param <ArgumentT>
+ * @param <OutputT>
+ * @param <ConfirmationT>
+ * @param <TaskUpdaterT>
+ */
+public final class TaskCapabilityImpl<
+                PropertyT,
+                ArgumentT,
+                OutputT,
+                ConfirmationT,
+                TaskUpdaterT extends AbstractTaskUpdater>
+        implements ActionCapabilityInternal, TaskUpdateHandler {
+
+    private final String mIdentifier;
+    private final Executor mExecutor;
+    private final TaskOrchestrator<PropertyT, ArgumentT, OutputT, ConfirmationT, TaskUpdaterT>
+            mTaskOrchestrator;
+
+    private final Object mAssistantUpdateLock = new Object();
+
+    @GuardedBy("mAssistantUpdateLock")
+    @Nullable
+    private AssistantUpdateRequest mPendingAssistantRequest = null;
+
+    @GuardedBy("mAssistantUpdateLock")
+    @Nullable
+    private TouchEventUpdateRequest mPendingTouchEventRequest = null;
+
+    public TaskCapabilityImpl(
+            @NonNull String identifier,
+            @NonNull ActionSpec<PropertyT, ArgumentT, OutputT> actionSpec,
+            PropertyT property,
+            @NonNull TaskParamRegistry paramRegistry,
+            @NonNull Optional<OnInitListener<TaskUpdaterT>> onInitListener,
+            @NonNull
+                    Optional<OnReadyToConfirmListenerInternal<ConfirmationT>>
+                            onReadyToConfirmListener,
+            @NonNull OnDialogFinishListener<ArgumentT, OutputT> onFinishListener,
+            @NonNull
+                    Map<String, Function<ConfirmationT, List<ParamValue>>>
+                            confirmationOutputBindings,
+            @NonNull Map<String, Function<OutputT, List<ParamValue>>> executionOutputBindings,
+            @NonNull Executor executor) {
+        this.mIdentifier = identifier;
+        this.mExecutor = executor;
+        this.mTaskOrchestrator =
+                new TaskOrchestrator<>(
+                        identifier,
+                        actionSpec,
+                        property,
+                        paramRegistry,
+                        onInitListener,
+                        onReadyToConfirmListener,
+                        onFinishListener,
+                        confirmationOutputBindings,
+                        executionOutputBindings,
+                        executor);
+    }
+
+    @NonNull
+    @Override
+    public Optional<String> getId() {
+        return Optional.of(mIdentifier);
+    }
+
+    public void setTaskUpdaterSupplier(@NonNull Supplier<TaskUpdaterT> taskUpdaterSupplier) {
+        this.mTaskOrchestrator.setTaskUpdaterSupplier(
+                () -> {
+                    TaskUpdaterT taskUpdater = taskUpdaterSupplier.get();
+                    taskUpdater.init(this);
+                    return taskUpdater;
+                });
+    }
+
+    @Override
+    public void setTouchEventCallback(@NonNull TouchEventCallback touchEventCallback) {
+        mTaskOrchestrator.setTouchEventCallback(touchEventCallback);
+    }
+
+    @NonNull
+    @Override
+    public AppAction getAppAction() {
+        return this.mTaskOrchestrator.getAppAction();
+    }
+
+    /**
+     * If there is a pendingAssistantRequest, we will overwrite that request (and send CANCELLED
+     * response to that request).
+     *
+     * <p>This is done because assistant requests contain the full state, so we can safely ignore
+     * existing requests if a new one arrives.
+     */
+    private void enqueueAssistantRequest(@NonNull AssistantUpdateRequest request) {
+        synchronized (mAssistantUpdateLock) {
+            if (mPendingAssistantRequest != null) {
+                mPendingAssistantRequest.callbackInternal().onError(ErrorStatusInternal.CANCELLED);
+            }
+            mPendingAssistantRequest = request;
+            dispatchPendingRequestIfIdle();
+        }
+    }
+
+    private void enqueueTouchEventRequest(@NonNull TouchEventUpdateRequest request) {
+        synchronized (mAssistantUpdateLock) {
+            if (mPendingTouchEventRequest == null) {
+                mPendingTouchEventRequest = request;
+            } else {
+                mPendingTouchEventRequest =
+                        TouchEventUpdateRequest.merge(mPendingTouchEventRequest, request);
+            }
+            dispatchPendingRequestIfIdle();
+        }
+    }
+
+    /**
+     * If taskOrchestrator is idle, select the next request to dispatch to taskOrchestrator (if
+     * there are any pending requests).
+     *
+     * <p>If taskOrchestrator is not idle, do nothing, since this method will automatically be
+     * called when the current request finishes.
+     */
+    void dispatchPendingRequestIfIdle() {
+        synchronized (mAssistantUpdateLock) {
+            if (!mTaskOrchestrator.isIdle()) {
+                return;
+            }
+            UpdateRequest nextRequest = null;
+            if (mPendingAssistantRequest != null) {
+                nextRequest = UpdateRequest.of(mPendingAssistantRequest);
+                mPendingAssistantRequest = null;
+            } else if (mPendingTouchEventRequest != null) {
+                nextRequest = UpdateRequest.of(mPendingTouchEventRequest);
+                mPendingTouchEventRequest = null;
+            }
+            if (nextRequest != null) {
+                Futures.addCallback(
+                        mTaskOrchestrator.processUpdateRequest(nextRequest),
+                        new FutureCallback<Void>() {
+                            @Override
+                            public void onSuccess(Void unused) {
+                                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
+                            public void onFailure(@NonNull Throwable t) {
+                                throw new IllegalStateException(
+                                        "unhandled exception in request processing", t);
+                            }
+                        },
+                        mExecutor);
+            }
+        }
+    }
+
+    @Override
+    public void execute(
+            @NonNull ArgumentsWrapper argumentsWrapper, @NonNull CallbackInternal callback) {
+        enqueueAssistantRequest(AssistantUpdateRequest.create(argumentsWrapper, callback));
+    }
+
+    /** Method for attempting to manually update the param values. */
+    @Override
+    public void updateParamValues(@NonNull Map<String, List<ParamValue>> paramValuesMap) {
+        enqueueTouchEventRequest(TouchEventUpdateRequest.create(paramValuesMap));
+    }
+}
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
new file mode 100644
index 0000000..41fe7f6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtils.java
@@ -0,0 +1,252 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 com.google.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/TaskOrchestrator.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.java
new file mode 100644
index 0000000..4edee50
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskOrchestrator.java
@@ -0,0 +1,713 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ConfirmationOutput;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+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.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+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.Optional;
+import java.util.Set;
+import java.util.concurrent.Executor;
+import java.util.function.Function;
+import java.util.function.Supplier;
+
+/**
+ * TaskOrchestrator is responsible for holding task state, and processing assistant / manual input
+ * updates to update task 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<
+        PropertyT, ArgumentT, OutputT, ConfirmationT, TaskUpdaterT extends AbstractTaskUpdater> {
+
+    private static final String LOG_TAG = "TaskOrchestrator";
+    private final String mIdentifier;
+    private final ActionSpec<PropertyT, ArgumentT, OutputT> mActionSpec;
+    private final PropertyT mProperty;
+    private final TaskParamRegistry mParamRegistry;
+    private final Optional<OnInitListener<TaskUpdaterT>> mOnInitListener;
+    private final Optional<OnReadyToConfirmListenerInternal<ConfirmationT>>
+            mOnReadyToConfirmListener;
+
+    private final OnDialogFinishListener<ArgumentT, OutputT> mOnFinishListener;
+    private final Executor mExecutor;
+
+    /**
+     * Map of argument name to the {@link CurrentValue} which wraps the argument name and status
+     * .
+     */
+    private final Map<String, List<CurrentValue>> mCurrentValuesMap;
+    /**
+     * Map of confirmation data name to a function that converts confirmation data to ParamValue
+     * .
+     */
+    private final Map<String, Function<ConfirmationT, List<ParamValue>>>
+            mConfirmationOutputBindings;
+    /** Map of execution output name to a function that converts execution output to ParamValue. */
+    private final Map<String, Function<OutputT, List<ParamValue>>> mExecutionOutputBindings;
+    /**
+     * 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 TaskStatus mTaskStatus;
+    /** Supplies new instances of TaskUpdaterT to give to onInitListener. */
+    private Supplier<TaskUpdaterT> mTaskUpdaterSupplier;
+
+    /**
+     * The current TaskUpdaterT instance. Should only be non-null when taskStatus is IN_PROGRESS.
+     */
+    @Nullable
+    private TaskUpdaterT mTaskUpdater;
+    /** True if an UpdateRequest is currently being processed, false otherwise. */
+    @GuardedBy("mTaskOrchestratorLock")
+    private boolean mIsIdle = true;
+
+    TaskOrchestrator(
+            String identifier,
+            ActionSpec<PropertyT, ArgumentT, OutputT> actionSpec,
+            PropertyT property,
+            TaskParamRegistry paramRegistry,
+            Optional<OnInitListener<TaskUpdaterT>> onInitListener,
+            Optional<OnReadyToConfirmListenerInternal<ConfirmationT>> onReadyToConfirmListener,
+            OnDialogFinishListener<ArgumentT, OutputT> onFinishListener,
+            Map<String, Function<ConfirmationT, List<ParamValue>>> confirmationOutputBindings,
+            Map<String, Function<OutputT, List<ParamValue>>> executionOutputBindings,
+            Executor executor) {
+        this.mIdentifier = identifier;
+        this.mActionSpec = actionSpec;
+        this.mProperty = property;
+        this.mParamRegistry = paramRegistry;
+        this.mOnInitListener = onInitListener;
+        this.mOnReadyToConfirmListener = onReadyToConfirmListener;
+        this.mOnFinishListener = onFinishListener;
+        this.mConfirmationOutputBindings = confirmationOutputBindings;
+        this.mExecutionOutputBindings = executionOutputBindings;
+        this.mExecutor = executor;
+
+        this.mCurrentValuesMap = Collections.synchronizedMap(new HashMap<>());
+        this.mTaskStatus = TaskStatus.UNINITIATED;
+        this.mTaskUpdater = null;
+    }
+
+    void setTaskUpdaterSupplier(Supplier<TaskUpdaterT> taskUpdaterSupplier) {
+        this.mTaskUpdaterSupplier = taskUpdaterSupplier;
+    }
+
+    // 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;
+        }
+    }
+
+    /**
+     * 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.requestMetadata().isPresent()) {
+            callback.onError(ErrorStatusInternal.INVALID_REQUEST_TYPE);
+            return Futures.immediateVoidFuture();
+        }
+        Fulfillment.Type requestType = argumentsWrapper.requestMetadata().get().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:
+                clearState();
+                callback.onSuccess(FulfillmentResponse.getDefaultInstance());
+                break;
+        }
+        return Futures.immediateVoidFuture();
+    }
+
+    public ListenableFuture<Void> processTouchEventUpdateRequest(
+            TouchEventUpdateRequest touchEventUpdateRequest) {
+        Map<String, List<ParamValue>> paramValuesMap = touchEventUpdateRequest.paramValuesMap();
+        if (mTouchEventCallback == null
+                || paramValuesMap.isEmpty()
+                || mTaskStatus != TaskStatus.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";
+                });
+    }
+
+    /** Remove any state that may affect the #getAppAction() call. */
+    private void clearState() {
+        if (this.mTaskUpdater != null) {
+            this.mTaskUpdater.destroy();
+            this.mTaskUpdater = null;
+        }
+        this.mCurrentValuesMap.clear();
+        this.mTaskStatus = TaskStatus.UNINITIATED;
+    }
+
+    /**
+     * 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();
+        AppAction appAction = mActionSpec.convertPropertyToProto(mProperty);
+        if (anyParamsOfStatus(Status.REJECTED)
+                || !TaskCapabilityUtils.isSlotFillingComplete(finalArguments,
+                appAction.getParamsList())) {
+            return Futures.immediateFuture(FulfillmentResponse.getDefaultInstance());
+        }
+        if (mOnReadyToConfirmListener.isPresent()) {
+            return getFulfillmentResponseForConfirmation(finalArguments);
+        }
+        return getFulfillmentResponseForExecution(finalArguments);
+    }
+
+    private ListenableFuture<Void> maybeInitializeTask() {
+        if (this.mTaskStatus == TaskStatus.UNINITIATED && mOnInitListener.isPresent()) {
+            this.mTaskUpdater = mTaskUpdaterSupplier.get();
+            this.mTaskStatus = TaskStatus.IN_PROGRESS;
+            return mOnInitListener.get().onInit(this.mTaskUpdater);
+        }
+        this.mTaskStatus = TaskStatus.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.paramValues()),
+                        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.paramValues().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, mParamRegistry, 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 mOnReadyToConfirmListener.get().onReadyToConfirm(finalArguments);
+        } catch (StructConversionException | MissingRequiredArgException e) {
+            return Futures.immediateFailedFuture(e);
+        }
+    }
+
+    private ListenableFuture<ExecutionResult<OutputT>> executeOnTaskFinish(
+            Map<String, List<ParamValue>> finalArguments) {
+        ListenableFuture<ExecutionResult<OutputT>> finishListener;
+        try {
+            finishListener = mOnFinishListener.onFinish(mActionSpec.buildArgument(finalArguments));
+        } catch (StructConversionException e) {
+            return Futures.immediateFailedFuture(e);
+        }
+        return Futures.transform(
+                finishListener,
+                executionResult -> {
+                    this.mTaskStatus = TaskStatus.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 == TaskStatus.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() {
+        AppAction appActionWithoutState = mActionSpec.convertPropertyToProto(mProperty);
+        return appActionWithoutState.toBuilder()
+                .clearParams()
+                .addAllParams(addStateToParamsContext(appActionWithoutState.getParamsList()))
+                .setTaskInfo(TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                .setIdentifier(mIdentifier)
+                .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 || output instanceof Void) {
+            return Optional.empty();
+        }
+
+        StructuredOutput.Builder executionOutputBuilder = StructuredOutput.newBuilder();
+        for (Map.Entry<String, Function<OutputT, List<ParamValue>>> entry :
+                mExecutionOutputBindings.entrySet()) {
+            executionOutputBuilder.addOutputValues(
+                    StructuredOutput.OutputValue.newBuilder()
+                            .setName(entry.getKey())
+                            .addAllValues(entry.getValue().apply(output))
+                            .build());
+        }
+        return Optional.of(executionOutputBuilder.build());
+    }
+
+    /**
+     * 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 :
+                mConfirmationOutputBindings.entrySet()) {
+            confirmationOutputBuilder.addOutputValues(
+                    StructuredOutput.OutputValue.newBuilder()
+                            .setName(entry.getKey())
+                            .addAllValues(entry.getValue().apply(confirmation))
+                            .build());
+        }
+        return Optional.of(confirmationOutputBuilder.build());
+    }
+
+    /** State of the task internal to this capability. */
+    private enum TaskStatus {
+        UNINITIATED,
+        IN_PROGRESS,
+        COMPLETED
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamBinding.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamBinding.java
new file mode 100644
index 0000000..ef18dea
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamBinding.java
@@ -0,0 +1,74 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.converters.DisambigEntityConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.SearchActionConverter;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+import java.util.function.Predicate;
+
+/**
+ * A binding between a parameter and its Property converter / Argument setter.
+ *
+ * @param <ValueTypeT>
+ */
+@AutoValue
+public abstract class TaskParamBinding<ValueTypeT> {
+
+    /** Create a TaskParamBinding for a slot. */
+    static <ValueTypeT> TaskParamBinding<ValueTypeT> create(
+            String name,
+            Predicate<ParamValue> groundingPredicate,
+            GenericResolverInternal<ValueTypeT> resolver,
+            ParamValueConverter<ValueTypeT> converter,
+            Optional<DisambigEntityConverter<ValueTypeT>> entityConverter,
+            Optional<SearchActionConverter<ValueTypeT>> searchActionConverter) {
+        return new AutoValue_TaskParamBinding<>(
+                name, groundingPredicate, resolver, converter, entityConverter,
+                searchActionConverter);
+    }
+
+    /** Returns the name of this param. */
+    @NonNull
+    public abstract String name();
+
+    /** Tests whether the ParamValue requires app-driven grounding or not. */
+    @NonNull
+    public abstract Predicate<ParamValue> groundingPredicate();
+
+    /** Stores concrete resolver for the slot. */
+    @NonNull
+    public abstract GenericResolverInternal<ValueTypeT> resolver();
+
+    /** Converts from internal {@code ParamValue} proto to public {@code ValueTypeT}. */
+    @NonNull
+    public abstract ParamValueConverter<ValueTypeT> converter();
+
+    /** Converts from the {@code ValueTypeT} to app-driven disambig entities i.e. {@code Entity}. */
+    @NonNull
+    public abstract Optional<DisambigEntityConverter<ValueTypeT>> entityConverter();
+
+    /** Converts an ungrounded {@code ParamValue} to a {@code SearchAction} object. */
+    @NonNull
+    public abstract Optional<SearchActionConverter<ValueTypeT>> searchActionConverter();
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamRegistry.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamRegistry.java
new file mode 100644
index 0000000..4db5a2b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskParamRegistry.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.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.converters.DisambigEntityConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.ParamValueConverter;
+import androidx.appactions.interaction.capabilities.core.impl.converters.SearchActionConverter;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Collections;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Optional;
+import java.util.function.Predicate;
+
+/** Bindings for grounding and resolving arguments. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class TaskParamRegistry {
+
+    @NonNull
+    public static Builder builder() {
+        return new AutoValue_TaskParamRegistry.Builder();
+    }
+
+    /** Map of argument name to param binding. */
+    @NonNull
+    public abstract Map<String, TaskParamBinding<?>> bindings();
+
+    /** Builder for the TaskParamRegistry. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+        private final Map<String, TaskParamBinding<?>> mBindings = new HashMap<>();
+
+        abstract Builder setBindings(Map<String, TaskParamBinding<?>> bindings);
+
+        /**
+         * Register some slot related objects and method references.
+         *
+         * @param paramName          the slot name.
+         * @param groundingPredicate a function that returns true if ParamValue needs grounding,
+         *                           false
+         *                           otherwise.
+         * @param resolver           the GenericResolverInternal instance wrapping developer's slot
+         *                           listener
+         * @param entityConverter    a function that converts developer provided grounded objects
+         *                           to Entity proto
+         * @param searchActionConverter
+         * @param typeConverter      a function that converts a single ParamValue to some
+         *                           developer-facing object type
+         * @return
+         * @param <T>
+         */
+        @NonNull
+        public final <T> Builder addTaskParameter(
+                @NonNull String paramName,
+                @NonNull Predicate<ParamValue> groundingPredicate,
+                @NonNull GenericResolverInternal<T> resolver,
+                @NonNull Optional<DisambigEntityConverter<T>> entityConverter,
+                @NonNull Optional<SearchActionConverter<T>> searchActionConverter,
+                @NonNull ParamValueConverter<T> typeConverter) {
+            mBindings.put(
+                    paramName,
+                    TaskParamBinding.create(
+                            paramName,
+                            groundingPredicate,
+                            resolver,
+                            typeConverter,
+                            entityConverter,
+                            searchActionConverter));
+            return this;
+        }
+
+        abstract TaskParamRegistry autoBuild();
+
+        @NonNull
+        public TaskParamRegistry build() {
+            setBindings(Collections.unmodifiableMap(mBindings));
+            return autoBuild();
+        }
+    }
+}
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
new file mode 100644
index 0000000..841d860
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessor.java
@@ -0,0 +1,345 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.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.resolver();
+        if (!binding.entityConverter().isPresent()) {
+            return Futures.immediateFailedFuture(
+                    new MissingEntityConverterException(
+                            "No entity converter found in the binding."));
+        }
+        if (!binding.searchActionConverter().isPresent()) {
+            return Futures.immediateFailedFuture(
+                    new MissingSearchActionConverterException(
+                            "No search action converter found in the binding."));
+        }
+        DisambigEntityConverter<T> entityConverter = binding.entityConverter().get();
+        SearchActionConverter<T> searchActionConverter = binding.searchActionConverter().get();
+        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.resolver().notifyValueChange(updatedValue, binding.converter());
+        } 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,
+            TaskParamRegistry taskParamRegistry,
+            Executor executor) {
+        TaskParamBinding<?> taskParamBinding = taskParamRegistry.bindings().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.groundingPredicate().test(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.possibleValues().size()) {
+            case 0:
+                return AppGroundingResult.ofFailure(
+                        TaskCapabilityUtils.toCurrentValue(ungroundedValue, Status.REJECTED));
+            case 1:
+                Entity groundedEntity =
+                        entityConverter.convert(
+                                Objects.requireNonNull(entitySearchResult.possibleValues().get(0)));
+                return AppGroundingResult.ofSuccess(
+                        TaskCapabilityUtils.groundedValueToParamValue(groundedEntity));
+            default:
+                List<Entity> disambigEntities =
+                        getDisambigEntities(entitySearchResult.possibleValues(), 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.resolver().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/TaskUpdateHandler.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskUpdateHandler.java
new file mode 100644
index 0000000..d769e619
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskUpdateHandler.java
@@ -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.appactions.interaction.capabilities.core.task.impl;
+
+import androidx.appactions.interaction.proto.ParamValue;
+
+import java.util.List;
+import java.util.Map;
+
+/** Implemented by TaskCapabilityImpl to handle manual input updates and BIC input. */
+interface TaskUpdateHandler {
+    void updateParamValues(Map<String, List<ParamValue>> paramValuesMap);
+}
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
new file mode 100644
index 0000000..b9bf730
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/UpdateRequest.java
@@ -0,0 +1,42 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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/exceptions/DisambigStateException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/DisambigStateException.java
new file mode 100644
index 0000000..b993114
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/DisambigStateException.java
@@ -0,0 +1,31 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.exceptions;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Represents an internal issue with the state sync between the SDK and Assistant. One example is
+ * when the SDK places an argument in dismabig state, but then Assistant sends the same argument
+ * data again without any grounding.
+ */
+public final class DisambigStateException extends Exception {
+
+    public DisambigStateException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/InvalidResolverException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/InvalidResolverException.java
new file mode 100644
index 0000000..24e4e77
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/InvalidResolverException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task.impl.exceptions;
+
+import androidx.annotation.NonNull;
+
+/**
+ * Represents an internal issue with Resolvers, such as an "app-driven" method being invoked on a
+ * "assistant-driven" resolver.
+ */
+public final class InvalidResolverException extends Exception {
+
+    public InvalidResolverException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingEntityConverterException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingEntityConverterException.java
new file mode 100644
index 0000000..6f7808a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingEntityConverterException.java
@@ -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.appactions.interaction.capabilities.core.task.impl.exceptions;
+
+import androidx.annotation.NonNull;
+
+/** No entity converter is present in the {@code TaskParamBinding}. */
+public final class MissingEntityConverterException extends Exception {
+
+    public MissingEntityConverterException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingRequiredArgException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingRequiredArgException.java
new file mode 100644
index 0000000..c2af79c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingRequiredArgException.java
@@ -0,0 +1,30 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task.impl.exceptions;
+
+import androidx.annotation.NonNull;
+
+/**
+ * During the onFinishListener handling, all required params should be present in the Map sent to
+ * the listener. If they are not for some reason, this is an internal error.
+ */
+public final class MissingRequiredArgException extends Exception {
+
+    public MissingRequiredArgException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingSearchActionConverterException.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingSearchActionConverterException.java
new file mode 100644
index 0000000..9d8b690
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/task/impl/exceptions/MissingSearchActionConverterException.java
@@ -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.appactions.interaction.capabilities.core.task.impl.exceptions;
+
+import androidx.annotation.NonNull;
+
+/** No SearchAction converter is present in the {@code DialogParamBinding}. */
+public final class MissingSearchActionConverterException extends Exception {
+
+    public MissingSearchActionConverterException(@NonNull String message) {
+        super(message);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Alarm.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Alarm.java
new file mode 100644
index 0000000..ca545f1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Alarm.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents a single item in an item list. */
+@AutoValue
+public abstract class Alarm extends Thing {
+
+    /** Create a new Alarm instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Alarm.Builder();
+    }
+
+    /** Builder class for Alarm entities. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder> implements
+            BuilderOf<Alarm> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/CalendarEvent.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/CalendarEvent.java
new file mode 100644
index 0000000..d35dcf1
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/CalendarEvent.java
@@ -0,0 +1,114 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.properties.Attendee;
+
+import com.google.auto.value.AutoValue;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/** Represents a CalendarEvent. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class CalendarEvent extends Thing {
+    /** Create a new CalendarEvent.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_CalendarEvent.Builder();
+    }
+
+    /** Returns the start date. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getStartDate();
+
+    /** Returns the end date. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getEndDate();
+
+    /** Returns the {@link Attendee}s in the CalendarEvent. */
+    @NonNull
+    public abstract List<Attendee> getAttendeeList();
+
+    /** Builder class for building a CalendarEvent. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<CalendarEvent> {
+
+        private final List<Attendee> mAttendeeList = new ArrayList<>();
+
+        /** Sets start date. */
+        @NonNull
+        public abstract Builder setStartDate(@NonNull ZonedDateTime startDate);
+
+        /** Sets end date. */
+        @NonNull
+        public abstract Builder setEndDate(@NonNull ZonedDateTime endDate);
+
+        /** Adds a person. */
+        @NonNull
+        public final Builder addAttendee(@NonNull Person person) {
+            mAttendeeList.add(new Attendee(person));
+            return this;
+        }
+
+        /** Adds a Attendee. */
+        @NonNull
+        public final Builder addAttendee(@NonNull Attendee attendee) {
+            mAttendeeList.add(attendee);
+            return this;
+        }
+
+        /** Add a list of attendees. */
+        @NonNull
+        public final Builder addAllAttendee(@NonNull Iterable<Attendee> attendees) {
+            for (Attendee attendee : attendees) {
+                mAttendeeList.add(attendee);
+            }
+            return this;
+        }
+
+        /** Add a list of persons. */
+        @NonNull
+        public final Builder addAllPerson(@NonNull Iterable<Person> persons) {
+            for (Person person : persons) {
+                mAttendeeList.add(new Attendee(person));
+            }
+            return this;
+        }
+
+        /** Builds and returns the CalendarEvent instance. */
+        @Override
+        @NonNull
+        public final CalendarEvent build() {
+            setAttendeeList(mAttendeeList);
+            return autoBuild();
+        }
+
+        /** Sets the attendees of the CalendarEvent. */
+        @NonNull
+        abstract Builder setAttendeeList(@NonNull List<Attendee> attendeeList);
+
+        @NonNull
+        abstract CalendarEvent autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Call.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Call.java
new file mode 100644
index 0000000..5698873
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Call.java
@@ -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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.properties.Participant;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/** Represents a Call. */
+@AutoValue
+public abstract class Call extends Thing {
+    /** Create a new Call.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Call.Builder();
+    }
+
+    /** Returns the call format, e.g. video or audio. */
+    @NonNull
+    public abstract Optional<CallFormat> getCallFormat();
+
+    /** Returns the {@link Participant}s in the call. */
+    @NonNull
+    @SuppressWarnings("AutoValueImmutableFields")
+    public abstract List<Participant> getParticipantList();
+
+    /** Format of the call. */
+    public enum CallFormat {
+        AUDIO("Audio"),
+        VIDEO("Video");
+
+        private final String mCallFormat;
+
+        CallFormat(String callFormat) {
+            this.mCallFormat = callFormat;
+        }
+
+        @Override
+        public String toString() {
+            return mCallFormat;
+        }
+    }
+
+    /** Builder class for building a Call. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder> implements BuilderOf<Call> {
+
+        private final List<Participant> mParticipantList = new ArrayList<>();
+
+        /** Sets call format. */
+        @NonNull
+        public abstract Builder setCallFormat(@NonNull CallFormat callFormat);
+
+        /** Adds a person. */
+        @NonNull
+        public final Builder addParticipant(@NonNull Person person) {
+            mParticipantList.add(new Participant(person));
+            return this;
+        }
+
+        /** Adds a Participant. */
+        @NonNull
+        public final Builder addParticipant(@NonNull Participant participant) {
+            mParticipantList.add(participant);
+            return this;
+        }
+
+        /** Add a list of participants. */
+        @NonNull
+        public final Builder addAllParticipant(@NonNull Iterable<Participant> participants) {
+            for (Participant participant : participants) {
+                mParticipantList.add(participant);
+            }
+            return this;
+        }
+
+        /** Add a list of persons. */
+        @NonNull
+        public final Builder addAllPerson(@NonNull Iterable<Person> persons) {
+            for (Person person : persons) {
+                mParticipantList.add(new Participant(person));
+            }
+            return this;
+        }
+
+        /** Builds and returns the Call instance. */
+        @Override
+        @NonNull
+        public final Call build() {
+            setParticipantList(mParticipantList);
+            return autoBuild();
+        }
+
+        /** Sets the participants of the call. */
+        @NonNull
+        abstract Builder setParticipantList(@NonNull List<Participant> participantList);
+
+        @NonNull
+        abstract Call autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java
new file mode 100644
index 0000000..690c591
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/EntityValue.java
@@ -0,0 +1,73 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.values;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/**
+ * Represents an entity value for {@code ActionCapability} which includes a value and optionally an
+ * id.
+ */
+@AutoValue
+public abstract class EntityValue {
+
+    /** Returns a new Builder to build a EntityValue. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_EntityValue.Builder();
+    }
+
+    /** Returns a EntityValue that has both its id and value set to the given identifier. */
+    @NonNull
+    public static EntityValue ofId(@NonNull String id) {
+        return EntityValue.newBuilder().setId(id).setValue(id).build();
+    }
+
+    /** Returns a EntityValue that has the given value and no id. */
+    @NonNull
+    public static EntityValue ofValue(@NonNull String value) {
+        return EntityValue.newBuilder().setValue(value).build();
+    }
+
+    /** Returns the id of the EntityValue. */
+    @NonNull
+    public abstract Optional<String> getId();
+
+    /** Returns the value of the EntityValue. */
+    @NonNull
+    public abstract String getValue();
+
+    /** Builder for {@link EntityValue}. */
+    @AutoValue.Builder
+    public abstract static class Builder {
+        /** Sets the identifier of the EntityValue to be built. */
+        @NonNull
+        public abstract Builder setId(@NonNull String id);
+
+        /** Sets The value of the EntityValue to be built. */
+        @NonNull
+        public abstract Builder setValue(@NonNull String value);
+
+        /** Builds and returns the EntityValue. */
+        @NonNull
+        public abstract EntityValue build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/GenericErrorStatus.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/GenericErrorStatus.java
new file mode 100644
index 0000000..a7c0713
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/GenericErrorStatus.java
@@ -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.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** GenericErrorStatus for ExecutionStatus. */
+@AutoValue
+public abstract class GenericErrorStatus extends Thing {
+
+    /** Create a new GenericErrorStatus instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_GenericErrorStatus.Builder();
+    }
+
+    /** Create a new default instance. */
+    @NonNull
+    public static GenericErrorStatus getDefaultInstance() {
+        return new AutoValue_GenericErrorStatus.Builder().build();
+    }
+
+    @Override
+    public final String toString() {
+        return "GenericErrorStatus";
+    }
+
+    /** Builder class for GenericErrorStatus status. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<GenericErrorStatus> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ItemList.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ItemList.java
new file mode 100644
index 0000000..041663d
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ItemList.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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.List;
+
+/** Represents an ItemList object. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class ItemList extends Thing {
+
+    /** Create a new ItemList.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_ItemList.Builder();
+    }
+
+    /** Returns the optional list items in this ItemList. */
+    @NonNull
+    public abstract List<ListItem> getListItems();
+
+    /** Builder class for building item lists with items. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<ItemList> {
+
+        private final List<ListItem> mListItemsToBuild = new ArrayList<>();
+
+        /** Add one or more ListItem to the ItemList to be built. */
+        @NonNull
+        public final Builder addListItem(@NonNull ListItem... listItems) {
+            Collections.addAll(mListItemsToBuild, listItems);
+            return this;
+        }
+
+        /** Add a list of ListItem to the ItemList to be built. */
+        @NonNull
+        public final Builder addAllListItems(@NonNull List<ListItem> listItems) {
+            return addListItem(listItems.toArray(ListItem[]::new));
+        }
+
+        abstract Builder setListItems(List<ListItem> listItems);
+
+        /** Builds and returns the ItemList. */
+        @Override
+        @NonNull
+        public final ItemList build() {
+            setListItems(Collections.unmodifiableList(mListItemsToBuild));
+            return autoBuild();
+        }
+
+        @NonNull
+        abstract ItemList autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ListItem.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ListItem.java
new file mode 100644
index 0000000..e79e99f
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ListItem.java
@@ -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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents a single item in an item list. */
+@AutoValue
+public abstract class ListItem extends Thing {
+
+    /** Creates a ListItem given its name. */
+    @NonNull
+    public static ListItem create(@NonNull String id, @NonNull String name) {
+        return newBuilder().setId(id).setName(name).build();
+    }
+
+    /** Create a new ListItem.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_ListItem.Builder();
+    }
+
+    /** Builder class for building item lists with items. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<ListItem> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Message.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Message.java
new file mode 100644
index 0000000..7e5d875
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Message.java
@@ -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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.properties.Recipient;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/** Represents an message object. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class Message extends Thing {
+
+    /** Create a new Message.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Message.Builder();
+    }
+
+    /** Returns the recipients of the message. */
+    @NonNull
+    public abstract List<Recipient> getRecipientList();
+
+    /** Returns the message text. */
+    @NonNull
+    public abstract Optional<String> getMessageText();
+
+    /** Builder class for building an Message. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<Message> {
+
+        private final List<Recipient> mRecipientList = new ArrayList<>();
+
+        /** Adds a {@link Person}. */
+        @NonNull
+        public final Builder addRecipient(@NonNull Person person) {
+            mRecipientList.add(new Recipient(person));
+            return this;
+        }
+
+        /** Adds a {@link Recipient}. */
+        @NonNull
+        public final Builder addRecipient(@NonNull Recipient recipient) {
+            mRecipientList.add(recipient);
+            return this;
+        }
+
+        /** Adds a list of {@link Recipient}s. */
+        @NonNull
+        public final Builder addAllRecipient(@NonNull Iterable<Recipient> recipients) {
+            for (Recipient recipient : recipients) {
+                mRecipientList.add(recipient);
+            }
+            return this;
+        }
+
+        /** Sets the message text. */
+        @NonNull
+        public abstract Builder setMessageText(@NonNull String messageText);
+
+        /** Builds and returns the Message instance. */
+        @Override
+        @NonNull
+        public final Message build() {
+            setRecipientList(mRecipientList);
+            return autoBuild();
+        }
+
+        /** Sets the recipients of the message. */
+        @NonNull
+        abstract Builder setRecipientList(@NonNull List<Recipient> recipientList);
+
+        @NonNull
+        abstract Message autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Order.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Order.java
new file mode 100644
index 0000000..2f44202
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Order.java
@@ -0,0 +1,136 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.time.ZonedDateTime;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.Optional;
+
+/** Represents an order object. */
+@SuppressWarnings("AutoValueImmutableFields")
+@AutoValue
+public abstract class Order extends Thing {
+
+    /** Create a new Order.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Order.Builder();
+    }
+
+    /** Returns the date the order was placed. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getOrderDate();
+
+    /** Returns the {@link OrderItem}s in the order. */
+    @NonNull
+    public abstract List<OrderItem> getOrderedItems();
+
+    /** Returns the current status of the order. */
+    @NonNull
+    public abstract Optional<OrderStatus> getOrderStatus();
+
+    /** Returns the name of the seller. */
+    @NonNull
+    public abstract Optional<Organization> getSeller();
+
+    /** Returns the delivery information. */
+    @NonNull
+    public abstract Optional<ParcelDelivery> getOrderDelivery();
+
+    /** Status of the order. */
+    public enum OrderStatus {
+        ORDER_CANCELED("OrderCanceled"),
+        ORDER_DELIVERED("OrderDelivered"),
+        ORDER_IN_TRANSIT("OrderInTransit"),
+        ORDER_PAYMENT_DUE("OrderPaymentDue"),
+        ORDER_PICKUP_AVAILABLE("OrderPickupAvailable"),
+        ORDER_PROBLEM("OrderProblem"),
+        ORDER_PROCESSING("OrderProcessing"),
+        ORDER_RETURNED("OrderReturned");
+
+        private final String mStringValue;
+
+        OrderStatus(String stringValue) {
+            this.mStringValue = stringValue;
+        }
+
+        @Override
+        public String toString() {
+            return mStringValue;
+        }
+    }
+
+    /** Builder class for building an Order. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder> implements
+            BuilderOf<Order> {
+
+        /** Order items to build. */
+        private final List<OrderItem> mOrderItems = new ArrayList<>();
+
+        /** Sets the date the order was placed. */
+        @NonNull
+        public abstract Builder setOrderDate(@NonNull ZonedDateTime orderDate);
+
+        /** Sets the ordered items. */
+        @NonNull
+        abstract Builder setOrderedItems(@NonNull List<OrderItem> orderItems);
+
+        /** Adds an item to the order. */
+        @NonNull
+        public final Builder addOrderedItem(@NonNull OrderItem orderItem) {
+            mOrderItems.add(orderItem);
+            return this;
+        }
+
+        /** Add a list of OrderItem. */
+        @NonNull
+        public final Builder addAllOrderedItems(@NonNull List<OrderItem> orderItems) {
+            this.mOrderItems.addAll(orderItems);
+            return this;
+        }
+
+        /** Sets the current order status. */
+        @NonNull
+        public abstract Builder setOrderStatus(@NonNull OrderStatus orderStatus);
+
+        /** Sets the name of the seller. */
+        @NonNull
+        public abstract Builder setSeller(@NonNull Organization seller);
+
+        /** Sets the order delivery. */
+        @NonNull
+        public abstract Builder setOrderDelivery(@NonNull ParcelDelivery parcelDelivery);
+
+        /** Builds and returns the Order instance. */
+        @Override
+        @NonNull
+        public final Order build() {
+            setOrderedItems(mOrderItems);
+            return autoBuild();
+        }
+
+        @NonNull
+        abstract Order autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/OrderItem.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/OrderItem.java
new file mode 100644
index 0000000..0475268
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/OrderItem.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents an item in an order. */
+@AutoValue
+public abstract class OrderItem extends Thing {
+
+    /** Create a new OrderItem.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_OrderItem.Builder();
+    }
+
+    /** Builder class for building an OrderItem. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<OrderItem> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Organization.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Organization.java
new file mode 100644
index 0000000..9f5171a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Organization.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents an organization. */
+@AutoValue
+public abstract class Organization extends Thing {
+
+    /** Create a new Organization.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Organization.Builder();
+    }
+
+    /** Builder class for building an Organization. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<Organization> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ParcelDelivery.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ParcelDelivery.java
new file mode 100644
index 0000000..a2437cd
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/ParcelDelivery.java
@@ -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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+/** The delivery of a parcel. */
+@AutoValue
+public abstract class ParcelDelivery extends Thing {
+
+    /** Create a new ParcelDelivery.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_ParcelDelivery.Builder();
+    }
+
+    /** Returns the delivery address. */
+    @NonNull
+    public abstract Optional<String> getDeliveryAddress();
+
+    /** Returns the method used for delivery or shipping. */
+    @NonNull
+    public abstract Optional<String> getDeliveryMethod();
+
+    /** Returns the earliest date the package may arrive. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getExpectedArrivalFrom();
+
+    /** Returns the latest date the package may arrive. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getExpectedArrivalUntil();
+
+    /** Returns the tracking number. */
+    @NonNull
+    public abstract Optional<String> getTrackingNumber();
+
+    /** Returns the tracking URL. */
+    @NonNull
+    public abstract Optional<String> getTrackingUrl();
+
+    /** Builder class for building ParcelDelivery. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<ParcelDelivery> {
+
+        /** Sets the delivery address. */
+        @NonNull
+        public abstract Builder setDeliveryAddress(@NonNull String deliveryAddress);
+
+        /** Sets the delivery method. */
+        @NonNull
+        public abstract Builder setDeliveryMethod(@NonNull String deliveryMethod);
+
+        /** Sets the earliest date the package may arrive. */
+        @NonNull
+        public abstract Builder setExpectedArrivalFrom(@NonNull ZonedDateTime expectedArrivalFrom);
+
+        /** Sets the latest date the package may arrive. */
+        @NonNull
+        public abstract Builder setExpectedArrivalUntil(
+                @NonNull ZonedDateTime expectedArrivalUntil);
+
+        /** Sets the tracking number. */
+        @NonNull
+        public abstract Builder setTrackingNumber(@NonNull String trackingNumber);
+
+        /** Sets the tracking URL. */
+        @NonNull
+        public abstract Builder setTrackingUrl(@NonNull String trackingUrl);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Person.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Person.java
new file mode 100644
index 0000000..a54bb59
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Person.java
@@ -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.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/** Represents a person. */
+@AutoValue
+public abstract class Person extends Thing {
+    /** Create a new Person.Builder instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Person.Builder();
+    }
+
+    /** Returns the email. */
+    @NonNull
+    public abstract Optional<String> getEmail();
+
+    /** Returns the telephone. */
+    @NonNull
+    public abstract Optional<String> getTelephone();
+
+    /** Builder class for building a Person. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder> implements
+            BuilderOf<Person> {
+        /** Sets the email. */
+        @NonNull
+        public abstract Builder setEmail(@NonNull String email);
+
+        /** Sets the telephone. */
+        @NonNull
+        public abstract Builder setTelephone(@NonNull String telephone);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SafetyCheck.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SafetyCheck.java
new file mode 100644
index 0000000..afe240c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SafetyCheck.java
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+/** Represents a SafetyCheck. */
+@AutoValue
+public abstract class SafetyCheck extends Thing {
+    /** Create a new SafetyCheck instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_SafetyCheck.Builder();
+    }
+
+    /** Returns the duration. */
+    @NonNull
+    public abstract Optional<Duration> getDuration();
+
+    /** Returns the check-in time. */
+    @NonNull
+    public abstract Optional<ZonedDateTime> getCheckinTime();
+
+    /** Builder class for building a SafetyCheck. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<SafetyCheck> {
+        @NonNull
+        public abstract Builder setDuration(@NonNull Duration duration);
+
+        @NonNull
+        public abstract Builder setCheckinTime(@NonNull ZonedDateTime checkinTime);
+
+        /** Builds and returns the SafetyCheck instance. */
+        @Override
+        @NonNull
+        public final SafetyCheck build() {
+            return autoBuild();
+        }
+
+        @NonNull
+        abstract SafetyCheck autoBuild();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SearchAction.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SearchAction.java
new file mode 100644
index 0000000..1e472320
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SearchAction.java
@@ -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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/**
+ * Represents a request to perform search for some in-app entities.
+ *
+ * @param <T>
+ */
+@AutoValue
+public abstract class SearchAction<T> {
+
+    /** Returns a new Builder instance for SearchAction. */
+    @NonNull
+    public static <T> Builder<T> newBuilder() {
+        return new AutoValue_SearchAction.Builder<>();
+    }
+
+    /** The String query of this SearchAction. */
+    @NonNull
+    public abstract Optional<String> getQuery();
+
+    /** The object to search for of this SearchAction. */
+    @NonNull
+    public abstract Optional<T> getObject();
+
+    /**
+     * Builder class for SearchAction.
+     *
+     * @param <T>
+     */
+    @AutoValue.Builder
+    public abstract static class Builder<T> implements BuilderOf<SearchAction<T>> {
+        /** Sets the String query of this SearchAction. */
+        @NonNull
+        public abstract Builder<T> setQuery(@NonNull String query);
+
+        /** Sets the Object query of this SearchAction. */
+        @NonNull
+        public abstract Builder<T> setObject(T object);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/StringOrEnumValue.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/StringOrEnumValue.java
new file mode 100644
index 0000000..2c41ecb
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/StringOrEnumValue.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.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+
+import com.google.auto.value.AutoOneOf;
+
+/**
+ * Represents a string or enum argument value for {@code ActionCapability}.
+ *
+ * @param <EnumT>
+ */
+@AutoOneOf(StringOrEnumValue.Kind.class)
+public abstract class StringOrEnumValue<EnumT extends Enum<EnumT>> {
+    /** Creates a StringOrEnumValue instance with the given String. */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> StringOrEnumValue<EnumT> ofStringValue(
+            @NonNull String s) {
+        return AutoOneOf_StringOrEnumValue.stringValue(s);
+    }
+
+    /** Creates a StringOrEnumValue instance with the given Enum value. */
+    @NonNull
+    public static <EnumT extends Enum<EnumT>> StringOrEnumValue<EnumT> ofEnumValue(
+            @NonNull EnumT enumValue) {
+        return AutoOneOf_StringOrEnumValue.enumValue(enumValue);
+    }
+
+    /** The Kind of this StringOrEnumValue. */
+    @NonNull
+    public abstract Kind getKind();
+
+    /** The String value of this StringOrEnumValue, for Kind.STRING_VALUE. */
+    @NonNull
+    public abstract String stringValue();
+
+    /** The Enum value of this StringOrEnumValue, for Kind.ENUM_VALUE. */
+    @NonNull
+    public abstract EnumT enumValue();
+
+    /** Possible argument type. */
+    public enum Kind {
+        STRING_VALUE,
+        ENUM_VALUE,
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SuccessStatus.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SuccessStatus.java
new file mode 100644
index 0000000..af4469a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/SuccessStatus.java
@@ -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.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** SuccessStatus for ExecutionStatus. */
+@AutoValue
+public abstract class SuccessStatus extends Thing {
+
+    /** Create a new SuccessStatus instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_SuccessStatus.Builder();
+    }
+
+    /** Create a new default instance. */
+    @NonNull
+    public static SuccessStatus getDefaultInstance() {
+        return new AutoValue_SuccessStatus.Builder().build();
+    }
+
+    @Override
+    public final String toString() {
+        return "SuccessStatus";
+    }
+
+    /** Builder class for SuccessStatus status. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<SuccessStatus> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Thing.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Thing.java
new file mode 100644
index 0000000..7d897f0
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Thing.java
@@ -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.values;
+
+import androidx.annotation.NonNull;
+
+import java.util.Optional;
+
+/** Common interface for structured entity. */
+public abstract class Thing {
+    /** Returns the id of this thing. */
+    @NonNull
+    public abstract Optional<String> getId();
+
+    /** Returns the name of this thing. */
+    @NonNull
+    public abstract Optional<String> getName();
+
+    /**
+     * Base builder class that can be extended to build objects that extend Thing.
+     *
+     * @param <T>
+     */
+    public abstract static class Builder<T extends Builder<T>> {
+        /** Sets the id of the Thing to be built. */
+        @NonNull
+        public abstract T setId(@NonNull String id);
+
+        /** Sets the name of the Thing to be built. */
+        @NonNull
+        public abstract T setName(@NonNull String name);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Timer.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Timer.java
new file mode 100644
index 0000000..80627b4
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/Timer.java
@@ -0,0 +1,39 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+/** Represents a Timer. */
+@AutoValue
+public abstract class Timer extends Thing {
+
+    /** Create a new Timer instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_Timer.Builder();
+    }
+
+    /** Builder class for Timer. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder> implements
+            BuilderOf<Timer> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionAlreadyInProgress.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionAlreadyInProgress.java
new file mode 100644
index 0000000..57fe650
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionAlreadyInProgress.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values.executionstatus;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.Thing;
+
+import com.google.auto.value.AutoValue;
+
+/** Error status for execution failure due to the action being in progress. */
+@AutoValue
+public abstract class ActionAlreadyInProgress extends Thing {
+
+    /** Create a new ActionAlreadyInProgress instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_ActionAlreadyInProgress.Builder();
+    }
+
+    /** Create a new default instance. */
+    @NonNull
+    public static ActionAlreadyInProgress getDefaultInstance() {
+        return new AutoValue_ActionAlreadyInProgress.Builder().build();
+    }
+
+    @Override
+    public final String toString() {
+        return "ActionAlreadyInProgress";
+    }
+
+    /** Builder class for ActionAlreadyInProgress status. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<ActionAlreadyInProgress> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionNotInProgress.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionNotInProgress.java
new file mode 100644
index 0000000..4b342a6
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/ActionNotInProgress.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values.executionstatus;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.Thing;
+
+import com.google.auto.value.AutoValue;
+
+/** Error status for execution failure due to the action not being in progress. */
+@AutoValue
+public abstract class ActionNotInProgress extends Thing {
+
+    /** Create a new ActionNotInProgress instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_ActionNotInProgress.Builder();
+    }
+
+    /** Create a new default instance. */
+    @NonNull
+    public static ActionNotInProgress getDefaultInstance() {
+        return new AutoValue_ActionNotInProgress.Builder().build();
+    }
+
+    @Override
+    public final String toString() {
+        return "ActionNotInProgress";
+    }
+
+    /** Builder class for ActionNotInProgress status. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<ActionNotInProgress> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/NoInternetConnection.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/NoInternetConnection.java
new file mode 100644
index 0000000..3a3bb5b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/executionstatus/NoInternetConnection.java
@@ -0,0 +1,51 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.values.executionstatus;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.Thing;
+
+import com.google.auto.value.AutoValue;
+
+/** Error status for execution failure due to no internet connection. */
+@AutoValue
+public abstract class NoInternetConnection extends Thing {
+
+    /** Create a new NoInternetConnection instance. */
+    @NonNull
+    public static Builder newBuilder() {
+        return new AutoValue_NoInternetConnection.Builder();
+    }
+
+    /** Create a new default instance. */
+    @NonNull
+    public static NoInternetConnection getDefaultInstance() {
+        return new AutoValue_NoInternetConnection.Builder().build();
+    }
+
+    @Override
+    public final String toString() {
+        return "NoInternetConnection";
+    }
+
+    /** Builder class for NoInternetConnection status. */
+    @AutoValue.Builder
+    public abstract static class Builder extends Thing.Builder<Builder>
+            implements BuilderOf<NoInternetConnection> {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Attendee.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Attendee.java
new file mode 100644
index 0000000..1c5ef74
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Attendee.java
@@ -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.appactions.interaction.capabilities.core.values.properties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.values.Person;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/**
+ * Represents the value of the union property: http://schema.org/attendee, currently it only can
+ * contain {@link Person}.
+ */
+public class Attendee {
+    private final Value mValue;
+
+    public Attendee(@NonNull Person person) {
+        mValue = new AutoValue_Attendee_Value(Optional.of(person));
+    }
+
+    @NonNull
+    public Optional<Person> asPerson() {
+        return mValue.person();
+    }
+
+    @Override
+    public int hashCode() {
+        return mValue.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object instanceof Attendee) {
+            Attendee that = (Attendee) object;
+            return this.mValue.equals(that.mValue);
+        }
+        return false;
+    }
+
+    /** Represents the value in the this wrapper class. */
+    @AutoValue
+    abstract static class Value {
+        abstract Optional<Person> person();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Participant.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Participant.java
new file mode 100644
index 0000000..02cbc7b
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Participant.java
@@ -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.appactions.interaction.capabilities.core.values.properties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.values.Person;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/**
+ * Represents the value of the union property: http://schema.org/participant, currently it only can
+ * contain {@link Person}.
+ */
+public class Participant {
+    private final Value mValue;
+
+    public Participant(@NonNull Person person) {
+        mValue = new AutoValue_Participant_Value(Optional.of(person));
+    }
+
+    @NonNull
+    public Optional<Person> asPerson() {
+        return mValue.person();
+    }
+
+    @Override
+    public int hashCode() {
+        return mValue.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object instanceof Participant) {
+            Participant that = (Participant) object;
+            return this.mValue.equals(that.mValue);
+        }
+        return false;
+    }
+
+    /** Represents the value in the this wrapper class. */
+    @AutoValue
+    abstract static class Value {
+        abstract Optional<Person> person();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Recipient.java b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Recipient.java
new file mode 100644
index 0000000..56d2021
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/main/java/androidx/appactions/interaction/capabilities/core/values/properties/Recipient.java
@@ -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.appactions.interaction.capabilities.core.values.properties;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.appactions.interaction.capabilities.core.values.Person;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/**
+ * Represents the value of the union property: http://schema.org/recipient, currently it only can
+ * contain {@link Person}.
+ */
+public class Recipient {
+    private final Value mValue;
+
+    public Recipient(@NonNull Person person) {
+        mValue = new AutoValue_Recipient_Value(Optional.of(person));
+    }
+
+    @NonNull
+    public Optional<Person> asPerson() {
+        return mValue.person();
+    }
+
+    @Override
+    public int hashCode() {
+        return mValue.hashCode();
+    }
+
+    @Override
+    public boolean equals(@Nullable Object object) {
+        if (object instanceof Recipient) {
+            Recipient that = (Recipient) object;
+            return this.mValue.equals(that.mValue);
+        }
+        return false;
+    }
+
+    /** Represents the value in the this wrapper class. */
+    @AutoValue
+    abstract static class Value {
+        abstract Optional<Person> person();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FuturesTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FuturesTest.java
new file mode 100644
index 0000000..0016b7e
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/concurrent/FuturesTest.java
@@ -0,0 +1,224 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.impl.concurrent;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.testing.spec.SettableFutureWrapper;
+import androidx.test.filters.SdkSuppress;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.concurrent.ExecutionException;
+import java.util.function.Function;
+
+@RunWith(JUnit4.class)
+public final class FuturesTest {
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void addCallback_onSuccess() throws Exception {
+        SettableFutureWrapper<Integer> integerFutureWrapper = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Boolean> testFutureWrapper = new SettableFutureWrapper<>();
+        Futures.addCallback(
+                integerFutureWrapper.getFuture(),
+                new FutureCallback<Integer>() {
+                    @Override
+                    public void onSuccess(Integer value) {
+                        assertThat(value).isEqualTo(25);
+                        testFutureWrapper.set(true);
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        testFutureWrapper.set(false);
+                    }
+                },
+                Runnable::run);
+
+        assertThat(testFutureWrapper.getFuture().isDone()).isFalse();
+        integerFutureWrapper.set(25);
+        assertThat(testFutureWrapper.getFuture().get()).isTrue();
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void addCallback_onFailure() throws Exception {
+        SettableFutureWrapper<Object> objectFutureWrapper = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Boolean> testFutureWrapper = new SettableFutureWrapper<>();
+        Futures.addCallback(
+                objectFutureWrapper.getFuture(),
+                new FutureCallback<Object>() {
+                    @Override
+                    public void onSuccess(Object value) {
+                        testFutureWrapper.set(false);
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        assertThat(t).isInstanceOf(IllegalStateException.class);
+                        testFutureWrapper.set(true);
+                    }
+                },
+                Runnable::run);
+
+        assertThat(testFutureWrapper.getFuture().isDone()).isFalse();
+        objectFutureWrapper.setException(new IllegalStateException());
+        assertThat(testFutureWrapper.getFuture().get()).isTrue();
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void transform_success() throws Exception {
+        SettableFutureWrapper<Integer> integerFutureWrapper = new SettableFutureWrapper<>();
+        ListenableFuture<Integer> transformedFuture =
+                Futures.transform(integerFutureWrapper.getFuture(), (x) -> x + 10, Runnable::run,
+                        "add 10");
+
+        assertThat(transformedFuture.isDone()).isFalse();
+        integerFutureWrapper.set(25);
+        assertThat(transformedFuture.get()).isEqualTo(35);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void transformAsync_success() throws Exception {
+        SettableFutureWrapper<Integer> integerFutureWrapper = new SettableFutureWrapper<>();
+        ListenableFuture<Integer> transformedFuture =
+                Futures.transformAsync(
+                        integerFutureWrapper.getFuture(),
+                        (x) -> {
+                            SettableFutureWrapper<Integer> transformFuture =
+                                    new SettableFutureWrapper<>();
+                            transformFuture.set(x + 10);
+                            return transformFuture.getFuture();
+                        },
+                        Runnable::run,
+                        "add 10 async");
+
+        assertThat(transformedFuture.isDone()).isFalse();
+        integerFutureWrapper.set(25);
+        assertThat(transformedFuture.get()).isEqualTo(35);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void immediateFuture_success() throws Exception {
+        ListenableFuture<Integer> immediateFuture = Futures.immediateFuture(25);
+        immediateFuture.cancel(true);
+        assertThat(immediateFuture.isCancelled()).isFalse();
+        assertThat(immediateFuture.isDone()).isTrue();
+        assertThat(immediateFuture.get()).isEqualTo(25);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void immediateVoidFuture_success() throws Exception {
+        ListenableFuture<Void> immediateVoidFuture = Futures.immediateVoidFuture();
+        immediateVoidFuture.cancel(true);
+        assertThat(immediateVoidFuture.isCancelled()).isFalse();
+        assertThat(immediateVoidFuture.isDone()).isTrue();
+        assertThat(immediateVoidFuture.get()).isEqualTo(null);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void immediateFailedFuture_failure() throws Exception {
+        ListenableFuture<Object> immediateFailedFuture =
+                Futures.immediateFailedFuture(new CustomException());
+        ListenableFuture<Object> transformedFuture =
+                Futures.transform(immediateFailedFuture, Function.identity(), Runnable::run,
+                        "test");
+
+        assertThat(transformedFuture.isDone()).isTrue();
+        ExecutionException e = assertThrows(ExecutionException.class, transformedFuture::get);
+        assertThat(e).hasCauseThat().isInstanceOf(CustomException.class);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void transform_synchronousExceptionPropagated() throws Exception {
+        Function<Integer, Integer> badTransform =
+                (unused) -> {
+                    throw new IllegalStateException();
+                };
+        ListenableFuture<Integer> transformedFuture =
+                Futures.transform(Futures.immediateFuture(25), badTransform, Runnable::run,
+                        "badTransform");
+        assertThat(transformedFuture.isDone()).isTrue();
+
+        SettableFutureWrapper<Throwable> errorContainer = new SettableFutureWrapper<>();
+        Futures.addCallback(
+                transformedFuture,
+                new FutureCallback<Integer>() {
+                    @Override
+                    public void onSuccess(Integer value) {
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        errorContainer.set(t);
+                    }
+                },
+                Runnable::run);
+        assertThat(errorContainer.getFuture().get()).isInstanceOf(IllegalStateException.class);
+        ExecutionException e = assertThrows(ExecutionException.class, transformedFuture::get);
+        assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
+    }
+
+    @SdkSuppress(minSdkVersion = 24)
+    @Test
+    public void transformAsync_synchronousExceptionPropagated() throws Exception {
+        Function<Integer, ListenableFuture<Integer>> badAsyncTransform =
+                (unused) -> {
+                    throw new IllegalStateException();
+                };
+        ListenableFuture<Integer> transformedFuture =
+                Futures.transformAsync(
+                        Futures.immediateFuture(25), badAsyncTransform, Runnable::run,
+                        "badAsyncTransform");
+        assertThat(transformedFuture.isDone()).isTrue();
+
+        SettableFutureWrapper<Throwable> errorContainer = new SettableFutureWrapper<>();
+        Futures.addCallback(
+                transformedFuture,
+                new FutureCallback<Integer>() {
+                    @Override
+                    public void onSuccess(Integer value) {
+                    }
+
+                    @Override
+                    public void onFailure(@NonNull Throwable t) {
+                        errorContainer.set(t);
+                    }
+                },
+                Runnable::run);
+        assertThat(errorContainer.getFuture().get()).isInstanceOf(IllegalStateException.class);
+        ExecutionException e = assertThrows(ExecutionException.class, transformedFuture::get);
+        assertThat(e).hasCauseThat().isInstanceOf(IllegalStateException.class);
+    }
+
+    private static class CustomException extends Exception {
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
new file mode 100644
index 0000000..2721638
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeConvertersTest.java
@@ -0,0 +1,869 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.values.Alarm;
+import androidx.appactions.interaction.capabilities.core.values.CalendarEvent;
+import androidx.appactions.interaction.capabilities.core.values.Call;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+import androidx.appactions.interaction.capabilities.core.values.ItemList;
+import androidx.appactions.interaction.capabilities.core.values.ListItem;
+import androidx.appactions.interaction.capabilities.core.values.Message;
+import androidx.appactions.interaction.capabilities.core.values.Order;
+import androidx.appactions.interaction.capabilities.core.values.OrderItem;
+import androidx.appactions.interaction.capabilities.core.values.Organization;
+import androidx.appactions.interaction.capabilities.core.values.ParcelDelivery;
+import androidx.appactions.interaction.capabilities.core.values.Person;
+import androidx.appactions.interaction.capabilities.core.values.SafetyCheck;
+import androidx.appactions.interaction.capabilities.core.values.SearchAction;
+import androidx.appactions.interaction.capabilities.core.values.Timer;
+import androidx.appactions.interaction.capabilities.core.values.properties.Participant;
+import androidx.appactions.interaction.capabilities.core.values.properties.Recipient;
+import androidx.appactions.interaction.proto.Entity;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.protobuf.ListValue;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.time.LocalDate;
+import java.time.LocalTime;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+import java.time.zone.ZoneRulesException;
+import java.util.Collections;
+import java.util.List;
+
+@RunWith(JUnit4.class)
+public final class TypeConvertersTest {
+
+    private static final Order ORDER_JAVA_THING =
+            Order.newBuilder()
+                    .setId("id")
+                    .setName("name")
+                    .addOrderedItem(OrderItem.newBuilder().setName("apples").build())
+                    .addOrderedItem(OrderItem.newBuilder().setName("oranges").build())
+                    .setSeller(Organization.newBuilder().setName("Google").build())
+                    .setOrderDate(ZonedDateTime.of(2022, 1, 1, 8, 0, 0, 0, ZoneOffset.UTC))
+                    .setOrderStatus(Order.OrderStatus.ORDER_DELIVERED)
+                    .setOrderDelivery(
+                            ParcelDelivery.newBuilder()
+                                    .setDeliveryAddress("test address")
+                                    .setDeliveryMethod("UPS")
+                                    .setTrackingNumber("A12345")
+                                    .setTrackingUrl("https://")
+                                    .build())
+                    .build();
+    private static final Person PERSON_JAVA_THING =
+            Person.newBuilder()
+                    .setName("name")
+                    .setEmail("email")
+                    .setTelephone("telephone")
+                    .setId("id")
+                    .build();
+    private static final Person PERSON_JAVA_THING_2 = Person.newBuilder().setId("id2").build();
+    private static final CalendarEvent CALENDAR_EVENT_JAVA_THING =
+            CalendarEvent.newBuilder()
+                    .setStartDate(ZonedDateTime.of(2022, 1, 1, 8, 0, 0, 0, ZoneOffset.UTC))
+                    .setEndDate(ZonedDateTime.of(2023, 1, 1, 8, 0, 0, 0, ZoneOffset.UTC))
+                    .addAttendee(PERSON_JAVA_THING)
+                    .addAttendee(PERSON_JAVA_THING_2)
+                    .build();
+    private static final Call CALL_JAVA_THING =
+            Call.newBuilder()
+                    .setId("id")
+                    .setCallFormat(Call.CallFormat.AUDIO)
+                    .addParticipant(PERSON_JAVA_THING)
+                    .build();
+    private static final Message MESSAGE_JAVA_THING =
+            Message.newBuilder()
+                    .setId("id")
+                    .addRecipient(PERSON_JAVA_THING)
+                    .setMessageText("hello")
+                    .build();
+    private static final SafetyCheck SAFETY_CHECK_JAVA_THING =
+            SafetyCheck.newBuilder()
+                    .setId("id")
+                    .setDuration(Duration.ofMinutes(5))
+                    .setCheckinTime(ZonedDateTime.of(2023, 01, 10, 10, 0, 0, 0, ZoneOffset.UTC))
+                    .build();
+    private static final ListValue ORDER_ITEMS_STRUCT =
+            ListValue.newBuilder()
+                    .addValues(
+                            Value.newBuilder()
+                                    .setStructValue(
+                                            Struct.newBuilder()
+                                                    .putFields(
+                                                            "@type",
+                                                            Value.newBuilder()
+                                                                    .setStringValue("OrderItem")
+                                                                    .build())
+                                                    .putFields(
+                                                            "name",
+                                                            Value.newBuilder()
+                                                                    .setStringValue("apples")
+                                                                    .build()))
+                                    .build())
+                    .addValues(
+                            Value.newBuilder()
+                                    .setStructValue(
+                                            Struct.newBuilder()
+                                                    .putFields(
+                                                            "@type",
+                                                            Value.newBuilder()
+                                                                    .setStringValue("OrderItem")
+                                                                    .build())
+                                                    .putFields(
+                                                            "name",
+                                                            Value.newBuilder()
+                                                                    .setStringValue("oranges")
+                                                                    .build()))
+                                    .build())
+                    .build();
+    private static final Struct PARCEL_DELIVERY_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("ParcelDelivery").build())
+                    .putFields(
+                            "deliveryAddress",
+                            Value.newBuilder().setStringValue("test address").build())
+                    .putFields(
+                            "hasDeliveryMethod", Value.newBuilder().setStringValue("UPS").build())
+                    .putFields(
+                            "trackingNumber", Value.newBuilder().setStringValue("A12345").build())
+                    .putFields("trackingUrl", Value.newBuilder().setStringValue("https://").build())
+                    .build();
+    private static final Struct ORGANIZATION_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Organization").build())
+                    .putFields("name", Value.newBuilder().setStringValue("Google").build())
+                    .build();
+    private static final Struct ORDER_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Order").build())
+                    .putFields("identifier", Value.newBuilder().setStringValue("id").build())
+                    .putFields("name", Value.newBuilder().setStringValue("name").build())
+                    .putFields(
+                            "orderDate",
+                            Value.newBuilder().setStringValue("2022-01-01T08:00Z").build())
+                    .putFields(
+                            "orderDelivery",
+                            Value.newBuilder().setStructValue(PARCEL_DELIVERY_STRUCT).build())
+                    .putFields(
+                            "orderedItem",
+                            Value.newBuilder().setListValue(ORDER_ITEMS_STRUCT).build())
+                    .putFields(
+                            "orderStatus",
+                            Value.newBuilder().setStringValue("OrderDelivered").build())
+                    .putFields(
+                            "seller",
+                            Value.newBuilder().setStructValue(ORGANIZATION_STRUCT).build())
+                    .build();
+    private static final Struct PERSON_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Person").build())
+                    .putFields("identifier", Value.newBuilder().setStringValue("id").build())
+                    .putFields("name", Value.newBuilder().setStringValue("name").build())
+                    .putFields("email", Value.newBuilder().setStringValue("email").build())
+                    .putFields("telephone", Value.newBuilder().setStringValue("telephone").build())
+                    .build();
+    private static final Struct PERSON_STRUCT_2 =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Person").build())
+                    .putFields("identifier", Value.newBuilder().setStringValue("id2").build())
+                    .build();
+    private static final Struct CALENDAR_EVENT_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("CalendarEvent").build())
+                    .putFields(
+                            "startDate",
+                            Value.newBuilder().setStringValue("2022-01-01T08:00Z").build())
+                    .putFields(
+                            "endDate",
+                            Value.newBuilder().setStringValue("2023-01-01T08:00Z").build())
+                    .putFields(
+                            "attendee",
+                            Value.newBuilder()
+                                    .setListValue(
+                                            ListValue.newBuilder()
+                                                    .addValues(
+                                                            Value.newBuilder()
+                                                                    .setStructValue(PERSON_STRUCT)
+                                                                    .build())
+                                                    .addValues(
+                                                            Value.newBuilder()
+                                                                    .setStructValue(PERSON_STRUCT_2)
+                                                                    .build()))
+                                    .build())
+                    .build();
+    private static final Struct CALL_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Call").build())
+                    .putFields("callFormat", Value.newBuilder().setStringValue("Audio").build())
+                    .putFields(
+                            "participant",
+                            Value.newBuilder()
+                                    .setListValue(
+                                            ListValue.newBuilder()
+                                                    .addValues(
+                                                            Value.newBuilder()
+                                                                    .setStructValue(PERSON_STRUCT)))
+                                    .build())
+                    .build();
+    private static final Struct MESSAGE_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("Message").build())
+                    .putFields(
+                            "recipient",
+                            Value.newBuilder()
+                                    .setListValue(
+                                            ListValue.newBuilder()
+                                                    .addValues(
+                                                            Value.newBuilder()
+                                                                    .setStructValue(PERSON_STRUCT))
+                                                    .build())
+                                    .build())
+                    .putFields("text", Value.newBuilder().setStringValue("hello").build())
+                    .build();
+    private static final Struct SAFETY_CHECK_STRUCT =
+            Struct.newBuilder()
+                    .putFields("@type", Value.newBuilder().setStringValue("SafetyCheck").build())
+                    .putFields("identifier", Value.newBuilder().setStringValue("id").build())
+                    .putFields("duration", Value.newBuilder().setStringValue("PT5M").build())
+                    .putFields(
+                            "checkinTime",
+                            Value.newBuilder().setStringValue("2023-01-10T10:00Z").build())
+                    .build();
+
+    private static ParamValue toParamValue(Struct struct, String identifier) {
+        return ParamValue.newBuilder().setIdentifier(identifier).setStructValue(struct).build();
+    }
+
+    private static Entity toEntity(Struct struct) {
+        return Entity.newBuilder().setIdentifier("id").setValue(struct).build();
+    }
+
+    @Test
+    public void toEntityValue() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder()
+                                .setIdentifier("entity-id")
+                                .setStringValue("string-val")
+                                .build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toEntityValue).convert(input))
+                .isEqualTo(
+                        EntityValue.newBuilder().setId("entity-id").setValue("string-val").build());
+    }
+
+    @Test
+    public void toIntegerValue() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setNumberValue(5).build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toIntegerValue).convert(input))
+                .isEqualTo(5);
+    }
+
+    @Test
+    public void toStringValue_fromList() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("hello world").build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toStringValue).convert(input))
+                .isEqualTo("hello world");
+    }
+
+    @Test
+    public void toStringValue_withIdentifier() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder()
+                                .setIdentifier("id1")
+                                .setStringValue("hello world")
+                                .build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toStringValue).convert(input))
+                .isEqualTo("id1");
+    }
+
+    @Test
+    public void toStringValue_fromSingleParam() {
+        ParamValue input = ParamValue.newBuilder().setStringValue("hello world").build();
+
+        assertThat(TypeConverters.toStringValue(input)).isEqualTo("hello world");
+    }
+
+    @Test
+    public void alarm_conversions_matchesExpected() throws Exception {
+        Alarm alarm = Alarm.newBuilder().setId("id").build();
+
+        assertThat(
+                        TypeConverters.toAssistantAlarm(
+                                ParamValue.newBuilder().setIdentifier("id").build()))
+                .isEqualTo(alarm);
+    }
+
+    @Test
+    public void listItem_conversions_matchesExpected() throws Exception {
+        ListItem listItem = ListItem.create("itemId", "Test Item");
+        Struct listItemStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("ListItem").build())
+                        .putFields(
+                                "identifier", Value.newBuilder().setStringValue("itemId").build())
+                        .putFields("name", Value.newBuilder().setStringValue("Test Item").build())
+                        .build();
+        Entity listItemProto =
+                Entity.newBuilder().setIdentifier("itemId").setValue(listItemStruct).build();
+
+        assertThat(TypeConverters.toEntity(listItem)).isEqualTo(listItemProto);
+        assertThat(TypeConverters.toListItem(toParamValue(listItemStruct, "itemId")))
+                .isEqualTo(listItem);
+    }
+
+    @Test
+    public void itemList_conversions_matchesExpected() throws Exception {
+        ItemList itemList =
+                ItemList.newBuilder()
+                        .setId("testList")
+                        .setName("Test List")
+                        .addListItem(
+                                ListItem.create("item1", "apple"),
+                                ListItem.create("item2", "banana"))
+                        .build();
+        Struct itemListStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("ItemList").build())
+                        .putFields(
+                                "identifier", Value.newBuilder().setStringValue("testList").build())
+                        .putFields("name", Value.newBuilder().setStringValue("Test List").build())
+                        .putFields(
+                                "itemListElement",
+                                Value.newBuilder()
+                                        .setListValue(
+                                                ListValue.newBuilder()
+                                                        .addValues(
+                                                                Value.newBuilder()
+                                                                        .setStructValue(
+                                                                                Struct.newBuilder()
+                                                                                        .putFields(
+                                                                                                "@type",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "ListItem")
+                                                                                                        .build())
+                                                                                        .putFields(
+                                                                                                "identifier",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "item1")
+                                                                                                        .build())
+                                                                                        .putFields(
+                                                                                                "name",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "apple")
+                                                                                                        .build())
+                                                                                        .build())
+                                                                        .build())
+                                                        .addValues(
+                                                                Value.newBuilder()
+                                                                        .setStructValue(
+                                                                                Struct.newBuilder()
+                                                                                        .putFields(
+                                                                                                "@type",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "ListItem")
+                                                                                                        .build())
+                                                                                        .putFields(
+                                                                                                "identifier",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "item2")
+                                                                                                        .build())
+                                                                                        .putFields(
+                                                                                                "name",
+                                                                                                Value
+                                                                                                        .newBuilder()
+                                                                                                        .setStringValue(
+                                                                                                                "banana")
+                                                                                                        .build())
+                                                                                        .build())
+                                                                        .build())
+                                                        .build())
+                                        .build())
+                        .build();
+        Entity itemListProto =
+                Entity.newBuilder().setIdentifier("testList").setValue(itemListStruct).build();
+
+        assertThat(TypeConverters.toEntity(itemList)).isEqualTo(itemListProto);
+        assertThat(TypeConverters.toItemList(toParamValue(itemListStruct, "testList")))
+                .isEqualTo(itemList);
+    }
+
+    @Test
+    public void order_conversions_matchesExpected() throws Exception {
+        assertThat(TypeConverters.toParamValue(ORDER_JAVA_THING))
+                .isEqualTo(toParamValue(ORDER_STRUCT, "id"));
+        assertThat(TypeConverters.toOrder(toParamValue(ORDER_STRUCT, "id")))
+                .isEqualTo(ORDER_JAVA_THING);
+        assertThat(TypeConverters.toEntity(ORDER_JAVA_THING)).isEqualTo(toEntity(ORDER_STRUCT));
+    }
+
+    @Test
+    public void participant_conversions_matchesExpected() throws Exception {
+        ParamValue paramValue =
+                ParamValue.newBuilder()
+                        .setIdentifier(PERSON_JAVA_THING.getId().orElse("id"))
+                        .setStructValue(PERSON_STRUCT)
+                        .build();
+        Participant participant = new Participant(PERSON_JAVA_THING);
+
+        assertThat(TypeConverters.toParamValue(participant)).isEqualTo(paramValue);
+        assertThat(TypeConverters.toParticipant(paramValue)).isEqualTo(participant);
+    }
+
+    @Test
+    public void calendarEvent_conversions_matchesExpected() throws Exception {
+        assertThat(TypeConverters.CALENDAR_EVENT_TYPE_SPEC.toStruct(CALENDAR_EVENT_JAVA_THING))
+                .isEqualTo(CALENDAR_EVENT_STRUCT);
+        assertThat(TypeConverters.CALENDAR_EVENT_TYPE_SPEC.fromStruct(CALENDAR_EVENT_STRUCT))
+                .isEqualTo(CALENDAR_EVENT_JAVA_THING);
+    }
+
+    @Test
+    public void recipient_conversions_matchesExpected() throws Exception {
+        ParamValue paramValue =
+                ParamValue.newBuilder()
+                        .setIdentifier(PERSON_JAVA_THING.getId().orElse("id"))
+                        .setStructValue(PERSON_STRUCT)
+                        .build();
+        Recipient recipient = new Recipient(PERSON_JAVA_THING);
+
+        assertThat(TypeConverters.toParamValue(recipient)).isEqualTo(paramValue);
+        assertThat(TypeConverters.toRecipient(paramValue)).isEqualTo(recipient);
+    }
+
+    @Test
+    public void toParticipant_unexpectedType_throwsException() {
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
+                        .build();
+
+        assertThrows(
+                StructConversionException.class,
+                () -> TypeConverters.toParticipant(toParamValue(malformedStruct, "id")));
+    }
+
+    @Test
+    public void toRecipient_unexpectedType_throwsException() {
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
+                        .build();
+
+        assertThrows(
+                StructConversionException.class,
+                () -> TypeConverters.toRecipient(toParamValue(malformedStruct, "id")));
+    }
+
+    @Test
+    public void itemList_malformedStruct_throwsException() {
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
+                        .putFields("name", Value.newBuilder().setStringValue("List Name").build())
+                        .putFields("identifier", Value.newBuilder().setStringValue("list1").build())
+                        .build();
+
+        assertThrows(
+                StructConversionException.class,
+                () -> TypeConverters.toItemList(toParamValue(malformedStruct, "list1")));
+    }
+
+    @Test
+    public void listItem_malformedStruct_throwsException() throws Exception {
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("Malformed").build())
+                        .putFields("name", Value.newBuilder().setStringValue("Apple").build())
+                        .putFields("identifier", Value.newBuilder().setStringValue("item1").build())
+                        .build();
+
+        assertThrows(
+                StructConversionException.class,
+                () -> TypeConverters.toListItem(toParamValue(malformedStruct, "item1")));
+    }
+
+    @Test
+    public void toBoolean_success() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setBoolValue(false).build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toBooleanValue).convert(input))
+                .isFalse();
+    }
+
+    @Test
+    public void toBoolean_throwsException() {
+        List<ParamValue> input = Collections.singletonList(ParamValue.getDefaultInstance());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toBooleanValue)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Cannot parse boolean because bool_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void toLocalDate_success() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("2018-06-17").build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toLocalDate).convert(input))
+                .isEqualTo(LocalDate.of(2018, 6, 17));
+    }
+
+    @Test
+    public void toLocalDate_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("2018-0617").build());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toLocalDate)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Failed to parse ISO 8601 string to LocalDate");
+    }
+
+    @Test
+    public void toLocalDateMissingValue_throwsException() {
+        List<ParamValue> input = Collections.singletonList(ParamValue.getDefaultInstance());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toLocalDate)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Cannot parse date because string_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void toLocalTime_success() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("15:10:05").build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toLocalTime).convert(input))
+                .isEqualTo(LocalTime.of(15, 10, 5));
+    }
+
+    @Test
+    public void toLocalTime_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setStringValue("15:1:5").build());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toLocalTime)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Failed to parse ISO 8601 string to LocalTime");
+    }
+
+    @Test
+    public void toLocalTimeMissingValue_throwsException() {
+        List<ParamValue> input = Collections.singletonList(ParamValue.getDefaultInstance());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toLocalTime)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Cannot parse time because string_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void toZoneId_success() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("America/New_York").build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toZoneId).convert(input))
+                .isEqualTo(ZoneId.of("America/New_York"));
+    }
+
+    @Test
+    public void toZoneId_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("America/New_Yo").build());
+
+        ZoneRulesException thrown =
+                assertThrows(
+                        ZoneRulesException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toZoneId)
+                                        .convert(input));
+        assertThat(thrown).hasMessageThat().isEqualTo("Unknown time-zone ID: America/New_Yo");
+    }
+
+    @Test
+    public void toZoneIdMissingValue_throwsException() {
+        List<ParamValue> input = Collections.singletonList(ParamValue.getDefaultInstance());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toZoneId)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Cannot parse ZoneId because string_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void toZonedDateTime_fromList() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setStringValue("2018-06-17T15:10:05Z").build());
+
+        assertThat(SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime).convert(input))
+                .isEqualTo(ZonedDateTime.of(2018, 6, 17, 15, 10, 5, 0, ZoneOffset.UTC));
+    }
+
+    @Test
+    public void toZonedDateTime_invalidStringFormat_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(
+                        ParamValue.newBuilder()
+                                .setStringValue("Failed to parse ISO 8601 string to ZonedDateTime")
+                                .build());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo("Failed to parse ISO 8601 string to ZonedDateTime");
+    }
+
+    @Test
+    public void toZonedDateTime_stringTypeMissing_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setNumberValue(100).build());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toZonedDateTime)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo(
+                        "Cannot parse datetime because string_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void toDuration_success() throws Exception {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setStringValue("PT5M").build());
+
+        Duration convertedDuration =
+                SlotTypeConverter.ofSingular(TypeConverters::toDuration).convert(input);
+
+        assertThat(convertedDuration).isEqualTo(Duration.ofMinutes(5));
+    }
+
+    @Test
+    public void toDuration_stringTypeMissing_throwsException() {
+        List<ParamValue> input =
+                Collections.singletonList(ParamValue.newBuilder().setNumberValue(100).build());
+
+        StructConversionException thrown =
+                assertThrows(
+                        StructConversionException.class,
+                        () ->
+                                SlotTypeConverter.ofSingular(TypeConverters::toDuration)
+                                        .convert(input));
+        assertThat(thrown)
+                .hasMessageThat()
+                .isEqualTo(
+                        "Cannot parse duration because string_value is missing from ParamValue.");
+    }
+
+    @Test
+    public void searchActionConverter_withoutNestedObject() throws Exception {
+        ParamValue input =
+                ParamValue.newBuilder()
+                        .setStructValue(
+                                Struct.newBuilder()
+                                        .putFields(
+                                                "@type",
+                                                Value.newBuilder()
+                                                        .setStringValue("SearchAction")
+                                                        .build())
+                                        .putFields(
+                                                "query",
+                                                Value.newBuilder()
+                                                        .setStringValue("grocery")
+                                                        .build())
+                                        .build())
+                        .build();
+
+        SearchAction<ItemList> output =
+                TypeConverters.createSearchActionConverter(TypeConverters.ITEM_LIST_TYPE_SPEC)
+                        .toSearchAction(input);
+
+        assertThat(output).isEqualTo(SearchAction.newBuilder().setQuery("grocery").build());
+    }
+
+    @Test
+    public void searchActionConverter_withNestedObject() throws Exception {
+        ItemList itemList =
+                ItemList.newBuilder()
+                        .addListItem(ListItem.newBuilder().setName("sugar").build())
+                        .build();
+        Struct nestedObject = TypeConverters.ITEM_LIST_TYPE_SPEC.toStruct(itemList);
+        ParamValue input =
+                ParamValue.newBuilder()
+                        .setStructValue(
+                                Struct.newBuilder()
+                                        .putFields(
+                                                "@type",
+                                                Value.newBuilder()
+                                                        .setStringValue("SearchAction")
+                                                        .build())
+                                        .putFields(
+                                                "object",
+                                                Value.newBuilder()
+                                                        .setStructValue(nestedObject)
+                                                        .build())
+                                        .build())
+                        .build();
+
+        SearchAction<ItemList> output =
+                TypeConverters.createSearchActionConverter(TypeConverters.ITEM_LIST_TYPE_SPEC)
+                        .toSearchAction(input);
+
+        assertThat(output).isEqualTo(SearchAction.newBuilder().setObject(itemList).build());
+    }
+
+    @Test
+    public void toParamValues_string_success() {
+        ParamValue output = TypeConverters.toParamValue("grocery");
+
+        assertThat(output).isEqualTo(ParamValue.newBuilder().setStringValue("grocery").build());
+    }
+
+    @Test
+    public void toTimer_success() throws Exception {
+        Timer timer = Timer.newBuilder().setId("abc").build();
+
+        assertThat(
+                        TypeConverters.toTimer(
+                                ParamValue.newBuilder()
+                                        .setStructValue(
+                                                Struct.newBuilder()
+                                                        .putFields(
+                                                                "@type",
+                                                                Value.newBuilder()
+                                                                        .setStringValue("Timer")
+                                                                        .build())
+                                                        .putFields(
+                                                                "identifier",
+                                                                Value.newBuilder()
+                                                                        .setStringValue("abc")
+                                                                        .build()))
+                                        .build()))
+                .isEqualTo(timer);
+    }
+
+    @Test
+    public void toParamValues_call_success() {
+        assertThat(TypeConverters.toParamValue(CALL_JAVA_THING))
+                .isEqualTo(
+                        ParamValue.newBuilder()
+                                .setStructValue(CALL_STRUCT)
+                                .setIdentifier("id")
+                                .build());
+    }
+
+    @Test
+    public void toParamValues_message_success() {
+        assertThat(TypeConverters.toParamValue(MESSAGE_JAVA_THING))
+                .isEqualTo(
+                        ParamValue.newBuilder()
+                                .setStructValue(MESSAGE_STRUCT)
+                                .setIdentifier("id")
+                                .build());
+    }
+
+    @Test
+    public void toParamValues_safetyCheck_success() {
+        assertThat(TypeConverters.toParamValue(SAFETY_CHECK_JAVA_THING))
+                .isEqualTo(
+                        ParamValue.newBuilder()
+                                .setStructValue(SAFETY_CHECK_STRUCT)
+                                .setIdentifier("id")
+                                .build());
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImplTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImplTest.java
new file mode 100644
index 0000000..52d6c2a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/converters/TypeSpecImplTest.java
@@ -0,0 +1,246 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.converters;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.appactions.interaction.capabilities.core.impl.exceptions.StructConversionException;
+import androidx.appactions.interaction.capabilities.core.testing.spec.TestEntity;
+
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.time.Duration;
+import java.time.ZoneId;
+import java.time.ZoneOffset;
+import java.time.ZonedDateTime;
+
+@RunWith(JUnit4.class)
+public final class TypeSpecImplTest {
+
+    @Test
+    public void bindEnumField_convertsSuccessfully() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindEnumField(
+                                "enum", TestEntity::getEnum, TestEntity.Builder::setEnum,
+                                TestEntity.TestEnum.class)
+                        .build();
+        TestEntity entity = TestEntity.newBuilder().setEnum(TestEntity.TestEnum.VALUE_1).build();
+        Struct entityStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("enum", Value.newBuilder().setStringValue("value_1").build())
+                        .build();
+
+        Struct convertedStruct = entityTypeSpec.toStruct(entity);
+        assertThat(convertedStruct).isEqualTo(entityStruct);
+
+        TestEntity convertedEntity = entityTypeSpec.fromStruct(entityStruct);
+        assertThat(convertedEntity).isEqualTo(entity);
+    }
+
+    @Test
+    public void bindEnumField_throwsException() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindEnumField(
+                                "enum", TestEntity::getEnum, TestEntity.Builder::setEnum,
+                                TestEntity.TestEnum.class)
+                        .build();
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("enum", Value.newBuilder().setStringValue("invalid").build())
+                        .build();
+
+        assertThrows(StructConversionException.class,
+                () -> entityTypeSpec.fromStruct(malformedStruct));
+    }
+
+    @Test
+    public void bindDurationField_convertsSuccessfully() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindDurationField("duration", TestEntity::getDuration,
+                                TestEntity.Builder::setDuration)
+                        .build();
+        TestEntity entity = TestEntity.newBuilder().setDuration(Duration.ofMinutes(5)).build();
+        Struct entityStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("duration", Value.newBuilder().setStringValue("PT5M").build())
+                        .build();
+
+        Struct convertedStruct = entityTypeSpec.toStruct(entity);
+        assertThat(convertedStruct).isEqualTo(entityStruct);
+
+        TestEntity convertedEntity = entityTypeSpec.fromStruct(entityStruct);
+        assertThat(convertedEntity).isEqualTo(entity);
+    }
+
+    @Test
+    public void bindZonedDateTimeField_convertsSuccessfully() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindZonedDateTimeField(
+                                "date", TestEntity::getZonedDateTime,
+                                TestEntity.Builder::setZonedDateTime)
+                        .build();
+        TestEntity entity =
+                TestEntity.newBuilder()
+                        .setZonedDateTime(ZonedDateTime.of(2022, 1, 1, 8, 0, 0, 0, ZoneOffset.UTC))
+                        .build();
+        Struct entityStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("date",
+                                Value.newBuilder().setStringValue("2022-01-01T08:00Z").build())
+                        .build();
+
+        Struct convertedStruct = entityTypeSpec.toStruct(entity);
+        assertThat(convertedStruct).isEqualTo(entityStruct);
+
+        TestEntity convertedEntity = entityTypeSpec.fromStruct(entityStruct);
+        assertThat(convertedEntity).isEqualTo(entity);
+    }
+
+    @Test
+    public void bindZonedDateTimeField_zoneId_convertsSuccessfully() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindZonedDateTimeField(
+                                "date", TestEntity::getZonedDateTime,
+                                TestEntity.Builder::setZonedDateTime)
+                        .build();
+        TestEntity entity =
+                TestEntity.newBuilder()
+                        .setZonedDateTime(
+                                ZonedDateTime.of(2022, 1, 1, 8, 0, 0, 0, ZoneId.of("UTC+01:00")))
+                        .build();
+        Struct entityStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("date",
+                                Value.newBuilder().setStringValue("2022-01-01T08:00+01:00").build())
+                        .build();
+        TestEntity expectedEntity =
+                TestEntity.newBuilder()
+                        .setZonedDateTime(
+                                ZonedDateTime.of(2022, 1, 1, 8, 0, 0, 0, ZoneOffset.of("+01:00")))
+                        .build();
+
+        Struct convertedStruct = entityTypeSpec.toStruct(entity);
+        assertThat(convertedStruct).isEqualTo(entityStruct);
+
+        TestEntity convertedEntity = entityTypeSpec.fromStruct(entityStruct);
+        assertThat(convertedEntity).isEqualTo(expectedEntity);
+    }
+
+    @Test
+    public void bindZonedDateTimeField_throwsException() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindZonedDateTimeField(
+                                "date", TestEntity::getZonedDateTime,
+                                TestEntity.Builder::setZonedDateTime)
+                        .build();
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("date",
+                                Value.newBuilder().setStringValue("2022-01-01T08").build())
+                        .build();
+
+        assertThrows(StructConversionException.class,
+                () -> entityTypeSpec.fromStruct(malformedStruct));
+    }
+
+    @Test
+    public void bindSpecField_convertsSuccessfully() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindSpecField(
+                                "entity",
+                                TestEntity::getEntity,
+                                TestEntity.Builder::setEntity,
+                                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                                        .bindStringField("name", TestEntity::getName,
+                                                TestEntity.Builder::setName)
+                                        .build())
+                        .build();
+        TestEntity entity =
+                TestEntity.newBuilder()
+                        .setEntity(TestEntity.newBuilder().setName("entity name").build())
+                        .build();
+        Struct entityStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields(
+                                "entity",
+                                Value.newBuilder()
+                                        .setStructValue(
+                                                Struct.newBuilder()
+                                                        .putFields(
+                                                                "@type",
+                                                                Value.newBuilder().setStringValue(
+                                                                        "TestEntity").build())
+                                                        .putFields(
+                                                                "name",
+                                                                Value.newBuilder().setStringValue(
+                                                                        "entity name").build())
+                                                        .build())
+                                        .build())
+                        .build();
+
+        Struct convertedStruct = entityTypeSpec.toStruct(entity);
+        assertThat(convertedStruct).isEqualTo(entityStruct);
+
+        TestEntity convertedEntity = entityTypeSpec.fromStruct(entityStruct);
+        assertThat(convertedEntity).isEqualTo(entity);
+    }
+
+    @Test
+    public void bindSpecField_throwsException() throws Exception {
+        TypeSpec<TestEntity> entityTypeSpec =
+                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                        .bindSpecField(
+                                "entity",
+                                TestEntity::getEntity,
+                                TestEntity.Builder::setEntity,
+                                TypeSpecBuilder.newBuilder("TestEntity", TestEntity::newBuilder)
+                                        .bindStringField("name", TestEntity::getName,
+                                                TestEntity.Builder::setName)
+                                        .build())
+                        .build();
+        Struct malformedStruct =
+                Struct.newBuilder()
+                        .putFields("@type", Value.newBuilder().setStringValue("TestEntity").build())
+                        .putFields("entity",
+                                Value.newBuilder().setStringValue("wrong value").build())
+                        .build();
+
+        assertThrows(StructConversionException.class,
+                () -> entityTypeSpec.fromStruct(malformedStruct));
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImplTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImplTest.java
new file mode 100644
index 0000000..4e2e5b8
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionCapabilityImplTest.java
@@ -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.appactions.interaction.capabilities.core.impl.spec;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.Mockito.verify;
+
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper;
+import androidx.appactions.interaction.capabilities.core.impl.CallbackInternal;
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+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;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment;
+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.proto.ParamValue;
+
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+import org.mockito.ArgumentCaptor;
+import org.mockito.Captor;
+import org.mockito.Mock;
+import org.mockito.junit.MockitoJUnit;
+import org.mockito.junit.MockitoRule;
+
+import java.util.List;
+import java.util.Optional;
+
+@RunWith(JUnit4.class)
+public final class ActionCapabilityImplTest {
+
+    private static final ActionSpec<Property, Argument, Output> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed("actions.intent.TEST")
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .setOutput(Output.class)
+                    .bindRequiredEntityParameter(
+                            "requiredEntity",
+                            Property::requiredEntityField,
+                            Argument.Builder::setRequiredEntityField)
+                    .bindOptionalOutput(
+                            "optionalStringOutput", Output::optionalStringField,
+                            TypeConverters::toParamValue)
+                    .bindRepeatedOutput(
+                            "repeatedStringOutput", Output::repeatedStringField,
+                            TypeConverters::toParamValue)
+                    .build();
+    @Rule
+    public final MockitoRule mockito = MockitoJUnit.rule();
+    @Captor
+    ArgumentCaptor<FulfillmentResponse> mCaptor;
+    @Mock
+    private CallbackInternal mCallbackInternal;
+
+    @Test
+    @SuppressWarnings("JdkImmutableCollections")
+    public void execute_convertExecutionResult() {
+        Property property =
+                Property.newBuilder().setRequiredEntityField(
+                        EntityProperty.newBuilder().build()).build();
+
+        ExecutionResult<Output> executionResult =
+                ExecutionResult.<Output>newBuilderWithOutput()
+                        .setOutput(
+                                Output.builder()
+                                        .setOptionalStringField("test2")
+                                        .setRepeatedStringField(List.of("test3", "test4"))
+                                        .build())
+                        .build();
+        ActionCapabilityImpl<Property, Argument, Output> capability =
+                new ActionCapabilityImpl<>(
+                        ACTION_SPEC,
+                        Optional.of("id"),
+                        property,
+                        (argument, callback) -> callback.onSuccess(executionResult));
+        StructuredOutput expectedExecutionOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test2").build())
+                                        .build())
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("repeatedStringOutput")
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test3").build())
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test4").build())
+                                        .build())
+                        .build();
+
+        capability.execute(ArgumentsWrapper.create(Fulfillment.getDefaultInstance()),
+                mCallbackInternal);
+
+        verify(mCallbackInternal).onSuccess(mCaptor.capture());
+        assertThat(mCaptor.getValue().getExecutionOutput().getOutputValuesList())
+                .containsExactlyElementsIn(expectedExecutionOutput.getOutputValuesList());
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
new file mode 100644
index 0000000..f2cc5b8
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/impl/spec/ActionSpecTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.spec;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ActionCapability;
+import androidx.appactions.interaction.capabilities.core.ActionExecutor;
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilityInternal;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
+import androidx.appactions.interaction.capabilities.core.properties.Entity;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.properties.EnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+import androidx.appactions.interaction.capabilities.core.testing.spec.Output;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+import androidx.appactions.interaction.proto.AppActionsContext.AppAction;
+import androidx.appactions.interaction.proto.AppActionsContext.IntentParameter;
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput;
+import androidx.appactions.interaction.proto.FulfillmentResponse.StructuredOutput.OutputValue;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.auto.value.AutoValue;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+import java.util.List;
+import java.util.Optional;
+
+@RunWith(JUnit4.class)
+public final class ActionSpecTest {
+
+    private static final ActionSpec<Property, Argument, Output> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed("actions.intent.TEST")
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .setOutput(Output.class)
+                    .bindRequiredEntityParameter(
+                            "requiredEntity",
+                            Property::requiredEntityField,
+                            Argument.Builder::setRequiredEntityField)
+                    .bindOptionalEntityParameter(
+                            "optionalEntity",
+                            Property::optionalEntityField,
+                            Argument.Builder::setOptionalEntityField)
+                    .bindOptionalEnumParameter(
+                            "optionalEnum",
+                            TestEnum.class,
+                            Property::optionalEnumField,
+                            Argument.Builder::setOptionalEnumField)
+                    .bindRepeatedEntityParameter(
+                            "repeatedEntity",
+                            Property::repeatedEntityField,
+                            Argument.Builder::setRepeatedEntityField)
+                    .bindRequiredStringParameter(
+                            "requiredString",
+                            Property::requiredStringField,
+                            Argument.Builder::setRequiredStringField)
+                    .bindOptionalStringParameter(
+                            "optionalString",
+                            Property::optionalStringField,
+                            Argument.Builder::setOptionalStringField)
+                    .bindRepeatedStringParameter(
+                            "repeatedString",
+                            Property::repeatedStringField,
+                            Argument.Builder::setRepeatedStringField)
+                    .bindOptionalOutput(
+                            "optionalStringOutput", Output::optionalStringField,
+                            TypeConverters::toParamValue)
+                    .bindRepeatedOutput(
+                            "repeatedStringOutput", Output::repeatedStringField,
+                            TypeConverters::toParamValue)
+                    .build();
+
+    private static ActionCapability createCapability(
+            String id, Property property, ActionExecutor<Argument, Output> executor) {
+        return new ActionCapabilityImpl<>(ACTION_SPEC, Optional.of(id), property, executor);
+    }
+
+    @Test
+    public void getAppAction_onlyRequiredProperty() {
+        Property property =
+                Property.create(
+                        EntityProperty.newBuilder()
+                                .addPossibleEntity(
+                                        Entity.newBuilder()
+                                                .setId("contact_2")
+                                                .setName("Donald")
+                                                .setAlternateNames("Duck")
+                                                .build())
+                                .setValueMatchRequired(true)
+                                .build(),
+                        StringProperty.EMPTY);
+
+        ActionCapabilityInternal capability =
+                (ActionCapabilityInternal) createCapability("id", property, (arg, callback) -> {
+                });
+
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setIdentifier("id")
+                                .setName("actions.intent.TEST")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("requiredEntity")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("contact_2")
+                                                                .setName("Donald")
+                                                                .addAlternateNames("Duck")
+                                                                .build())
+                                                .setIsRequired(false)
+                                                .setEntityMatchRequired(true))
+                                .addParams(IntentParameter.newBuilder().setName("requiredString"))
+                                .build());
+    }
+
+    @Test
+    public void getAppAction_allProperties() {
+        Property property =
+                Property.create(
+                        EntityProperty.newBuilder()
+                                .addPossibleEntity(
+                                        Entity.newBuilder()
+                                                .setId("contact_2")
+                                                .setName("Donald")
+                                                .setAlternateNames("Duck")
+                                                .build())
+                                .build(),
+                        Optional.of(
+                                EntityProperty.newBuilder()
+                                        .addPossibleEntity(
+                                                Entity.newBuilder()
+                                                        .setId("entity1")
+                                                        .setName("optional possible entity")
+                                                        .build())
+                                        .setIsRequired(true)
+                                        .build()),
+                        Optional.of(
+                                EnumProperty.newBuilder(TestEnum.class)
+                                        .addSupportedEnumValues(TestEnum.VALUE_1)
+                                        .setIsRequired(true)
+                                        .build()),
+                        Optional.of(
+                                EntityProperty.newBuilder()
+                                        .addPossibleEntity(
+                                                Entity.newBuilder().setId("entity1").setName(
+                                                        "repeated entity1").build())
+                                        .addPossibleEntity(
+                                                Entity.newBuilder().setId("entity2").setName(
+                                                        "repeated entity2").build())
+                                        .setIsRequired(true)
+                                        .build()),
+                        StringProperty.EMPTY,
+                        Optional.of(
+                                StringProperty.newBuilder()
+                                        .addPossibleValue("value1")
+                                        .setValueMatchRequired(true)
+                                        .setIsRequired(true)
+                                        .build()),
+                        Optional.of(StringProperty.PROHIBITED));
+
+        ActionCapabilityInternal capability =
+                (ActionCapabilityInternal) createCapability("id", property, (arg, callback) -> {
+                });
+
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setIdentifier("id")
+                                .setName("actions.intent.TEST")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("requiredEntity")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("contact_2")
+                                                                .setName("Donald")
+                                                                .addAlternateNames("Duck")
+                                                                .build())
+                                                .setIsRequired(false)
+                                                .setEntityMatchRequired(false))
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("optionalEntity")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("entity1")
+                                                                .setName("optional possible entity")
+                                                                .build())
+                                                .setIsRequired(true)
+                                                .setEntityMatchRequired(false))
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("optionalEnum")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier(
+                                                                        TestEnum.VALUE_1.toString())
+                                                                .build())
+                                                .setIsRequired(true)
+                                                .setEntityMatchRequired(true))
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("repeatedEntity")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("entity1")
+                                                                .setName("repeated entity1")
+                                                                .build())
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("entity2")
+                                                                .setName("repeated entity2")
+                                                                .build())
+                                                .setIsRequired(true)
+                                                .setEntityMatchRequired(false))
+                                .addParams(IntentParameter.newBuilder().setName("requiredString"))
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("optionalString")
+                                                .addPossibleEntities(
+                                                        androidx.appactions.interaction.proto.Entity.newBuilder()
+                                                                .setIdentifier("value1")
+                                                                .setName("value1")
+                                                                .build())
+                                                .setIsRequired(true)
+                                                .setEntityMatchRequired(true))
+                                .addParams(
+                                        IntentParameter.newBuilder().setName(
+                                                "repeatedString").setIsProhibited(true))
+                                .build());
+    }
+
+    @Test
+    @SuppressWarnings("JdkImmutableCollections")
+    public void convertOutputToProto_string() {
+        Output output =
+                Output.builder()
+                        .setOptionalStringField("test2")
+                        .setRepeatedStringField(List.of("test3", "test4"))
+                        .build();
+
+        StructuredOutput expectedExecutionOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test2").build())
+                                        .build())
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("repeatedStringOutput")
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test3").build())
+                                        .addValues(ParamValue.newBuilder().setStringValue(
+                                                "test4").build())
+                                        .build())
+                        .build();
+
+        StructuredOutput executionOutput = ACTION_SPEC.convertOutputToProto(output);
+
+        assertThat(executionOutput.getOutputValuesList())
+                .containsExactlyElementsIn(expectedExecutionOutput.getOutputValuesList());
+    }
+
+    enum TestEnum {
+        VALUE_1,
+        VALUE_2,
+    }
+
+    @AutoValue
+    abstract static class Argument {
+
+        static Builder newBuilder() {
+            return new AutoValue_ActionSpecTest_Argument.Builder();
+        }
+
+        abstract EntityValue requiredEntityField();
+
+        abstract EntityValue optionalEntityField();
+
+        abstract TestEnum optionalEnumField();
+
+        abstract List<EntityValue> repeatedEntityField();
+
+        abstract String requiredStringField();
+
+        abstract String optionalStringField();
+
+        abstract List<String> repeatedStringField();
+
+        @AutoValue.Builder
+        abstract static class Builder implements BuilderOf<Argument> {
+
+            abstract Builder setRequiredEntityField(EntityValue value);
+
+            abstract Builder setOptionalEntityField(EntityValue value);
+
+            abstract Builder setOptionalEnumField(TestEnum value);
+
+            abstract Builder setRepeatedEntityField(List<EntityValue> repeated);
+
+            abstract Builder setRequiredStringField(String value);
+
+            abstract Builder setOptionalStringField(String value);
+
+            abstract Builder setRepeatedStringField(List<String> repeated);
+
+            @NonNull
+            @Override
+            public abstract Argument build();
+        }
+    }
+
+    @AutoValue
+    abstract static class Property {
+
+        static Property create(
+                EntityProperty requiredEntityField,
+                Optional<EntityProperty> optionalEntityField,
+                Optional<EnumProperty<TestEnum>> optionalEnumField,
+                Optional<EntityProperty> repeatedEntityField,
+                StringProperty requiredStringField,
+                Optional<StringProperty> optionalStringField,
+                Optional<StringProperty> repeatedStringField) {
+            return new AutoValue_ActionSpecTest_Property(
+                    requiredEntityField,
+                    optionalEntityField,
+                    optionalEnumField,
+                    repeatedEntityField,
+                    requiredStringField,
+                    optionalStringField,
+                    repeatedStringField);
+        }
+
+        static Property create(EntityProperty requiredEntityField,
+                StringProperty requiredStringField) {
+            return create(
+                    requiredEntityField,
+                    Optional.empty(),
+                    Optional.empty(),
+                    Optional.empty(),
+                    requiredStringField,
+                    Optional.empty(),
+                    Optional.empty());
+        }
+
+        abstract EntityProperty requiredEntityField();
+
+        abstract Optional<EntityProperty> optionalEntityField();
+
+        abstract Optional<EnumProperty<TestEnum>> optionalEnumField();
+
+        abstract Optional<EntityProperty> repeatedEntityField();
+
+        abstract StringProperty requiredStringField();
+
+        abstract Optional<StringProperty> optionalStringField();
+
+        abstract Optional<StringProperty> repeatedStringField();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResultTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResultTest.java
new file mode 100644
index 0000000..3d0bf1c
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/EntitySearchResultTest.java
@@ -0,0 +1,41 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.task;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public final class EntitySearchResultTest {
+
+    @Test
+    public void emptyList() {
+        assertThat(EntitySearchResult.empty().possibleValues()).isEmpty();
+    }
+
+    @Test
+    public void test() {
+        EntitySearchResult.Builder<String> gr = EntitySearchResult.newBuilder();
+        gr.addPossibleValue("foo");
+        gr.addPossibleValue("bar");
+
+        assertThat(gr.build().possibleValues()).containsExactly("foo", "bar");
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.java
new file mode 100644
index 0000000..89df7ba
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityImplTest.java
@@ -0,0 +1,1770 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.buildRequestArgs;
+import static androidx.appactions.interaction.capabilities.core.testing.ArgumentUtils.buildSearchActionParamValue;
+import static androidx.appactions.interaction.capabilities.core.testing.TestingUtils.CB_TIMEOUT;
+import static androidx.appactions.interaction.capabilities.core.testing.TestingUtils.buildActionCallback;
+import static androidx.appactions.interaction.capabilities.core.testing.TestingUtils.buildActionCallbackWithFulfillmentResponse;
+import static androidx.appactions.interaction.capabilities.core.testing.TestingUtils.buildErrorActionCallback;
+import static androidx.appactions.interaction.capabilities.core.testing.TestingUtils.buildTouchEventCallback;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.CANCEL;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.CONFIRM;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.SYNC;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.TERMINATE;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.UNKNOWN_TYPE;
+import static androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.Type.UNRECOGNIZED;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import static java.util.concurrent.TimeUnit.MILLISECONDS;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.AbstractCapabilityBuilder;
+import androidx.appactions.interaction.capabilities.core.AbstractTaskHandlerBuilder;
+import androidx.appactions.interaction.capabilities.core.ConfirmationOutput;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+import androidx.appactions.interaction.capabilities.core.impl.ActionCapabilityInternal;
+import androidx.appactions.interaction.capabilities.core.impl.ErrorStatusInternal;
+import androidx.appactions.interaction.capabilities.core.impl.TouchEventCallback;
+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.TypeConverters;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.properties.EnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.SimpleProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+import androidx.appactions.interaction.capabilities.core.task.AppEntityResolver;
+import androidx.appactions.interaction.capabilities.core.task.EntitySearchResult;
+import androidx.appactions.interaction.capabilities.core.task.InvalidTaskException;
+import androidx.appactions.interaction.capabilities.core.task.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+import androidx.appactions.interaction.capabilities.core.task.OnReadyToConfirmListener;
+import androidx.appactions.interaction.capabilities.core.task.ValidationResult;
+import androidx.appactions.interaction.capabilities.core.task.ValueListener;
+import androidx.appactions.interaction.capabilities.core.testing.TestingUtils;
+import androidx.appactions.interaction.capabilities.core.testing.TestingUtils.ReusableTouchEventCallback;
+import androidx.appactions.interaction.capabilities.core.testing.TestingUtils.TouchEventResult;
+import androidx.appactions.interaction.capabilities.core.testing.spec.Argument;
+import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityStructFill;
+import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityTwoEntityValues;
+import androidx.appactions.interaction.capabilities.core.testing.spec.CapabilityTwoStrings;
+import androidx.appactions.interaction.capabilities.core.testing.spec.Confirmation;
+import androidx.appactions.interaction.capabilities.core.testing.spec.Output;
+import androidx.appactions.interaction.capabilities.core.testing.spec.Property;
+import androidx.appactions.interaction.capabilities.core.testing.spec.SettableFutureWrapper;
+import androidx.appactions.interaction.capabilities.core.testing.spec.TestEnum;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+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.IntentParameter;
+import androidx.appactions.interaction.proto.CurrentValue;
+import androidx.appactions.interaction.proto.DisambiguationData;
+import androidx.appactions.interaction.proto.Entity;
+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.proto.ParamValue;
+import androidx.appactions.interaction.proto.TaskInfo;
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+import androidx.concurrent.futures.CallbackToFutureAdapter.Completer;
+
+import com.google.common.util.concurrent.ListenableFuture;
+import com.google.errorprone.annotations.CanIgnoreReturnValue;
+
+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.Optional;
+import java.util.concurrent.Executors;
+import java.util.concurrent.atomic.AtomicInteger;
+import java.util.concurrent.atomic.AtomicReference;
+import java.util.function.Supplier;
+
+@RunWith(JUnit4.class)
+public final class TaskCapabilityImplTest {
+
+    private static final Optional<DisambigEntityConverter<EntityValue>> DISAMBIG_ENTITY_CONVERTER =
+            Optional.of(TypeConverters::toEntity);
+    private static final GenericResolverInternal<EntityValue> AUTO_ACCEPT_ENTITY_VALUE =
+            GenericResolverInternal.fromAppEntityResolver(
+                    new AppEntityResolver<EntityValue>() {
+                        @Override
+                        public ListenableFuture<EntitySearchResult<EntityValue>> lookupAndRender(
+                                SearchAction<EntityValue> searchAction) {
+                            EntitySearchResult.Builder<EntityValue> result =
+                                    EntitySearchResult.newBuilder();
+                            return Futures.immediateFuture(
+                                    result.addPossibleValue(EntityValue.ofId("valid1")).build());
+                        }
+
+                        @NonNull
+                        @Override
+                        public ListenableFuture<ValidationResult> onReceived(EntityValue newValue) {
+                            return Futures.immediateFuture(ValidationResult.newAccepted());
+                        }
+                    });
+    private static final GenericResolverInternal<EntityValue> AUTO_REJECT_ENTITY_VALUE =
+            GenericResolverInternal.fromAppEntityResolver(
+                    new AppEntityResolver<EntityValue>() {
+                        @Override
+                        public ListenableFuture<EntitySearchResult<EntityValue>> lookupAndRender(
+                                SearchAction<EntityValue> searchAction) {
+                            EntitySearchResult.Builder<EntityValue> result =
+                                    EntitySearchResult.newBuilder();
+                            return Futures.immediateFuture(
+                                    result.addPossibleValue(EntityValue.ofId("valid1")).build());
+                        }
+
+                        @NonNull
+                        @Override
+                        public ListenableFuture<ValidationResult> onReceived(EntityValue newValue) {
+                            return Futures.immediateFuture(ValidationResult.newRejected());
+                        }
+                    });
+    private static final String CAPABILITY_NAME = "actions.intent.TEST";
+    private static final ActionSpec<Property, Argument, Output> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .setOutput(Output.class)
+                    .bindRequiredEntityParameter(
+                            "required",
+                            Property::requiredEntityField,
+                            Argument.Builder::setRequiredEntityField)
+                    .bindOptionalStringParameter(
+                            "optional",
+                            Property::optionalStringField,
+                            Argument.Builder::setOptionalStringField)
+                    .bindOptionalEnumParameter(
+                            "optionalEnum",
+                            TestEnum.class,
+                            Property::enumField,
+                            Argument.Builder::setEnumField)
+                    .bindRepeatedStringParameter(
+                            "repeated",
+                            Property::repeatedStringField,
+                            Argument.Builder::setRepeatedStringField)
+                    .build();
+    private static final Property SINGLE_REQUIRED_FIELD_PROPERTY =
+            Property.newBuilder()
+                    .setRequiredEntityField(EntityProperty.newBuilder().setIsRequired(true).build())
+                    .build();
+    private static final Optional<OnReadyToConfirmListener<Argument, Confirmation>>
+            EMPTY_CONFIRM_LISTENER = Optional.empty();
+    private static final OnDialogFinishListener<Argument, Output> EMPTY_FINISH_LISTENER =
+            (finalArgs) ->
+                    Futures.immediateFuture(ExecutionResult.<Output>getDefaultInstanceWithOutput());
+
+    private static boolean groundingPredicate(ParamValue paramValue) {
+        return !paramValue.hasIdentifier();
+    }
+
+    private static List<CurrentValue> getCurrentValues(String argName, AppAction appAction) {
+        return appAction.getParamsList().stream()
+                .filter(intentParam -> intentParam.getName().equals(argName))
+                .findFirst()
+                .orElse(IntentParameter.getDefaultInstance())
+                .getCurrentValueList();
+    }
+
+    private static <TaskUpdaterT extends AbstractTaskUpdater>
+            TaskCapabilityImpl<Property, Argument, Output, Confirmation, TaskUpdaterT>
+                    createTaskCapability(
+                            Property property,
+                            TaskParamRegistry paramRegistry,
+                            Supplier<TaskUpdaterT> taskUpdaterSupplier,
+                            Optional<OnInitListener<TaskUpdaterT>> onInitListener,
+                            Optional<OnReadyToConfirmListener<Argument, Confirmation>>
+                                    optionalOnReadyToConfirmListener,
+                            OnDialogFinishListener<Argument, Output> onTaskFinishListener) {
+
+        Optional<OnReadyToConfirmListenerInternal<Confirmation>> onReadyToConfirmListenerInternal =
+                optionalOnReadyToConfirmListener.isPresent()
+                        ? Optional.of(
+                                (args) ->
+                                        optionalOnReadyToConfirmListener
+                                                .get()
+                                                .onReadyToConfirm(ACTION_SPEC.buildArgument(args)))
+                        : Optional.empty();
+
+        TaskCapabilityImpl<Property, Argument, Output, Confirmation, TaskUpdaterT> taskCapability =
+                new TaskCapabilityImpl<>(
+                        "id",
+                        ACTION_SPEC,
+                        property,
+                        paramRegistry,
+                        onInitListener,
+                        onReadyToConfirmListenerInternal,
+                        onTaskFinishListener,
+                        /* confirmationOutputBindings= */ new HashMap<>(),
+                        /* executionOutputBindings= */ new HashMap<>(),
+                        Executors.newFixedThreadPool(5));
+        taskCapability.setTaskUpdaterSupplier(taskUpdaterSupplier);
+        return taskCapability;
+    }
+
+    @Test
+    public void getAppAction_executeNeverCalled_taskIsUninitialized() {
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        Optional.empty(),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+    }
+
+    @Test
+    public void onInitInvoked_invokedOnce() throws Exception {
+        AtomicInteger onSuccessInvocationCount = new AtomicInteger(0);
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        Optional.of(
+                                (unused) -> {
+                                    onSuccessInvocationCount.incrementAndGet();
+                                    return Futures.immediateVoidFuture();
+                                }),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(SYNC, "unknownArgName", "foo"),
+                buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onSuccessInvocationCount.get()).isEqualTo(1);
+
+        // TURN 2.
+        SettableFutureWrapper<Boolean> onSuccessInvoked2 = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallback(onSuccessInvoked2));
+
+        assertThat(onSuccessInvoked2.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onSuccessInvocationCount.get()).isEqualTo(1);
+    }
+
+    @Test
+    public void fulfillmentType_terminate_taskStateCleared() throws Exception {
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        Optional.empty(),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+
+        // TURN 2.
+        SettableFutureWrapper<Boolean> onSuccessInvoked2 = new SettableFutureWrapper<>();
+
+        capability.execute(buildRequestArgs(TERMINATE), buildActionCallback(onSuccessInvoked2));
+
+        assertThat(onSuccessInvoked2.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+    }
+
+    @Test
+    public void fulfillmentType_cancel_taskStateCleared() throws Exception {
+        SettableFutureWrapper<RequiredTaskUpdater> taskUpdaterCb = new SettableFutureWrapper<>();
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        TestingUtils.buildOnInitListener(taskUpdaterCb),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(buildRequestArgs(SYNC), buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(taskUpdaterCb.getFuture().get(CB_TIMEOUT, MILLISECONDS).isDestroyed()).isFalse();
+
+        // TURN 2.
+        SettableFutureWrapper<Boolean> onSuccessInvoked2 = new SettableFutureWrapper<>();
+
+        capability.execute(buildRequestArgs(CANCEL), buildActionCallback(onSuccessInvoked2));
+
+        assertThat(onSuccessInvoked2.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+        assertThat(taskUpdaterCb.getFuture().get(CB_TIMEOUT, MILLISECONDS).isDestroyed()).isTrue();
+    }
+
+    @Test
+    public void fulfillmentType_unknown_errorReported() throws Exception {
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        Optional.empty(),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1 (UNKNOWN).
+        SettableFutureWrapper<ErrorStatusInternal> errorCb = new SettableFutureWrapper<>();
+
+        capability.execute(buildRequestArgs(UNKNOWN_TYPE), buildErrorActionCallback(errorCb));
+
+        assertThat(errorCb.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(ErrorStatusInternal.INVALID_REQUEST_TYPE);
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+
+        // TURN 2 (UNRECOGNIZED).
+        SettableFutureWrapper<ErrorStatusInternal> errorCb2 = new SettableFutureWrapper<>();
+
+        capability.execute(buildRequestArgs(UNRECOGNIZED), buildErrorActionCallback(errorCb2));
+
+        assertThat(errorCb2.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(ErrorStatusInternal.INVALID_REQUEST_TYPE);
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+    }
+
+    @Test
+    public void slotFilling_optionalButRejectedParam_onFinishNotInvoked() throws Exception {
+        AtomicInteger onFinishInvocationCount = new AtomicInteger(0);
+        CapabilityTwoEntityValues.Property property =
+                CapabilityTwoEntityValues.Property.newBuilder()
+                        .setSlotA(EntityProperty.newBuilder().setIsRequired(true).build())
+                        .setSlotB(EntityProperty.newBuilder().setIsRequired(false).build())
+                        .build();
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "slotA",
+                                TaskCapabilityImplTest::groundingPredicate,
+                                AUTO_ACCEPT_ENTITY_VALUE,
+                                DISAMBIG_ENTITY_CONVERTER,
+                                Optional.of(
+                                        unused -> SearchAction.<EntityValue>newBuilder().build()),
+                                TypeConverters::toEntityValue)
+                        .addTaskParameter(
+                                "slotB",
+                                TaskCapabilityImplTest::groundingPredicate,
+                                AUTO_REJECT_ENTITY_VALUE,
+                                DISAMBIG_ENTITY_CONVERTER,
+                                Optional.of(
+                                        unused -> SearchAction.<EntityValue>newBuilder().build()),
+                                TypeConverters::toEntityValue)
+                        .build();
+        TaskCapabilityImpl<
+                        CapabilityTwoEntityValues.Property,
+                        CapabilityTwoEntityValues.Argument,
+                        Void,
+                        Void,
+                        EmptyTaskUpdater>
+                capability =
+                        new TaskCapabilityImpl<>(
+                                "fakeId",
+                                CapabilityTwoEntityValues.ACTION_SPEC,
+                                property,
+                                paramRegistry,
+                                /* onInitListener= */ Optional.empty(),
+                                /* onReadyToConfirmListener= */ Optional.empty(),
+                                (finalArgs) -> {
+                                    onFinishInvocationCount.incrementAndGet();
+                                    return Futures.immediateFuture(
+                                            ExecutionResult.getDefaultInstance());
+                                },
+                                /* confirmationOutputBindings= */ Collections.emptyMap(),
+                                /* executionOutputBindings= */ Collections.emptyMap(),
+                                Runnable::run);
+        capability.setTaskUpdaterSupplier(EmptyTaskUpdater::new);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "slotA",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build(),
+                        "slotB",
+                        ParamValue.newBuilder().setIdentifier("bar").setStringValue("bar").build()),
+                buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onFinishInvocationCount.get()).isEqualTo(0);
+        assertThat(getCurrentValues("slotA", capability.getAppAction()))
+                .containsExactly(
+                        CurrentValue.newBuilder()
+                                .setValue(
+                                        ParamValue.newBuilder()
+                                                .setIdentifier("foo")
+                                                .setStringValue("foo"))
+                                .setStatus(CurrentValue.Status.ACCEPTED)
+                                .build());
+        assertThat(getCurrentValues("slotB", capability.getAppAction()))
+                .containsExactly(
+                        CurrentValue.newBuilder()
+                                .setValue(
+                                        ParamValue.newBuilder()
+                                                .setIdentifier("bar")
+                                                .setStringValue("bar"))
+                                .setStatus(CurrentValue.Status.REJECTED)
+                                .build());
+    }
+
+    @Test
+    public void slotFilling_assistantRemovedParam_clearInSdkState() throws Exception {
+        Property property =
+                Property.newBuilder()
+                        .setRequiredEntityField(
+                                EntityProperty.newBuilder().setIsRequired(true).build())
+                        .setEnumField(
+                                EnumProperty.newBuilder(TestEnum.class)
+                                        .addSupportedEnumValues(TestEnum.VALUE_1, TestEnum.VALUE_2)
+                                        .setIsRequired(true)
+                                        .build())
+                        .build();
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        property,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        Optional.empty(),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(getCurrentValues("required", capability.getAppAction()))
+                .containsExactly(
+                        CurrentValue.newBuilder()
+                                .setValue(
+                                        ParamValue.newBuilder()
+                                                .setIdentifier("foo")
+                                                .setStringValue("foo"))
+                                .setStatus(CurrentValue.Status.ACCEPTED)
+                                .build());
+        assertThat(getCurrentValues("optionalEnum", capability.getAppAction())).isEmpty();
+
+        // TURN 2.
+        SettableFutureWrapper<Boolean> onSuccessInvoked2 = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(SYNC, "optionalEnum", TestEnum.VALUE_2),
+                buildActionCallback(onSuccessInvoked2));
+
+        assertThat(onSuccessInvoked2.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(getCurrentValues("required", capability.getAppAction())).isEmpty();
+        assertThat(getCurrentValues("optionalEnum", capability.getAppAction()))
+                .containsExactly(
+                        CurrentValue.newBuilder()
+                                .setValue(ParamValue.newBuilder().setIdentifier("VALUE_2"))
+                                .setStatus(CurrentValue.Status.ACCEPTED)
+                                .build());
+    }
+
+    @Test
+    public void disambig_singleParam_disambigEntitiesInContext() throws Exception {
+        TaskParamRegistry.Builder paramRegistry = TaskParamRegistry.builder();
+        paramRegistry.addTaskParameter(
+                "required",
+                TaskCapabilityImplTest::groundingPredicate,
+                GenericResolverInternal.fromAppEntityResolver(
+                        new AppEntityResolver<EntityValue>() {
+                            @Override
+                            public ListenableFuture<EntitySearchResult<EntityValue>>
+                                    lookupAndRender(SearchAction<EntityValue> searchAction) {
+                                EntitySearchResult.Builder<EntityValue> result =
+                                        EntitySearchResult.newBuilder();
+                                return Futures.immediateFuture(
+                                        result.addPossibleValue(EntityValue.ofId("valid1"))
+                                                .addPossibleValue(EntityValue.ofId("valid2"))
+                                                .build());
+                            }
+
+                            @NonNull
+                            @Override
+                            public ListenableFuture<ValidationResult> onReceived(
+                                    EntityValue newValue) {
+                                return Futures.immediateFuture(ValidationResult.newAccepted());
+                            }
+                        }),
+                DISAMBIG_ENTITY_CONVERTER,
+                Optional.of(unused -> SearchAction.<EntityValue>newBuilder().build()),
+                TypeConverters::toEntityValue);
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        paramRegistry.build(),
+                        RequiredTaskUpdater::new,
+                        Optional.empty(),
+                        EMPTY_CONFIRM_LISTENER,
+                        EMPTY_FINISH_LISTENER);
+
+        // TURN 1.
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(SYNC, "required", buildSearchActionParamValue("invalid")),
+                buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true)
+                                                .addCurrentValue(
+                                                        CurrentValue.newBuilder()
+                                                                .setValue(
+                                                                        buildSearchActionParamValue(
+                                                                                "invalid"))
+                                                                .setStatus(
+                                                                        CurrentValue.Status
+                                                                                .DISAMBIG)
+                                                                .setDisambiguationData(
+                                                                        DisambiguationData
+                                                                                .newBuilder()
+                                                                                .addEntities(
+                                                                                        Entity
+                                                                                                .newBuilder()
+                                                                                                .setIdentifier(
+                                                                                                        "valid1")
+                                                                                                .setName(
+                                                                                                        "valid1"))
+                                                                                .addEntities(
+                                                                                        Entity
+                                                                                                .newBuilder()
+                                                                                                .setIdentifier(
+                                                                                                        "valid2")
+                                                                                                .setName(
+                                                                                                        "valid2")))))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+
+        // TURN 2.
+        SettableFutureWrapper<Boolean> turn2SuccessInvoked = new SettableFutureWrapper<>();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "required",
+                        ParamValue.newBuilder()
+                                .setIdentifier("valid2")
+                                .setStringValue("valid2")
+                                .build()),
+                buildActionCallback(turn2SuccessInvoked));
+
+        assertThat(turn2SuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("id")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("required")
+                                                .setIsRequired(true)
+                                                .addCurrentValue(
+                                                        CurrentValue.newBuilder()
+                                                                .setValue(
+                                                                        ParamValue.newBuilder()
+                                                                                .setIdentifier(
+                                                                                        "valid2")
+                                                                                .setStringValue(
+                                                                                        "valid2"))
+                                                                .setStatus(
+                                                                        CurrentValue.Status
+                                                                                .ACCEPTED)))
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+    }
+
+    /**
+     * Assistant sends grounded objects as identifier only, but we need to mark the entire value
+     * struct as accepted.
+     */
+    @Test
+    public void identifierOnly_refillsStruct() throws Exception {
+        ListItem item1 = ListItem.newBuilder().setName("red apple").setId("item1").build();
+        ListItem item2 = ListItem.newBuilder().setName("green apple").setId("item2").build();
+        SettableFutureWrapper<ListItem> onReceivedCb = new SettableFutureWrapper<>();
+        CapabilityStructFill.Property property =
+                CapabilityStructFill.Property.newBuilder()
+                        .setItemList(SimpleProperty.REQUIRED)
+                        .setAnyString(StringProperty.newBuilder().setIsRequired(true).build())
+                        .build();
+        TaskParamRegistry.Builder paramRegistryBuilder = TaskParamRegistry.builder();
+        paramRegistryBuilder.addTaskParameter(
+                "listItem",
+                TaskCapabilityImplTest::groundingPredicate,
+                GenericResolverInternal.fromAppEntityResolver(
+                        new AppEntityResolver<ListItem>() {
+                            @NonNull
+                            @Override
+                            public ListenableFuture<ValidationResult> onReceived(
+                                    ListItem listItem) {
+                                onReceivedCb.set(listItem);
+                                return Futures.immediateFuture(ValidationResult.newAccepted());
+                            }
+
+                            @Override
+                            public ListenableFuture<EntitySearchResult<ListItem>> lookupAndRender(
+                                    SearchAction<ListItem> searchAction) {
+                                return Futures.immediateFuture(
+                                        EntitySearchResult.<ListItem>newBuilder()
+                                                .addPossibleValue(item1)
+                                                .addPossibleValue(item2)
+                                                .build());
+                            }
+                        }),
+                Optional.of((DisambigEntityConverter<ListItem>) TypeConverters::toEntity),
+                Optional.of(unused -> SearchAction.<ListItem>newBuilder().build()),
+                TypeConverters::toListItem);
+        SettableFutureWrapper<ListItem> onFinishListItemCb = new SettableFutureWrapper<>();
+        SettableFutureWrapper<String> onFinishStringCb = new SettableFutureWrapper<>();
+        TaskCapabilityImpl<
+                        CapabilityStructFill.Property,
+                        CapabilityStructFill.Argument,
+                        Void,
+                        Void,
+                        EmptyTaskUpdater>
+                capability =
+                        new TaskCapabilityImpl<>(
+                                "selectListItem",
+                                CapabilityStructFill.ACTION_SPEC,
+                                property,
+                                paramRegistryBuilder.build(),
+                                /* onInitListener= */ Optional.empty(),
+                                /* onReadyToConfirmListener= */ Optional.empty(),
+                                (argument) -> {
+                                    ListItem listItem = argument.listItem().orElse(null);
+                                    String string = argument.anyString().orElse(null);
+                                    onFinishListItemCb.set(listItem);
+                                    onFinishStringCb.set(string);
+                                    return Futures.immediateFuture(
+                                            ExecutionResult.getDefaultInstance());
+                                },
+                                /* confirmationOutputBindings= */ Collections.emptyMap(),
+                                /* executionOutputBindings= */ Collections.emptyMap(),
+                                Runnable::run);
+        capability.setTaskUpdaterSupplier(EmptyTaskUpdater::new);
+
+        // first sync request
+        SettableFutureWrapper<Boolean> firstTurnSuccess = new SettableFutureWrapper<>();
+        capability.execute(
+                buildRequestArgs(SYNC, "listItem", buildSearchActionParamValue("apple")),
+                buildActionCallback(firstTurnSuccess));
+        assertThat(firstTurnSuccess.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onReceivedCb.getFuture().isDone()).isFalse();
+        assertThat(onFinishListItemCb.getFuture().isDone()).isFalse();
+        assertThat(capability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("selectListItem")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("listItem")
+                                                .setIsRequired(true)
+                                                .addCurrentValue(
+                                                        CurrentValue.newBuilder()
+                                                                .setValue(
+                                                                        buildSearchActionParamValue(
+                                                                                "apple"))
+                                                                .setStatus(
+                                                                        CurrentValue.Status
+                                                                                .DISAMBIG)
+                                                                .setDisambiguationData(
+                                                                        DisambiguationData
+                                                                                .newBuilder()
+                                                                                .addEntities(
+                                                                                        TypeConverters
+                                                                                                .toEntity(
+                                                                                                        item1))
+                                                                                .addEntities(
+                                                                                        TypeConverters
+                                                                                                .toEntity(
+                                                                                                        item2))
+                                                                                .build())
+                                                                .build())
+                                                .build())
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("string")
+                                                .setIsRequired(true)
+                                                .build())
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder().setSupportsPartialFulfillment(true))
+                                .build());
+
+        // second sync request, sending grounded ParamValue with identifier only
+        SettableFutureWrapper<Boolean> secondTurnSuccess = new SettableFutureWrapper<>();
+        capability.execute(
+                buildRequestArgs(
+                        SYNC, "listItem", ParamValue.newBuilder().setIdentifier("item2").build()),
+                buildActionCallback(secondTurnSuccess));
+        assertThat(secondTurnSuccess.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onReceivedCb.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isEqualTo(item2);
+        assertThat(onFinishListItemCb.getFuture().isDone()).isFalse();
+
+        // third sync request, sending grounded ParamValue with identifier only, completes task
+        SettableFutureWrapper<Boolean> thirdTurnSuccess = new SettableFutureWrapper<>();
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        "listItem",
+                        ParamValue.newBuilder().setIdentifier("item2").build(),
+                        "string",
+                        "unused"),
+                buildActionCallback(thirdTurnSuccess));
+        assertThat(thirdTurnSuccess.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onFinishListItemCb.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isEqualTo(item2);
+        assertThat(onFinishStringCb.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isEqualTo("unused");
+    }
+
+    @Test
+    public void executionResult_resultReturned() throws Exception {
+        OnDialogFinishListener<Argument, Output> finishListener =
+                (argument) ->
+                        Futures.immediateFuture(
+                                ExecutionResult.<Output>newBuilderWithOutput()
+                                        .setOutput(
+                                                Output.builder()
+                                                        .setOptionalStringField("bar")
+                                                        .setRepeatedStringField(
+                                                                Arrays.asList("bar1", "bar2"))
+                                                        .build())
+                                        .build());
+        ActionCapabilityInternal capability =
+                (ActionCapabilityInternal)
+                        new CapabilityBuilder()
+                                .setTaskHandlerBuilder(
+                                        new TaskHandlerBuilder()
+                                                .setOnFinishListener(finishListener))
+                                .build();
+        SettableFutureWrapper<FulfillmentResponse> onSuccessInvoked = new SettableFutureWrapper<>();
+        StructuredOutput expectedOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("bar")
+                                                        .build())
+                                        .build())
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("repeatedStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("bar1")
+                                                        .build())
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("bar2")
+                                                        .build())
+                                        .build())
+                        .build();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        /* args...= */ "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallbackWithFulfillmentResponse(onSuccessInvoked));
+
+        assertThat(
+                        onSuccessInvoked
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .getExecutionOutput()
+                                .getOutputValuesList())
+                .containsExactlyElementsIn(expectedOutput.getOutputValuesList());
+    }
+
+    @Test
+    public void touchEvent_fillOnlySlot_onFinishInvoked() throws Exception {
+        EntityValue slotValue = EntityValue.newBuilder().setId("id1").setValue("value").build();
+        SettableFutureWrapper<RequiredTaskUpdater> updaterFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Argument> onFinishFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<FulfillmentResponse> touchEventResponse =
+                new SettableFutureWrapper<>();
+
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        TestingUtils.buildOnInitListener(updaterFuture),
+                        EMPTY_CONFIRM_LISTENER,
+                        TestingUtils.<Argument, Output>buildOnFinishListener(onFinishFuture));
+        capability.setTouchEventCallback(buildTouchEventCallback(touchEventResponse));
+
+        // Turn 1. No args but capability triggered (value updater should be set now).
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+        capability.execute(buildRequestArgs(SYNC), buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+        assertThat(touchEventResponse.getFuture().isDone()).isFalse();
+
+        // Turn 2. Invoke the TaskCapability via the updater.
+        // TaskUpdater should be usable after onFinishListener from the first turn.
+        RequiredTaskUpdater taskUpdater = updaterFuture.getFuture().get(CB_TIMEOUT, MILLISECONDS);
+        taskUpdater.setRequiredEntityValue(slotValue);
+
+        assertThat(
+                        onFinishFuture
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .requiredEntityField()
+                                .get())
+                .isNotNull();
+        assertThat(
+                        onFinishFuture
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .requiredEntityField()
+                                .get())
+                .isEqualTo(slotValue);
+        assertThat(touchEventResponse.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+    }
+
+    @Test
+    public void touchEvent_callbackNotSet_onFinishNotInvoked() throws Exception {
+        EntityValue slotValue = EntityValue.newBuilder().setId("id1").setValue("value").build();
+        SettableFutureWrapper<RequiredTaskUpdater> updaterFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Argument> onFinishFuture = new SettableFutureWrapper<>();
+        ActionCapabilityInternal capability =
+                createTaskCapability(
+                        SINGLE_REQUIRED_FIELD_PROPERTY,
+                        TaskParamRegistry.builder().build(),
+                        RequiredTaskUpdater::new,
+                        TestingUtils.buildOnInitListener(updaterFuture),
+                        EMPTY_CONFIRM_LISTENER,
+                        TestingUtils.<Argument, Output>buildOnFinishListener(onFinishFuture));
+        // Explicitly set to null for testing.
+        capability.setTouchEventCallback(null);
+
+        // Turn 1. No args but capability triggered (value updater should be set now).
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+        capability.execute(buildRequestArgs(SYNC), buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+
+        // Turn 2. Invoke the TaskCapability via the updater.
+        RequiredTaskUpdater taskUpdater = updaterFuture.getFuture().get(CB_TIMEOUT, MILLISECONDS);
+        taskUpdater.setRequiredEntityValue(slotValue);
+
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+    }
+
+    @Test
+    public void touchEvent_emptyValues_onFinishNotInvoked() throws Exception {
+        SettableFutureWrapper<RequiredTaskUpdater> updaterFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Argument> onFinishFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<FulfillmentResponse> touchEventResponse =
+                new SettableFutureWrapper<>();
+        TaskCapabilityImpl<Property, Argument, Output, Confirmation, RequiredTaskUpdater>
+                capability =
+                        createTaskCapability(
+                                SINGLE_REQUIRED_FIELD_PROPERTY,
+                                TaskParamRegistry.builder().build(),
+                                RequiredTaskUpdater::new,
+                                TestingUtils.buildOnInitListener(updaterFuture),
+                                EMPTY_CONFIRM_LISTENER,
+                                TestingUtils.<Argument, Output>buildOnFinishListener(
+                                        onFinishFuture));
+        capability.setTouchEventCallback(buildTouchEventCallback(touchEventResponse));
+
+        // Turn 1. No args but capability triggered (value updater should be set now).
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+        capability.execute(buildRequestArgs(SYNC), buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+        assertThat(touchEventResponse.getFuture().isDone()).isFalse();
+
+        // Turn 2. Invoke the TaskCapability via the updater.
+        // TaskUpdater should be usable after onFinishListener from the first turn.
+        capability.updateParamValues(Collections.emptyMap());
+
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+        assertThat(touchEventResponse.getFuture().isDone()).isFalse();
+    }
+
+    @Test
+    public void touchEvent_fillOnlySlot_confirmationRequired_onReadyToConfirmInvoked()
+            throws Exception {
+        EntityValue slotValue = EntityValue.newBuilder().setId("id1").setValue("value").build();
+        SettableFutureWrapper<RequiredTaskUpdater> updaterFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Argument> onReadyToConfirmFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Argument> onFinishFuture = new SettableFutureWrapper<>();
+        SettableFutureWrapper<FulfillmentResponse> touchEventResponse =
+                new SettableFutureWrapper<>();
+
+        TaskCapabilityImpl<Property, Argument, Output, Confirmation, RequiredTaskUpdater>
+                capability =
+                        createTaskCapability(
+                                SINGLE_REQUIRED_FIELD_PROPERTY,
+                                TaskParamRegistry.builder().build(),
+                                RequiredTaskUpdater::new,
+                                TestingUtils.buildOnInitListener(updaterFuture),
+                                TestingUtils.<Argument, Confirmation>buildOnReadyToConfirmListener(
+                                        onReadyToConfirmFuture),
+                                TestingUtils.<Argument, Output>buildOnFinishListener(
+                                        onFinishFuture));
+        capability.setTouchEventCallback(buildTouchEventCallback(touchEventResponse));
+
+        // Turn 1. No args but capability triggered (value updater should be set now).
+        SettableFutureWrapper<Boolean> onSuccessInvoked = new SettableFutureWrapper<>();
+        capability.execute(buildRequestArgs(SYNC), buildActionCallback(onSuccessInvoked));
+
+        assertThat(onSuccessInvoked.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(onReadyToConfirmFuture.getFuture().isDone()).isFalse();
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+        assertThat(touchEventResponse.getFuture().isDone()).isFalse();
+
+        // Turn 2. Invoke the TaskCapability via the updater.
+        // TaskUpdater should be usable after onReadyToConfirmListener from the first turn.
+        RequiredTaskUpdater taskUpdater = updaterFuture.getFuture().get(CB_TIMEOUT, MILLISECONDS);
+        taskUpdater.setRequiredEntityValue(slotValue);
+
+        assertThat(onFinishFuture.getFuture().isDone()).isFalse();
+        assertThat(
+                        onReadyToConfirmFuture
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .requiredEntityField()
+                                .get())
+                .isNotNull();
+        assertThat(
+                        onReadyToConfirmFuture
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .requiredEntityField()
+                                .get())
+                .isEqualTo(slotValue);
+        assertThat(touchEventResponse.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+    }
+
+    @Test
+    public void requiredConfirmation_throwsExecptionWhenConfirmationListenerIsNotSet()
+            throws Exception {
+        InvalidTaskException exception =
+                assertThrows(
+                        InvalidTaskException.class,
+                        () ->
+                                new CapabilityBuilderWithRequiredConfirmation()
+                                        .setTaskHandlerBuilder(
+                                                new TaskHandlerBuilderWithRequiredConfirmation())
+                                        .build());
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains("ConfirmationType is REQUIRED, but onReadyToConfirmListener is not set.");
+    }
+
+    @Test
+    public void confirmationNotSupported_throwsExecptionWhenConfirmationListenerIsSet()
+            throws Exception {
+
+        OnReadyToConfirmListenerInternal<Confirmation> onReadyToConfirmListener =
+                (args) ->
+                        Futures.immediateFuture(
+                                ConfirmationOutput.<Confirmation>newBuilderWithConfirmation()
+                                        .setConfirmation(
+                                                Confirmation.builder()
+                                                        .setOptionalStringField("bar")
+                                                        .build())
+                                        .build());
+
+        InvalidTaskException exception =
+                assertThrows(
+                        InvalidTaskException.class,
+                        () ->
+                                new CapabilityBuilder()
+                                        .setTaskHandlerBuilder(
+                                                new TaskHandlerBuilder()
+                                                        .setOnReadyToConfirmListener(
+                                                                onReadyToConfirmListener))
+                                        .build());
+
+        assertThat(exception)
+                .hasMessageThat()
+                .contains(
+                        "ConfirmationType is NOT_SUPPORTED, but onReadyToConfirmListener is set.");
+    }
+
+    @Test
+    public void confirmationOutput_resultReturned() throws Exception {
+        OnReadyToConfirmListenerInternal<Confirmation> onReadyToConfirmListener =
+                (args) ->
+                        Futures.immediateFuture(
+                                ConfirmationOutput.<Confirmation>newBuilderWithConfirmation()
+                                        .setConfirmation(
+                                                Confirmation.builder()
+                                                        .setOptionalStringField("bar")
+                                                        .build())
+                                        .build());
+        OnDialogFinishListener<Argument, Output> finishListener =
+                (argument) ->
+                        Futures.immediateFuture(
+                                ExecutionResult.<Output>newBuilderWithOutput()
+                                        .setOutput(
+                                                Output.builder()
+                                                        .setOptionalStringField("baz")
+                                                        .setRepeatedStringField(
+                                                                Arrays.asList("baz1", "baz2"))
+                                                        .build())
+                                        .build());
+        ActionCapabilityInternal capability =
+                (ActionCapabilityInternal)
+                        new CapabilityBuilderWithRequiredConfirmation()
+                                .setTaskHandlerBuilder(
+                                        new TaskHandlerBuilderWithRequiredConfirmation()
+                                                .setOnReadyToConfirmListener(
+                                                        onReadyToConfirmListener)
+                                                .setOnFinishListener(finishListener))
+                                .build();
+        SettableFutureWrapper<FulfillmentResponse> onSuccessInvoked = new SettableFutureWrapper<>();
+        StructuredOutput expectedOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("bar")
+                                                        .build())
+                                        .build())
+                        .build();
+
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        /* args...= */ "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallbackWithFulfillmentResponse(onSuccessInvoked));
+
+        assertThat(
+                        onSuccessInvoked
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .getConfirmationData()
+                                .getOutputValuesList())
+                .containsExactlyElementsIn(expectedOutput.getOutputValuesList());
+    }
+
+    @Test
+    public void executionResult_resultReturnedAfterConfirm() throws Exception {
+        // Build the capability
+        OnReadyToConfirmListenerInternal<Confirmation> onReadyToConfirmListener =
+                (args) ->
+                        Futures.immediateFuture(
+                                ConfirmationOutput.<Confirmation>newBuilderWithConfirmation()
+                                        .setConfirmation(
+                                                Confirmation.builder()
+                                                        .setOptionalStringField("bar")
+                                                        .build())
+                                        .build());
+        OnDialogFinishListener<Argument, Output> finishListener =
+                (argument) ->
+                        Futures.immediateFuture(
+                                ExecutionResult.<Output>newBuilderWithOutput()
+                                        .setOutput(
+                                                Output.builder()
+                                                        .setOptionalStringField("baz")
+                                                        .setRepeatedStringField(
+                                                                Arrays.asList("baz1", "baz2"))
+                                                        .build())
+                                        .build());
+        ActionCapabilityInternal capability =
+                (ActionCapabilityInternal)
+                        new CapabilityBuilderWithRequiredConfirmation()
+                                .setTaskHandlerBuilder(
+                                        new TaskHandlerBuilderWithRequiredConfirmation()
+                                                .setOnReadyToConfirmListener(
+                                                        onReadyToConfirmListener)
+                                                .setOnFinishListener(finishListener))
+                                .build();
+        SettableFutureWrapper<FulfillmentResponse> onSuccessInvokedFirstTurn =
+                new SettableFutureWrapper<>();
+
+        // Send a sync request that triggers confirmation
+        capability.execute(
+                buildRequestArgs(
+                        SYNC,
+                        /* args...= */ "required",
+                        ParamValue.newBuilder().setIdentifier("foo").setStringValue("foo").build()),
+                buildActionCallbackWithFulfillmentResponse(onSuccessInvokedFirstTurn));
+
+        // Confirm the BIC
+        StructuredOutput expectedConfirmationOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("bar")
+                                                        .build())
+                                        .build())
+                        .build();
+        assertThat(
+                        onSuccessInvokedFirstTurn
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .getConfirmationData()
+                                .getOutputValuesList())
+                .containsExactlyElementsIn(expectedConfirmationOutput.getOutputValuesList());
+
+        // Send a CONFIRM request which indicates the user confirmed the BIC. This triggers
+        // onFinish.
+        SettableFutureWrapper<FulfillmentResponse> onSuccessInvokedSecondTurn =
+                new SettableFutureWrapper<>();
+        capability.execute(
+                buildRequestArgs(CONFIRM),
+                buildActionCallbackWithFulfillmentResponse(onSuccessInvokedSecondTurn));
+
+        // Confirm the BIO
+        StructuredOutput expectedOutput =
+                StructuredOutput.newBuilder()
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("optionalStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("baz")
+                                                        .build())
+                                        .build())
+                        .addOutputValues(
+                                OutputValue.newBuilder()
+                                        .setName("repeatedStringOutput")
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("baz1")
+                                                        .build())
+                                        .addValues(
+                                                ParamValue.newBuilder()
+                                                        .setStringValue("baz2")
+                                                        .build())
+                                        .build())
+                        .build();
+        assertThat(
+                        onSuccessInvokedSecondTurn
+                                .getFuture()
+                                .get(CB_TIMEOUT, MILLISECONDS)
+                                .getExecutionOutput()
+                                .getOutputValuesList())
+                .containsExactlyElementsIn(expectedOutput.getOutputValuesList());
+
+        // send TERMINATE request after CONFIRM
+        SettableFutureWrapper<Boolean> terminateCb = new SettableFutureWrapper<>();
+        capability.execute(buildRequestArgs(TERMINATE), buildActionCallback(terminateCb));
+
+        assertThat(terminateCb.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+    }
+
+    @Test
+    public void concurrentRequests_prioritizeAssistantRequest() throws Exception {
+        CapabilityTwoStrings.Property property =
+                CapabilityTwoStrings.Property.newBuilder()
+                        .setStringSlotA(StringProperty.newBuilder().setIsRequired(true).build())
+                        .setStringSlotB(StringProperty.newBuilder().setIsRequired(true).build())
+                        .build();
+        DeferredValueListener<String> slotAListener = new DeferredValueListener<>();
+        DeferredValueListener<String> slotBListener = new DeferredValueListener<>();
+        TaskParamRegistry.Builder paramRegistryBuilder = TaskParamRegistry.builder();
+        paramRegistryBuilder.addTaskParameter(
+                "stringSlotA",
+                unused -> false,
+                GenericResolverInternal.fromValueListener(slotAListener),
+                Optional.empty(),
+                Optional.empty(),
+                TypeConverters::toStringValue);
+        paramRegistryBuilder.addTaskParameter(
+                "stringSlotB",
+                unused -> false,
+                GenericResolverInternal.fromValueListener(slotBListener),
+                Optional.empty(),
+                Optional.empty(),
+                TypeConverters::toStringValue);
+        SettableFutureWrapper<String> onFinishCb = new SettableFutureWrapper<>();
+        TaskCapabilityImpl<
+                        CapabilityTwoStrings.Property,
+                        CapabilityTwoStrings.Argument,
+                        Void,
+                        Void,
+                        EmptyTaskUpdater>
+                taskCapability =
+                        new TaskCapabilityImpl<>(
+                                "myTestCapability",
+                                CapabilityTwoStrings.ACTION_SPEC,
+                                property,
+                                paramRegistryBuilder.build(),
+                                /* onInitListener= */ Optional.empty(),
+                                /* onReadyToConfirmListener= */ Optional.empty(),
+                                (argument) -> {
+                                    String slotA = argument.stringSlotA().orElse(null);
+                                    String slotB = argument.stringSlotB().orElse(null);
+                                    onFinishCb.set(String.format("%s %s", slotA, slotB));
+                                    return Futures.immediateFuture(
+                                            ExecutionResult.getDefaultInstance());
+                                },
+                                new HashMap<>(),
+                                new HashMap<>(),
+                                Runnable::run);
+        taskCapability.setTaskUpdaterSupplier(EmptyTaskUpdater::new);
+        ReusableTouchEventCallback touchEventCallback = new ReusableTouchEventCallback();
+        taskCapability.setTouchEventCallback(touchEventCallback);
+
+        // first assistant request
+        SettableFutureWrapper<FulfillmentResponse> firstTurnResult = new SettableFutureWrapper<>();
+        taskCapability.execute(
+                buildRequestArgs(SYNC, "stringSlotA", "apple"),
+                buildActionCallbackWithFulfillmentResponse(firstTurnResult));
+
+        // manual input request
+        Map<String, List<ParamValue>> touchEventParamValues = new HashMap<>();
+        touchEventParamValues.put(
+                "stringSlotA",
+                Collections.singletonList(ParamValue.newBuilder().setIdentifier("banana").build()));
+        taskCapability.updateParamValues(touchEventParamValues);
+
+        // second assistant request
+        SettableFutureWrapper<FulfillmentResponse> secondTurnResult = new SettableFutureWrapper<>();
+        taskCapability.execute(
+                buildRequestArgs(SYNC, "stringSlotA", "apple", "stringSlotB", "smoothie"),
+                buildActionCallbackWithFulfillmentResponse(secondTurnResult));
+
+        assertThat(firstTurnResult.getFuture().isDone()).isFalse();
+        assertThat(touchEventCallback.getLastResult()).isEqualTo(null);
+
+        // unblock first assistant request
+        slotAListener.setValidationResult(ValidationResult.newAccepted());
+        assertThat(firstTurnResult.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+        assertThat(touchEventCallback.getLastResult()).isEqualTo(null);
+
+        // unblock second assistant request
+        slotBListener.setValidationResult(ValidationResult.newAccepted());
+        assertThat(secondTurnResult.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+
+        assertThat(onFinishCb.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo("apple smoothie");
+
+        // since task already finished, the manual input update was ignored.
+        assertThat(touchEventCallback.getLastResult()).isNull();
+    }
+
+    @Test
+    public void concurrentRequests_touchEventFinishesTask() throws Exception {
+        CapabilityTwoStrings.Property property =
+                CapabilityTwoStrings.Property.newBuilder()
+                        .setStringSlotA(StringProperty.newBuilder().setIsRequired(true).build())
+                        .setStringSlotB(StringProperty.newBuilder().setIsRequired(true).build())
+                        .build();
+        DeferredValueListener<String> slotAListener = new DeferredValueListener<>();
+        DeferredValueListener<String> slotBListener = new DeferredValueListener<>();
+        TaskParamRegistry.Builder paramRegistryBuilder = TaskParamRegistry.builder();
+        paramRegistryBuilder.addTaskParameter(
+                "stringSlotA",
+                unused -> false,
+                GenericResolverInternal.fromValueListener(slotAListener),
+                Optional.empty(),
+                Optional.empty(),
+                TypeConverters::toStringValue);
+        paramRegistryBuilder.addTaskParameter(
+                "stringSlotB",
+                unused -> false,
+                GenericResolverInternal.fromValueListener(slotBListener),
+                Optional.empty(),
+                Optional.empty(),
+                TypeConverters::toStringValue);
+        SettableFutureWrapper<String> onFinishCb = new SettableFutureWrapper<>();
+        TaskCapabilityImpl<
+                        CapabilityTwoStrings.Property,
+                        CapabilityTwoStrings.Argument,
+                        Void,
+                        Void,
+                        EmptyTaskUpdater>
+                taskCapability =
+                        new TaskCapabilityImpl<>(
+                                "myTestCapability",
+                                CapabilityTwoStrings.ACTION_SPEC,
+                                property,
+                                paramRegistryBuilder.build(),
+                                /* onInitListener= */ Optional.empty(),
+                                /* onReadyToConfirmListener= */ Optional.empty(),
+                                (argument) -> {
+                                    String slotA = argument.stringSlotA().orElse(null);
+                                    String slotB = argument.stringSlotB().orElse(null);
+                                    onFinishCb.set(String.format("%s %s", slotA, slotB));
+                                    return Futures.immediateFuture(
+                                            ExecutionResult.getDefaultInstance());
+                                },
+                                new HashMap<>(),
+                                new HashMap<>(),
+                                Runnable::run);
+        taskCapability.setTaskUpdaterSupplier(EmptyTaskUpdater::new);
+        ReusableTouchEventCallback touchEventCallback = new ReusableTouchEventCallback();
+        taskCapability.setTouchEventCallback(touchEventCallback);
+
+        // first assistant request
+        SettableFutureWrapper<FulfillmentResponse> firstTurnResult = new SettableFutureWrapper<>();
+        taskCapability.execute(
+                buildRequestArgs(SYNC, "stringSlotA", "apple"),
+                buildActionCallbackWithFulfillmentResponse(firstTurnResult));
+
+        // manual input request
+        Map<String, List<ParamValue>> touchEventParamValues = new HashMap<>();
+        touchEventParamValues.put(
+                "stringSlotB",
+                Collections.singletonList(
+                        ParamValue.newBuilder().setIdentifier("smoothie").build()));
+        taskCapability.updateParamValues(touchEventParamValues);
+
+        // second assistant request
+        SettableFutureWrapper<FulfillmentResponse> secondTurnResult = new SettableFutureWrapper<>();
+        taskCapability.execute(
+                buildRequestArgs(SYNC, "stringSlotA", "banana"),
+                buildActionCallbackWithFulfillmentResponse(secondTurnResult));
+
+        assertThat(firstTurnResult.getFuture().isDone()).isFalse();
+        assertThat(touchEventCallback.getLastResult()).isEqualTo(null);
+
+        // unblock first assistant request
+        slotAListener.setValidationResult(ValidationResult.newAccepted());
+        assertThat(firstTurnResult.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+        assertThat(touchEventCallback.getLastResult()).isEqualTo(null);
+
+        // unblock second assistant request
+        slotAListener.setValidationResult(ValidationResult.newAccepted());
+        assertThat(secondTurnResult.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+        assertThat(onFinishCb.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo("banana smoothie");
+        assertThat(touchEventCallback.getLastResult().getKind())
+                .isEqualTo(TouchEventResult.Kind.SUCCESS);
+    }
+
+    @Test
+    public void touchEvent_noDisambig_continuesProcessing() throws Exception {
+        TaskParamRegistry.Builder paramRegistryBuilder = TaskParamRegistry.builder();
+        SettableFutureWrapper<RequiredTaskUpdater> taskUpdaterCb = new SettableFutureWrapper<>();
+        SettableFutureWrapper<Boolean> onFinishCb = new SettableFutureWrapper<>();
+        CapabilityTwoEntityValues.Property property =
+                CapabilityTwoEntityValues.Property.newBuilder()
+                        .setSlotA(EntityProperty.newBuilder().setIsRequired(true).build())
+                        .setSlotB(EntityProperty.newBuilder().setIsRequired(true).build())
+                        .build();
+        paramRegistryBuilder.addTaskParameter(
+                "slotA",
+                paramValue -> !paramValue.hasIdentifier(),
+                GenericResolverInternal.fromAppEntityResolver(
+                        new AppEntityResolver<EntityValue>() {
+                            @NonNull
+                            @Override
+                            public ListenableFuture<ValidationResult> onReceived(
+                                    EntityValue unused) {
+                                return Futures.immediateFuture(ValidationResult.newAccepted());
+                            }
+
+                            @Override
+                            public ListenableFuture<EntitySearchResult<EntityValue>>
+                                    lookupAndRender(SearchAction<EntityValue> unused) {
+                                return Futures.immediateFuture(
+                                        EntitySearchResult.<EntityValue>newBuilder()
+                                                .addPossibleValue(EntityValue.ofId("entityValue1"))
+                                                .addPossibleValue(EntityValue.ofId("entityValue2"))
+                                                .build());
+                            }
+                        }),
+                Optional.of(TypeConverters::toEntity),
+                Optional.of(paramValue -> SearchAction.<EntityValue>newBuilder().build()),
+                TypeConverters::toEntityValue);
+        TaskCapabilityImpl<
+                        CapabilityTwoEntityValues.Property,
+                        CapabilityTwoEntityValues.Argument,
+                        Void,
+                        Void,
+                        RequiredTaskUpdater>
+                taskCapability =
+                        new TaskCapabilityImpl<>(
+                                "fakeId",
+                                CapabilityTwoEntityValues.ACTION_SPEC,
+                                property,
+                                paramRegistryBuilder.build(),
+                                /* onInitListener= */ Optional.of(
+                                        taskUpdater -> {
+                                            taskUpdaterCb.set(taskUpdater);
+                                            return Futures.immediateVoidFuture();
+                                        }),
+                                /* onReadyToConfirmListener= */ Optional.empty(),
+                                /* onFinishListener= */ (paramValuesMap) -> {
+                            onFinishCb.set(true);
+                            return Futures.immediateFuture(
+                                            ExecutionResult.getDefaultInstance());
+                        },
+                                /* confirmationOutputBindings= */ new HashMap<>(),
+                                /* executionOutputBindings= */ new HashMap<>(),
+                                Runnable::run);
+        taskCapability.setTaskUpdaterSupplier(RequiredTaskUpdater::new);
+        SettableFutureWrapper<FulfillmentResponse> touchEventCb = new SettableFutureWrapper<>();
+        TouchEventCallback touchEventCallback = buildTouchEventCallback(touchEventCb);
+        taskCapability.setTouchEventCallback(touchEventCallback);
+
+        // turn 1
+        SettableFutureWrapper<Boolean> turn1Finished = new SettableFutureWrapper<>();
+        taskCapability.execute(
+                buildRequestArgs(
+                        SYNC, "slotA", buildSearchActionParamValue("query"), "slotB", "anything"),
+                buildActionCallback(turn1Finished));
+
+        assertThat(turn1Finished.getFuture().get(CB_TIMEOUT, MILLISECONDS)).isTrue();
+        assertThat(taskCapability.getAppAction())
+                .isEqualTo(
+                        AppAction.newBuilder()
+                                .setName("actions.intent.TEST")
+                                .setIdentifier("fakeId")
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("slotA")
+                                                .setIsRequired(true)
+                                                .addCurrentValue(
+                                                        CurrentValue.newBuilder()
+                                                                .setValue(
+                                                                        buildSearchActionParamValue(
+                                                                                "query"))
+                                                                .setStatus(
+                                                                        CurrentValue.Status
+                                                                                .DISAMBIG)
+                                                                .setDisambiguationData(
+                                                                        DisambiguationData
+                                                                                .newBuilder()
+                                                                                .addEntities(
+                                                                                        Entity
+                                                                                                .newBuilder()
+                                                                                                .setIdentifier(
+                                                                                                        "entityValue1")
+                                                                                                .setName(
+                                                                                                        "entityValue1")
+                                                                                                .build())
+                                                                                .addEntities(
+                                                                                        Entity
+                                                                                                .newBuilder()
+                                                                                                .setIdentifier(
+                                                                                                        "entityValue2")
+                                                                                                .setName(
+                                                                                                        "entityValue2")
+                                                                                                .build())
+                                                                                .build())
+                                                                .build())
+                                                .build())
+                                .addParams(
+                                        IntentParameter.newBuilder()
+                                                .setName("slotB")
+                                                .setIsRequired(true)
+                                                .addCurrentValue(
+                                                        CurrentValue.newBuilder()
+                                                                .setValue(
+                                                                        ParamValue.newBuilder()
+                                                                                .setStringValue(
+                                                                                        "anything")
+                                                                                .build())
+                                                                .setStatus(
+                                                                        CurrentValue.Status.PENDING)
+                                                                .build())
+                                                .build())
+                                .setTaskInfo(
+                                        TaskInfo.newBuilder()
+                                                .setSupportsPartialFulfillment(true)
+                                                .build())
+                                .build());
+
+        // turn 2
+        taskCapability.updateParamValues(
+                Collections.singletonMap(
+                        "slotA",
+                        Collections.singletonList(
+                                ParamValue.newBuilder()
+                                        .setIdentifier("entityValue1")
+                                        .setStringValue("entityValue1")
+                                        .build())));
+
+        assertThat(touchEventCb.getFuture().get(CB_TIMEOUT, MILLISECONDS))
+                .isEqualTo(FulfillmentResponse.getDefaultInstance());
+        assertThat(onFinishCb.getFuture().get()).isTrue();
+    }
+
+    private static class RequiredTaskUpdater extends AbstractTaskUpdater {
+        void setRequiredEntityValue(EntityValue entityValue) {
+            super.updateParamValues(
+                    Collections.singletonMap(
+                            "required",
+                            Collections.singletonList(TypeConverters.toParamValue(entityValue))));
+        }
+    }
+
+    private static class EmptyTaskUpdater extends AbstractTaskUpdater {}
+
+    private static class DeferredValueListener<T> implements ValueListener<T> {
+
+        final AtomicReference<Completer<ValidationResult>> mCompleterRef = new AtomicReference<>();
+
+        void setValidationResult(ValidationResult t) {
+            Completer<ValidationResult> completer = mCompleterRef.getAndSet(null);
+            if (completer == null) {
+                throw new IllegalStateException("no onReceived is waiting");
+            }
+            completer.set(t);
+        }
+
+        @NonNull
+        @Override
+        public ListenableFuture<ValidationResult> onReceived(T value) {
+            return CallbackToFutureAdapter.getFuture(
+                    newCompleter -> {
+                        Completer<ValidationResult> oldCompleter =
+                                mCompleterRef.getAndSet(newCompleter);
+                        if (oldCompleter != null) {
+                            oldCompleter.setCancelled();
+                        }
+                        return "waiting for setValidationResult";
+                    });
+        }
+    }
+
+    private static class CapabilityBuilder
+            extends AbstractCapabilityBuilder<
+                    CapabilityBuilder,
+                    Property,
+                    Argument,
+                    Output,
+                    Confirmation,
+                    RequiredTaskUpdater> {
+        @SuppressWarnings("CheckReturnValue")
+        private CapabilityBuilder() {
+            super(ACTION_SPEC);
+            setId("id");
+            setProperty(SINGLE_REQUIRED_FIELD_PROPERTY);
+        }
+
+        @NonNull
+        public final CapabilityBuilder setTaskHandlerBuilder(
+                TaskHandlerBuilder taskHandlerBuilder) {
+            return setTaskHandler(taskHandlerBuilder.build());
+        }
+    }
+
+    private static class TaskHandlerBuilder
+            extends AbstractTaskHandlerBuilder<
+                    TaskHandlerBuilder, Argument, Output, Confirmation, RequiredTaskUpdater> {
+
+        private TaskHandlerBuilder() {
+            super.registerExecutionOutput(
+                    "optionalStringOutput",
+                    Output::optionalStringField,
+                    TypeConverters::toParamValue);
+            super.registerRepeatedExecutionOutput(
+                    "repeatedStringOutput",
+                    Output::repeatedStringField,
+                    TypeConverters::toParamValue);
+        }
+
+        @Override
+        protected Supplier<RequiredTaskUpdater> getTaskUpdaterSupplier() {
+            return RequiredTaskUpdater::new;
+        }
+
+        @CanIgnoreReturnValue
+        public TaskHandlerBuilder setOnReadyToConfirmListener(
+                OnReadyToConfirmListenerInternal<Confirmation> listener) {
+            return super.setOnReadyToConfirmListenerInternal(listener);
+        }
+    }
+
+    private static class CapabilityBuilderWithRequiredConfirmation
+            extends AbstractCapabilityBuilder<
+                    CapabilityBuilderWithRequiredConfirmation,
+                    Property,
+                    Argument,
+                    Output,
+                    Confirmation,
+                    RequiredTaskUpdater> {
+        @SuppressWarnings("CheckReturnValue")
+        private CapabilityBuilderWithRequiredConfirmation() {
+            super(ACTION_SPEC);
+            setProperty(SINGLE_REQUIRED_FIELD_PROPERTY);
+            setId("id");
+        }
+
+        @NonNull
+        public final CapabilityBuilderWithRequiredConfirmation setTaskHandlerBuilder(
+                TaskHandlerBuilderWithRequiredConfirmation taskHandlerBuilder) {
+            return setTaskHandler(taskHandlerBuilder.build());
+        }
+    }
+
+    private static class TaskHandlerBuilderWithRequiredConfirmation
+            extends AbstractTaskHandlerBuilder<
+                    TaskHandlerBuilderWithRequiredConfirmation,
+                    Argument,
+                    Output,
+                    Confirmation,
+                    RequiredTaskUpdater> {
+
+        private TaskHandlerBuilderWithRequiredConfirmation() {
+            super(ConfirmationType.REQUIRED);
+            super.registerExecutionOutput(
+                    "optionalStringOutput",
+                    Output::optionalStringField,
+                    TypeConverters::toParamValue);
+            super.registerRepeatedExecutionOutput(
+                    "repeatedStringOutput",
+                    Output::repeatedStringField,
+                    TypeConverters::toParamValue);
+            super.registerConfirmationOutput(
+                    "optionalStringOutput",
+                    Confirmation::optionalStringField,
+                    TypeConverters::toParamValue);
+        }
+
+        @Override
+        protected Supplier<RequiredTaskUpdater> getTaskUpdaterSupplier() {
+            return RequiredTaskUpdater::new;
+        }
+
+        @CanIgnoreReturnValue
+        public TaskHandlerBuilderWithRequiredConfirmation setOnReadyToConfirmListener(
+                OnReadyToConfirmListenerInternal<Confirmation> listener) {
+            return super.setOnReadyToConfirmListenerInternal(listener);
+        }
+    }
+}
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
new file mode 100644
index 0000000..f1f6ae4
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskCapabilityUtilsTest.java
@@ -0,0 +1,105 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.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", StringProperty.newBuilder().setIsRequired(true).build()));
+
+        assertThat(TaskCapabilityUtils.isSlotFillingComplete(args, intentParameters)).isTrue();
+    }
+
+    @Test
+    public void isSlotFillingComplete_notAllRequiredParamsFilled_returnsFalse() {
+        List<IntentParameter> intentParameters = new ArrayList<>();
+        intentParameters.add(
+                PropertyConverter.getIntentParameter(
+                        "required", StringProperty.newBuilder().setIsRequired(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/TaskSlotProcessorTest.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.java
new file mode 100644
index 0000000..6ec0dfb
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/task/impl/TaskSlotProcessorTest.java
@@ -0,0 +1,383 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ValueListListener;
+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 com.google.common.util.concurrent.ListenableFuture;
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+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.List;
+import java.util.Optional;
+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> onReceived(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>() {
+                    @NonNull
+                    @Override
+                    public ListenableFuture<ValidationResult> onReceived(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 ValueListListener<T>() {
+                    @NonNull
+                    @Override
+                    public ListenableFuture<ValidationResult> onReceived(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> onReceived(T value) {
+                        valueConsumer.accept(value);
+                        return Futures.immediateFuture(validationResult);
+                    }
+
+                    @Override
+                    public ListenableFuture<EntitySearchResult<T>> lookupAndRender(
+                            SearchAction<T> searchAction) {
+                        appSearchConsumer.accept(searchAction);
+                        return Futures.immediateFuture(appSearchResult);
+                    }
+                });
+    }
+
+    @Test
+    public void processSlot_singleValue_accepted() throws Exception {
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "singularValue",
+                                (paramValue) -> false,
+                                createValueResolver(ValidationResult.newAccepted()),
+                                Optional.empty(),
+                                Optional.empty(),
+                                TypeConverters::toStringValue)
+                        .build();
+        List<ParamValue> args =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setIdentifier("testValue").build());
+
+        SlotProcessingResult result =
+                TaskSlotProcessor.processSlot(
+                                "singularValue",
+                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
+                                paramRegistry,
+                                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 {
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "singularValue",
+                                (paramValue) -> false,
+                                createValueResolver(ValidationResult.newRejected()),
+                                Optional.empty(),
+                                Optional.empty(),
+                                TypeConverters::toStringValue)
+                        .build();
+        List<ParamValue> args =
+                Collections.singletonList(
+                        ParamValue.newBuilder().setIdentifier("testValue").build());
+
+        SlotProcessingResult result =
+                TaskSlotProcessor.processSlot(
+                                "singularValue",
+                                TaskCapabilityUtils.paramValuesToCurrentValue(args, Status.PENDING),
+                                paramRegistry,
+                                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<>();
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "repeatedValue",
+                                (paramValue) -> false,
+                                createValueListResolver(ValidationResult.newAccepted(),
+                                        lastReceivedArgs::set),
+                                Optional.empty(),
+                                Optional.empty(),
+                                TypeConverters::toStringValue)
+                        .build();
+        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),
+                                paramRegistry,
+                                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<>();
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "repeatedValue",
+                                (paramValue) -> false,
+                                createValueListResolver(ValidationResult.newRejected(),
+                                        lastReceivedArgs::set),
+                                Optional.empty(),
+                                Optional.empty(),
+                                TypeConverters::toStringValue)
+                        .build();
+        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),
+                                paramRegistry,
+                                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<>();
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "assistantDrivenSlot",
+                                (paramValue) -> !paramValue.hasIdentifier(),
+                                createAssistantDisambigResolver(
+                                        ValidationResult.newAccepted(), onReceivedCb::set,
+                                        renderCb::set),
+                                Optional.empty(),
+                                Optional.empty(),
+                                TypeConverters::toStringValue)
+                        .build();
+        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, paramRegistry,
+                                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);
+        TaskParamRegistry paramRegistry =
+                TaskParamRegistry.builder()
+                        .addTaskParameter(
+                                "appDrivenSlot",
+                                (paramValue) -> true, // always invoke app-grounding in all cases
+                                resolver,
+                                Optional.of((unused) -> Entity.getDefaultInstance()),
+                                Optional.of(
+                                        (unused) ->
+                                                SearchAction.<String>newBuilder()
+                                                        .setQuery("A")
+                                                        .setObject("nested")
+                                                        .build()),
+                                TypeConverters::toStringValue) // Not invoked
+                        .build();
+        List<CurrentValue> values =
+                Arrays.asList(
+                        CurrentValue.newBuilder()
+                                .setStatus(Status.PENDING)
+                                .setValue(buildSearchActionParamValue("A"))
+                                .build());
+
+        SlotProcessingResult result =
+                TaskSlotProcessor.processSlot("appDrivenSlot", values, paramRegistry,
+                        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/testing/ArgumentUtils.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/ArgumentUtils.java
new file mode 100644
index 0000000..32a97b4
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/ArgumentUtils.java
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.testing;
+
+import static java.util.stream.Collectors.toMap;
+
+import androidx.appactions.interaction.capabilities.core.impl.ArgumentsWrapper;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment;
+import androidx.appactions.interaction.proto.FulfillmentRequest.Fulfillment.FulfillmentParam;
+import androidx.appactions.interaction.proto.ParamValue;
+
+import com.google.protobuf.Struct;
+import com.google.protobuf.Value;
+
+import java.util.ArrayList;
+import java.util.Collections;
+import java.util.LinkedHashMap;
+import java.util.List;
+import java.util.Map;
+import java.util.Objects;
+
+/** Utilities for creating objects to make testing classes less verbose. */
+public final class ArgumentUtils {
+
+    private ArgumentUtils() {
+    }
+
+    /**
+     * Useful for one-shot BIIs where the task data is not needed and the ParamValues are singular.
+     */
+    public static ArgumentsWrapper buildArgs(Map<String, ParamValue> args) {
+        return ArgumentUtils.buildListArgs(
+                args.entrySet().stream()
+                        .collect(toMap(e -> e.getKey(),
+                                e -> Collections.singletonList(e.getValue()))));
+    }
+
+    /** Useful for one-shot BIIs where the task data is not needed. */
+    public static ArgumentsWrapper buildListArgs(Map<String, List<ParamValue>> args) {
+        Fulfillment.Builder builder = Fulfillment.newBuilder();
+        for (Map.Entry<String, List<ParamValue>> entry : args.entrySet()) {
+            builder.addParams(
+                    FulfillmentParam.newBuilder().setName(entry.getKey()).addAllValues(
+                            entry.getValue()));
+        }
+        return ArgumentsWrapper.create(builder.build());
+    }
+
+    private static ParamValue toParamValue(Object argVal) {
+        if (argVal instanceof Integer) {
+            return ParamValue.newBuilder().setNumberValue(((Integer) argVal).intValue()).build();
+        } else if (argVal instanceof Double) {
+            return ParamValue.newBuilder().setNumberValue(((Double) argVal).doubleValue()).build();
+        } else if (argVal instanceof String) {
+            return ParamValue.newBuilder().setStringValue((String) argVal).build();
+        } else if (argVal instanceof Enum) {
+            return ParamValue.newBuilder().setIdentifier(argVal.toString()).build();
+        } else if (argVal instanceof ParamValue) {
+            return (ParamValue) argVal;
+        }
+        throw new IllegalArgumentException("invalid argument type.");
+    }
+
+    public static ParamValue buildSearchActionParamValue(String query) {
+        return ParamValue.newBuilder()
+                .setStructValue(
+                        Struct.newBuilder()
+                                .putFields("@type",
+                                        Value.newBuilder().setStringValue("SearchAction").build())
+                                .putFields("query",
+                                        Value.newBuilder().setStringValue(query).build())
+                                .build())
+                .build();
+    }
+
+    /**
+     * Convenience method to build ArgumentsWrapper based on plain java types. Input args should be
+     * even in length, where each String argName is followed by any type of argVal.
+     */
+    public static ArgumentsWrapper buildRequestArgs(Fulfillment.Type type, Object... args) {
+        Fulfillment.Builder builder = Fulfillment.newBuilder();
+        if (type != Fulfillment.Type.UNRECOGNIZED) {
+            builder.setType(type);
+        }
+        if (args.length == 0) {
+            return ArgumentsWrapper.create(builder.build());
+        }
+        if (args.length % 2 != 0) {
+            throw new IllegalArgumentException("Must call function with even number of args");
+        }
+        Map<String, List<ParamValue>> argsMap = new LinkedHashMap<>();
+        for (int argNamePos = 0, argValPos = 1; argValPos < args.length; ) {
+            if (!(args[argNamePos] instanceof String)) {
+                throw new IllegalArgumentException(
+                        "Argument must be instance of String but got: "
+                                + args[argNamePos].getClass());
+            }
+            String argName = (String) args[argNamePos];
+            ParamValue paramValue = toParamValue(args[argValPos]);
+            argsMap.computeIfAbsent(argName, (unused) -> new ArrayList<>());
+            Objects.requireNonNull(argsMap.get(argName)).add(paramValue);
+            argNamePos += 2;
+            argValPos += 2;
+        }
+        argsMap.entrySet().stream()
+                .forEach(
+                        entry ->
+                                builder.addParams(
+                                        FulfillmentParam.newBuilder()
+                                                .setName(entry.getKey())
+                                                .addAllValues(entry.getValue())));
+        return ArgumentsWrapper.create(builder.build());
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.java
new file mode 100644
index 0000000..da5a952
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/TestingUtils.java
@@ -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.appactions.interaction.capabilities.core.testing;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.ConfirmationOutput;
+import androidx.appactions.interaction.capabilities.core.ExecutionResult;
+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.Futures;
+import androidx.appactions.interaction.capabilities.core.task.OnDialogFinishListener;
+import androidx.appactions.interaction.capabilities.core.task.OnInitListener;
+import androidx.appactions.interaction.capabilities.core.task.OnReadyToConfirmListener;
+import androidx.appactions.interaction.capabilities.core.testing.spec.SettableFutureWrapper;
+import androidx.appactions.interaction.proto.FulfillmentResponse;
+import androidx.appactions.interaction.proto.TouchEventMetadata;
+
+import com.google.auto.value.AutoOneOf;
+import com.google.auto.value.AutoValue;
+import com.google.common.util.concurrent.ListenableFuture;
+
+import java.util.Optional;
+import java.util.concurrent.atomic.AtomicReference;
+
+public final class TestingUtils {
+
+    public static final long CB_TIMEOUT = 1000L;
+
+    private TestingUtils() {}
+
+    public static CallbackInternal buildActionCallback(SettableFutureWrapper<Boolean> future) {
+        return new CallbackInternal() {
+            @Override
+            public void onSuccess(FulfillmentResponse response) {
+                future.set(true);
+            }
+
+            @Override
+            public void onError(ErrorStatusInternal error) {
+                future.set(false);
+            }
+        };
+    }
+
+    public static CallbackInternal buildActionCallbackWithFulfillmentResponse(
+            SettableFutureWrapper<FulfillmentResponse> future) {
+        return new CallbackInternal() {
+            @Override
+            public void onSuccess(FulfillmentResponse response) {
+                future.set(response);
+            }
+
+            @Override
+            public void onError(ErrorStatusInternal error) {
+                future.setException(
+                        new IllegalStateException(
+                                String.format(
+                                        "expected FulfillmentResponse, but got ErrorStatus=%s "
+                                                + "instead",
+                                        error)));
+            }
+        };
+    }
+
+    public static CallbackInternal buildErrorActionCallback(
+            SettableFutureWrapper<ErrorStatusInternal> future) {
+        return new CallbackInternal() {
+            @Override
+            public void onSuccess(FulfillmentResponse response) {
+                future.setException(
+                        new IllegalStateException(
+                                "expected ErrorStatus, but got FulfillmentResponse instead"));
+            }
+
+            @Override
+            public void onError(ErrorStatusInternal error) {
+                future.set(error);
+            }
+        };
+    }
+
+    public static <T> Optional<OnInitListener<T>> buildOnInitListener(
+            SettableFutureWrapper<T> updaterFuture) {
+        return Optional.of(
+                new OnInitListener<T>() {
+                    @NonNull
+                    @Override
+                    public ListenableFuture<Void> onInit(T taskUpdater) {
+                        updaterFuture.set(taskUpdater);
+                        return Futures.immediateVoidFuture();
+                    }
+                });
+    }
+
+    public static <ArgumentT, ConfirmationT>
+            Optional<OnReadyToConfirmListener<ArgumentT, ConfirmationT>>
+                    buildOnReadyToConfirmListener(SettableFutureWrapper<ArgumentT> future) {
+        return Optional.of(
+                (finalArgs) -> {
+                    future.set(finalArgs);
+                    return Futures.immediateFuture(
+                            ConfirmationOutput.<ConfirmationT>getDefaultInstanceWithConfirmation());
+                });
+    }
+
+    public static <ArgumentT, OutputT>
+            OnDialogFinishListener<ArgumentT, OutputT> buildOnFinishListener(
+                    SettableFutureWrapper<ArgumentT> future) {
+        return (finalArgs) -> {
+            future.set(finalArgs);
+            return Futures.immediateFuture(ExecutionResult.<OutputT>getDefaultInstanceWithOutput());
+        };
+    }
+
+    public static TouchEventCallback buildTouchEventCallback(
+            SettableFutureWrapper<FulfillmentResponse> future) {
+        return new TouchEventCallback() {
+            @Override
+            public void onSuccess(
+                    @NonNull FulfillmentResponse fulfillmentResponse,
+                    @NonNull TouchEventMetadata touchEventMetadata) {
+                future.set(fulfillmentResponse);
+            }
+
+            @Override
+            public void onError(@NonNull ErrorStatusInternal errorStatus) {}
+        };
+    }
+
+    @AutoValue
+    public abstract static class TouchEventSuccessResult {
+        public static TouchEventSuccessResult create(
+                FulfillmentResponse fulfillmentResponse, TouchEventMetadata touchEventMetadata) {
+            return new AutoValue_TestingUtils_TouchEventSuccessResult(
+                    fulfillmentResponse, touchEventMetadata);
+        }
+
+        public abstract FulfillmentResponse fulfillmentResponse();
+
+        public abstract TouchEventMetadata touchEventMetadata();
+    }
+
+    @AutoOneOf(TouchEventResult.Kind.class)
+    public abstract static class TouchEventResult {
+        public static TouchEventResult of(TouchEventSuccessResult result) {
+            return AutoOneOf_TestingUtils_TouchEventResult.success(result);
+        }
+
+        public static TouchEventResult of(ErrorStatusInternal error) {
+            return AutoOneOf_TestingUtils_TouchEventResult.error(error);
+        }
+
+        public abstract Kind getKind();
+
+        public abstract TouchEventSuccessResult success();
+
+        public abstract ErrorStatusInternal error();
+
+        public enum Kind {
+            SUCCESS,
+            ERROR
+        }
+    }
+
+    public static class ReusableTouchEventCallback implements TouchEventCallback {
+
+        AtomicReference<TouchEventResult> mResultRef = new AtomicReference<>();
+
+        @Override
+        public void onSuccess(
+                @NonNull FulfillmentResponse fulfillmentResponse,
+                @NonNull TouchEventMetadata touchEventMetadata) {
+            mResultRef.set(
+                    TouchEventResult.of(
+                            TouchEventSuccessResult.create(
+                                    fulfillmentResponse, touchEventMetadata)));
+        }
+
+        @Override
+        public void onError(@NonNull ErrorStatusInternal errorStatus) {
+            mResultRef.set(TouchEventResult.of(errorStatus));
+        }
+
+        public TouchEventResult getLastResult() {
+            return mResultRef.get();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Argument.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Argument.java
new file mode 100644
index 0000000..4e625f2
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Argument.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.appactions.interaction.capabilities.core.testing.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+import java.util.Optional;
+
+/** Testing implementation of a capability Argument. */
+@AutoValue
+public abstract class Argument {
+
+    public static Builder newBuilder() {
+        return new AutoValue_Argument.Builder();
+    }
+
+    public abstract Optional<EntityValue> requiredEntityField();
+
+    public abstract Optional<String> optionalStringField();
+
+    public abstract Optional<TestEnum> enumField();
+
+    public abstract Optional<List<String>> repeatedStringField();
+
+    /** Builder for the testing Argument. */
+    @AutoValue.Builder
+    public abstract static class Builder implements BuilderOf<Argument> {
+
+        public abstract Builder setRequiredEntityField(EntityValue value);
+
+        public abstract Builder setOptionalStringField(String value);
+
+        public abstract Builder setEnumField(TestEnum value);
+
+        public abstract Builder setRepeatedStringField(List<String> value);
+
+        @NonNull
+        @Override
+        public abstract Argument build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java
new file mode 100644
index 0000000..fc4b9b4
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityStructFill.java
@@ -0,0 +1,103 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.converters.TypeConverters;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder;
+import androidx.appactions.interaction.capabilities.core.properties.SimpleProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+import androidx.appactions.interaction.capabilities.core.values.ListItem;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/** Used to test the filling behavior of structured entities (e.g. ListItem) */
+public final class CapabilityStructFill {
+
+    private static final String CAPABILITY_NAME = "actions.intent.TEST";
+    public static final ActionSpec<Property, Argument, Void> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .bindStructParameter(
+                            "listItem",
+                            Property::itemList,
+                            Argument.Builder::setListItem,
+                            TypeConverters::toListItem)
+                    .bindOptionalStringParameter(
+                            "string", Property::anyString, Argument.Builder::setAnyString)
+                    .build();
+
+    private CapabilityStructFill() {
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Argument {
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityStructFill_Argument.Builder();
+        }
+
+        public abstract Optional<ListItem> listItem();
+
+        public abstract Optional<String> anyString();
+
+        /** Builder for the testing Argument. */
+        @AutoValue.Builder
+        public abstract static class Builder implements BuilderOf<Argument> {
+
+            public abstract Builder setListItem(@NonNull ListItem value);
+
+            public abstract Builder setAnyString(@NonNull String value);
+
+            @NonNull
+            @Override
+            public abstract Argument build();
+        }
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Property {
+        @NonNull
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityStructFill_Property.Builder();
+        }
+
+        public abstract Optional<SimpleProperty> itemList();
+
+        public abstract Optional<StringProperty> anyString();
+
+        /** Builder for {@link Property} */
+        @AutoValue.Builder
+        public abstract static class Builder {
+
+            @NonNull
+            public abstract Builder setItemList(@NonNull SimpleProperty value);
+
+            @NonNull
+            public abstract Builder setAnyString(@NonNull StringProperty value);
+
+            @NonNull
+            public abstract Property build();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
new file mode 100644
index 0000000..298a57f
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoEntityValues.java
@@ -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.appactions.interaction.capabilities.core.testing.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.values.EntityValue;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+public final class CapabilityTwoEntityValues {
+
+    private static final String CAPABILITY_NAME = "actions.intent.TEST";
+    public static final ActionSpec<Property, Argument, Void> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .bindOptionalEntityParameter("slotA", Property::slotA,
+                            Argument.Builder::setSlotA)
+                    .bindOptionalEntityParameter("slotB", Property::slotB,
+                            Argument.Builder::setSlotB)
+                    .build();
+
+    private CapabilityTwoEntityValues() {
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Argument {
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityTwoEntityValues_Argument.Builder();
+        }
+
+        public abstract Optional<EntityValue> slotA();
+
+        public abstract Optional<EntityValue> slotB();
+
+        /** Builder for the testing Argument. */
+        @AutoValue.Builder
+        public abstract static class Builder implements BuilderOf<Argument> {
+
+            public abstract Builder setSlotA(@NonNull EntityValue value);
+
+            public abstract Builder setSlotB(@NonNull EntityValue value);
+
+            @NonNull
+            @Override
+            public abstract Argument build();
+        }
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Property {
+        @NonNull
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityTwoEntityValues_Property.Builder();
+        }
+
+        public abstract Optional<EntityProperty> slotA();
+
+        public abstract Optional<EntityProperty> slotB();
+
+        /** Builder for {@link Property} */
+        @AutoValue.Builder
+        public abstract static class Builder {
+
+            @NonNull
+            public abstract Builder setSlotA(@NonNull EntityProperty value);
+
+            @NonNull
+            public abstract Builder setSlotB(@NonNull EntityProperty value);
+
+            @NonNull
+            public abstract Property build();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
new file mode 100644
index 0000000..f504c72
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/CapabilityTwoStrings.java
@@ -0,0 +1,96 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpec;
+import androidx.appactions.interaction.capabilities.core.impl.spec.ActionSpecBuilder;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+public final class CapabilityTwoStrings {
+
+    private static final String CAPABILITY_NAME = "actions.intent.TEST";
+    public static final ActionSpec<Property, Argument, Void> ACTION_SPEC =
+            ActionSpecBuilder.ofCapabilityNamed(CAPABILITY_NAME)
+                    .setDescriptor(Property.class)
+                    .setArgument(Argument.class, Argument::newBuilder)
+                    .bindOptionalStringParameter(
+                            "stringSlotA", Property::stringSlotA, Argument.Builder::setStringSlotA)
+                    .bindOptionalStringParameter(
+                            "stringSlotB", Property::stringSlotB, Argument.Builder::setStringSlotB)
+                    .build();
+
+    private CapabilityTwoStrings() {
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Argument {
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityTwoStrings_Argument.Builder();
+        }
+
+        public abstract Optional<String> stringSlotA();
+
+        public abstract Optional<String> stringSlotB();
+
+        /** Builder for the testing Argument. */
+        @AutoValue.Builder
+        public abstract static class Builder implements BuilderOf<Argument> {
+
+            public abstract Builder setStringSlotA(@NonNull String value);
+
+            public abstract Builder setStringSlotB(@NonNull String value);
+
+            @NonNull
+            @Override
+            public abstract Argument build();
+        }
+    }
+
+    /** Two required strings */
+    @AutoValue
+    public abstract static class Property {
+        @NonNull
+        public static Builder newBuilder() {
+            return new AutoValue_CapabilityTwoStrings_Property.Builder();
+        }
+
+        public abstract Optional<StringProperty> stringSlotA();
+
+        public abstract Optional<StringProperty> stringSlotB();
+
+        /** Builder for {@link Property} */
+        @AutoValue.Builder
+        public abstract static class Builder {
+
+            @NonNull
+            public abstract Builder setStringSlotA(@NonNull StringProperty value);
+
+            @NonNull
+            public abstract Builder setStringSlotB(@NonNull StringProperty value);
+
+            @NonNull
+            public abstract Property build();
+        }
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Confirmation.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Confirmation.java
new file mode 100644
index 0000000..491a282
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Confirmation.java
@@ -0,0 +1,40 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.testing.spec;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/** Testing implementation of a capability Confirmation. */
+@AutoValue
+public abstract class Confirmation {
+
+    public static Builder builder() {
+        return new AutoValue_Confirmation.Builder();
+    }
+
+    public abstract Optional<String> optionalStringField();
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+
+        public abstract Builder setOptionalStringField(String value);
+
+        public abstract Confirmation build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Output.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Output.java
new file mode 100644
index 0000000..c18a61a
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Output.java
@@ -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.appactions.interaction.capabilities.core.testing.spec;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.List;
+import java.util.Optional;
+
+/** Testing implementation of a capability Output. */
+@AutoValue
+public abstract class Output {
+
+    public static Builder builder() {
+        return new AutoValue_Output.Builder();
+    }
+
+    public abstract Optional<String> optionalStringField();
+
+    @SuppressWarnings("AutoValueImmutableFields")
+    public abstract List<String> repeatedStringField();
+
+    @AutoValue.Builder
+    public abstract static class Builder {
+
+        public abstract Builder setOptionalStringField(String value);
+
+        public abstract Builder setRepeatedStringField(List<String> value);
+
+        public abstract Output build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Property.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Property.java
new file mode 100644
index 0000000..78d3e97
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/Property.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.appactions.interaction.capabilities.core.testing.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+import androidx.appactions.interaction.capabilities.core.properties.EntityProperty;
+import androidx.appactions.interaction.capabilities.core.properties.EnumProperty;
+import androidx.appactions.interaction.capabilities.core.properties.StringProperty;
+
+import com.google.auto.value.AutoValue;
+
+import java.util.Optional;
+
+/** Testing implementation of a capability Property. */
+@AutoValue
+public abstract class Property {
+
+    public static Builder newBuilder() {
+        return new AutoValue_Property.Builder();
+    }
+
+    public abstract EntityProperty requiredEntityField();
+
+    public abstract Optional<StringProperty> optionalStringField();
+
+    public abstract Optional<EnumProperty<TestEnum>> enumField();
+
+    public abstract Optional<StringProperty> repeatedStringField();
+
+    /** Builder for the testing Property. */
+    @AutoValue.Builder
+    public abstract static class Builder implements BuilderOf<Property> {
+
+        public abstract Builder setRequiredEntityField(EntityProperty property);
+
+        public abstract Builder setOptionalStringField(StringProperty property);
+
+        public abstract Builder setEnumField(EnumProperty<TestEnum> property);
+
+        public abstract Builder setRepeatedStringField(StringProperty property);
+
+        @NonNull
+        @Override
+        public abstract Property build();
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/SettableFutureWrapper.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/SettableFutureWrapper.java
new file mode 100644
index 0000000..0c3d697
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/SettableFutureWrapper.java
@@ -0,0 +1,48 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.appactions.interaction.capabilities.core.testing.spec;
+
+import androidx.concurrent.futures.CallbackToFutureAdapter;
+
+import com.google.common.util.concurrent.ListenableFuture;
+
+/** Utility class to make working with callbacks in tests easier. */
+public final class SettableFutureWrapper<V> {
+    private final ListenableFuture<V> mFuture;
+    private CallbackToFutureAdapter.Completer<V> mCompleter;
+
+    public SettableFutureWrapper() {
+        this.mFuture =
+                CallbackToFutureAdapter.getFuture(
+                        completer -> {
+                            this.mCompleter = completer;
+                            return "SettableFutureWrapper";
+                        });
+    }
+
+    public ListenableFuture<V> getFuture() {
+        return mFuture;
+    }
+
+    public boolean set(V result) {
+        return mCompleter.set(result);
+    }
+
+    public boolean setException(Throwable t) {
+        return mCompleter.setException(t);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEntity.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEntity.java
new file mode 100644
index 0000000..ee3db39
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEntity.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.appactions.interaction.capabilities.core.testing.spec;
+
+import androidx.annotation.NonNull;
+import androidx.appactions.interaction.capabilities.core.impl.BuilderOf;
+
+import com.google.auto.value.AutoValue;
+
+import java.time.Duration;
+import java.time.ZonedDateTime;
+import java.util.Optional;
+
+/** A test class for capability value. */
+@AutoValue
+public abstract class TestEntity {
+
+    public static Builder newBuilder() {
+        return new AutoValue_TestEntity.Builder();
+    }
+
+    public abstract Optional<String> getName();
+
+    public abstract Optional<Duration> getDuration();
+
+    public abstract Optional<ZonedDateTime> getZonedDateTime();
+
+    public abstract Optional<TestEnum> getEnum();
+
+    public abstract Optional<TestEntity> getEntity();
+
+    public enum TestEnum {
+        VALUE_1("value_1"),
+        VALUE_2("value_2");
+
+        private final String mStringValue;
+
+        TestEnum(String stringValue) {
+            this.mStringValue = stringValue;
+        }
+
+        @NonNull
+        @Override
+        public String toString() {
+            return mStringValue;
+        }
+    }
+
+    @AutoValue.Builder
+    public abstract static class Builder implements BuilderOf<TestEntity> {
+
+        public abstract Builder setName(String name);
+
+        public abstract Builder setDuration(Duration duration);
+
+        public abstract Builder setZonedDateTime(ZonedDateTime date);
+
+        public abstract Builder setEnum(TestEnum enumValue);
+
+        public abstract Builder setEntity(TestEntity entity);
+    }
+}
diff --git a/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEnum.java b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEnum.java
new file mode 100644
index 0000000..c325c57
--- /dev/null
+++ b/appactions/interaction/interaction-capabilities-core/src/test/java/androidx/appactions/interaction/capabilities/core/testing/spec/TestEnum.java
@@ -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.interaction.capabilities.core.testing.spec;
+
+/** Sample enum value for testing. */
+public enum TestEnum {
+    VALUE_1,
+    VALUE_2,
+}
diff --git a/appactions/interaction/interaction-service/build.gradle b/appactions/interaction/interaction-service/build.gradle
index fa19659..d528b0e 100644
--- a/appactions/interaction/interaction-service/build.gradle
+++ b/appactions/interaction/interaction-service/build.gradle
@@ -19,9 +19,31 @@
 plugins {
     id("AndroidXPlugin")
     id("com.android.library")
+    id("com.google.protobuf")
 }
 
 dependencies {
+    implementation(libs.protobufLite)
+    implementation("androidx.annotation:annotation:1.1.0")
+    implementation(project(":appactions:interaction:interaction-proto"))
+}
+
+protobuf {
+    protoc {
+        artifact = libs.protobufCompiler.get()
+    }
+    // Generates the java proto-lite code for the protos in this project. See
+    // https://github.com/google/protobuf-gradle-plugin#customizing-protobuf-compilation
+    // for more information.
+    generateProtoTasks {
+        all().each { task ->
+            task.builtins {
+                java {
+                    option "lite"
+                }
+            }
+        }
+    }
 }
 
 android {
diff --git a/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/proto/package-info.java b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/proto/package-info.java
new file mode 100644
index 0000000..93db9d1
--- /dev/null
+++ b/appactions/interaction/interaction-service/src/main/java/androidx/appactions/interaction/service/proto/package-info.java
@@ -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.
+ */
+
+/** @hide */
+@RestrictTo(RestrictTo.Scope.LIBRARY)
+package androidx.appactions.interaction.service.proto;
+
+import androidx.annotation.RestrictTo;
diff --git a/appactions/interaction/interaction-service/src/main/proto/app_interaction_service.proto b/appactions/interaction/interaction-service/src/main/proto/app_interaction_service.proto
new file mode 100644
index 0000000..d62822a
--- /dev/null
+++ b/appactions/interaction/interaction-service/src/main/proto/app_interaction_service.proto
@@ -0,0 +1,264 @@
+/*
+ * 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.
+ */
+syntax = "proto3";
+
+package androidx.appactions.interaction.service.proto;
+
+import "app_actions_data.proto";
+import "grounding.proto";
+import "touch_event.proto";
+
+option java_package = "androidx.appactions.interaction.service.proto";
+option java_outer_classname = "AppActionsServiceProto";
+
+// Request from host client (Assistant) to service provider (app) as part of the
+// RequestFulfillment streaming call. Throughout a dialog facilitated by the
+// provider, host might send multiple Request every time the
+// user issues a query. This proto contains user's query (parsed into BII).
+// NEXT_ID: 3
+message Request {
+  // Session_id is used to identify the hosts. When there are multiple hosts
+  // connecting to the provider at the same time for different requests,
+  // provider can use the session_ids to distinguish sessions, thus
+  // providing different responses to different hosts.
+  int32 session_id = 1;
+  // FulfillmentRequest contains the request data sent from Assistant, such as
+  // new values of BII arguments.
+  .androidx.appactions.interaction.proto.FulfillmentRequest fulfillment_request = 2;
+}
+
+// Response from service provider (app) to host client (Assistant) as part of
+// the RequestFulfillment streaming call. While the RequestFulfillment streaming
+// call is not completed, provider can stream Response when
+// there is new TTS to play or new UI to display.
+// NEXT_ID: 7
+message Response {
+  // Response of the capability invocation. Contains single-turn information
+  // (such as structured data for TTS). State updates (e.g. slot value changes)
+  // are reflected in the app_actions_context).
+  .androidx.appactions.interaction.proto.FulfillmentResponse fulfillment_response = 1;
+  // Context from the app. This should invalidate the Assistant app cache.
+  .androidx.appactions.interaction.proto.AppActionsContext app_actions_context = 2;
+  // Signal to the host that there is a new UI (RemoteViews or Tiles) ready to
+  // be requested.
+  UiUpdate ui_update = 3;
+  // Signal to the host that there is a new UI (RemoteViews or Tiles) ready to
+  // be requested, specifically for a list view.
+  CollectionUpdate collection_update = 4;
+  Status ending_status = 5;
+  // Additional single-turn response data (in addition to
+  // 'fulfillment_response') when the capability processing was triggered via
+  // manual input.
+  .androidx.appactions.interaction.proto.TouchEventMetadata touch_event_metadata = 6;
+}
+
+// The ending status of the dialog.
+// NEXT_ID: 3
+message Status {
+  string message = 1;
+  Code status_code = 2;
+
+  enum Code {
+    CODE_UNSPECIFIED = 0;
+    COMPLETE = 1;
+    USER_CANCELED = 2;
+    ERROR = 3;
+    TIMEOUT = 4;
+  }
+}
+// NEXT_ID: 1
+message UiUpdate {}
+
+// NEXT_ID: 2
+message CollectionUpdate {
+  repeated int32 view_ids = 1;
+}
+
+// Host request to fetch UI
+// NEXT_ID: 2
+message UiRequest {
+  int32 session_id = 1;
+}
+
+// A wrapper for weartile Layout and Resources
+// NEXT_ID: 3
+message TileLayout {
+  // bytes for androidx.wear.tiles.proto.Layout
+  bytes layout = 1;
+  // bytes for androidx.wear.tiles.proto.Resources
+  bytes resources = 2;
+}
+
+// Information about the RemoteViews.
+// NEXT_ID: 5
+message RemoteViewsInfo {
+  oneof width {
+    // Fixed width in dp. Won't resize when host width changes.
+    float width_dp = 1;
+    // Take up as much horizontal space as possible, automatically resizes when
+    // host width changes
+    bool width_match_parent = 2;
+  }
+  oneof height {
+    // Fixed height in dp. Won't resize when host height changes.
+    float height_dp = 3;
+    // Take up as much vertical space as possible, automatically resizes when
+    // host height changes
+    bool height_match_parent = 4;
+  }
+}
+
+// Provider response to return UI. If the UI is RemoteViews, it will be attached
+// as trailer metadata of the response.
+// NEXT_ID: 3
+message UiResponse {
+  oneof ui_type {
+    TileLayout tile_layout = 1;
+    RemoteViewsInfo remote_views_info = 2;
+  }
+}
+
+// NEXT_ID: 5
+message HostProperties {
+  // The width of the host area (in dp) where the app content can be displayed.
+  float host_view_width_dp = 1;
+
+  // The height of the host area (in dp) where the app content can be displayed.
+  float host_view_height_dp = 2;
+
+  DeviceType device_type = 3;
+
+  bool requires_ui = 4;
+
+  enum ResponseType {
+    // default
+    TYPE_UNSPECIFIED = 0;
+
+    // The host supports displaying UI, text, and speech.
+    SPEECH_AND_UI = 1;
+
+    // The host supports playing TTS and receiving user voice query.
+    SPEECH_ONLY = 2;
+
+    // The host supports displaying a string text
+    UI_ONLY = 3;
+  }
+
+  enum DeviceType {
+    UNSPECIFIED = 0;   // default
+    MOBILE = 1;        // the host is an Android phone or tablet.
+    ANDROID_AUTO = 2;  // the host is an Android Auto device.
+    WEAR_OS = 3;       // The host is a WearOS device.
+    SPEAKER = 4;       // The host is a smart speaker.
+  }
+}
+
+// Request containing the specification of the host to prepare the session.
+// NEXT_ID: 4
+message StartSessionRequest {
+  HostProperties host_properties = 1;
+  string intent_name = 2;
+  string identifier = 3;
+}
+
+// Response providing a session_id. Session_id works like a cookie in browser.
+// It is used to identify a session.
+// NEXT_ID: 2
+message StartSessionResponse {
+  int32 session_id = 1;
+}
+
+// Request and response corresponding to methods on IRemoteViewsFactory.aidl
+// frameworks/base/core/java/com/android/internal/widget/IRemoteViewsFactory.aidl
+// NEXT_ID: 10
+message CollectionRequest {
+  int32 session_id = 1;
+  int32 view_id = 2;
+  oneof request_data {
+    OnDestroy on_destroy = 3;
+    GetCount get_count = 4;
+    GetViewAt get_view_at = 5;
+    GetLoadingView get_loading_view = 6;
+    GetViewTypeCount get_view_type_count = 7;
+    GetItemId get_item_id = 8;
+    HasStableIds has_stable_ids = 9;
+  }
+
+  message OnDestroy {}
+  message GetCount {}
+  message GetViewAt {
+    int32 position = 1;
+  }
+  message GetLoadingView {}
+  message GetViewTypeCount {}
+  message GetItemId {
+    int32 position = 1;
+  }
+  message HasStableIds {}
+}
+
+// NEXT_ID: 5
+message CollectionResponse {
+  oneof response_data {
+    GetCount get_count = 1;
+    GetViewTypeCount get_view_type_count = 2;
+    GetItemId get_item_id = 3;
+    HasStableIds has_stable_ids = 4;
+  }
+
+  message GetCount {
+    int32 count = 1;
+  }
+  message GetViewTypeCount {
+    int32 view_type_count = 1;
+  }
+  message GetItemId {
+    int64 item_id = 1;
+  }
+  message HasStableIds {
+    bool has_stable_ids = 1;
+  }
+}
+
+// Service between Assistant app and 3P app. The 3P app acts as the server and the Assistant app is
+// the client that binds to it. This GRPC service facilitates the communication with the 3P app.
+service AppActionsService {
+  // Start up a session.
+  rpc StartUpSession(stream StartSessionRequest)
+      returns (stream StartSessionResponse);
+
+  // Send request fulfillment.
+  rpc SendRequestFulfillment(Request) returns (Response);
+
+  // Request RemoteViews or TileLayout. This method is called after the
+  // AppInteractionService SDK returns a signal in the Response of
+  // RequestFulfillment. The Response.ui_update signal indicates that the app
+  // provider has requested to send UI to Assistant. RemoteViews are not
+  // directly sent in the response of this method, but passed in the gRPC
+  // 'metadata'
+  // (https://grpc.io/docs/what-is-grpc/core-concepts/#metadata).
+  // TileLayout is sent directly as protos in the UiResponse.
+  rpc RequestUi(UiRequest) returns (UiResponse);
+
+  // Request RemoteViews specifically regarding a collection view / list items.
+  // Similar to the RequestUI RPC, the response might contain RemoteViews in
+  // gRPC 'metadata'.
+  rpc RequestCollection(CollectionRequest) returns (CollectionResponse);
+
+  // Request grounded candidates from the app
+  rpc RequestGrounding(.androidx.appactions.interaction.proto.GroundingRequest)
+      returns (.androidx.appactions.interaction.proto.GroundingResponse);
+}
\ No newline at end of file
diff --git a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesApkProviderExtension.kt b/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesApkProviderExtension.kt
deleted file mode 100644
index b05ac08..0000000
--- a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesApkProviderExtension.kt
+++ /dev/null
@@ -1,48 +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.baselineprofiles.gradle.buildprovider
-
-import org.gradle.api.Project
-
-/**
- * Allows specifying settings for the Baseline Profiles Plugin.
- */
-open class BaselineProfilesApkProviderExtension {
-
-    companion object {
-
-        private const val EXTENSION_NAME = "baselineProfilesApkProvider"
-
-        internal fun registerExtension(project: Project): BaselineProfilesApkProviderExtension {
-            val ext = project
-                .extensions.findByType(BaselineProfilesApkProviderExtension::class.java)
-            if (ext != null) {
-                return ext
-            }
-            return project
-                .extensions.create(EXTENSION_NAME, BaselineProfilesApkProviderExtension::class.java)
-        }
-    }
-
-    /**
-     * Keep rule file for the special build for baseline profiles. Note that this file is
-     * automatically generated by default, unless a path is specified here. The path is relative
-     * to the module directory. The same file is used for all the variants. There should be no
-     * need to customize this file.
-     */
-    var keepRulesFile: String? = null
-}
diff --git a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPlugin.kt b/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPlugin.kt
index f8890ce..9f0df32 100644
--- a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPlugin.kt
+++ b/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPlugin.kt
@@ -40,85 +40,30 @@
 
     private fun configureWithAndroidPlugin(project: Project) {
 
-        // Prepares extensions used by the plugin
-        val baselineProfilesExtension =
-            BaselineProfilesApkProviderExtension.registerExtension(project)
-
-        val androidComponent = project.extensions.getByType(
-            ApplicationAndroidComponentsExtension::class.java
-        )
-
         // 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.
-        // The map created here maps the non obfuscated build types newly created to the release
-        // ones.
-        val extendedTypeToOriginalTypeMapping = mutableMapOf<String, String>()
-        androidComponent.finalizeDsl { applicationExtension ->
+        project
+            .extensions
+            .getByType(ApplicationAndroidComponentsExtension::class.java)
+            .finalizeDsl { applicationExtension ->
 
-            val debugBuildType = applicationExtension.buildTypes.getByName("debug")
-            createNonObfuscatedBuildTypes(
-                project = project,
-                extension = applicationExtension,
-                extendedBuildTypeToOriginalBuildTypeMapping = extendedTypeToOriginalTypeMapping,
-                filterBlock = { !it.isDebuggable },
-                configureBlock = {
-                    isJniDebuggable = false
-                    isDebuggable = false
-                    isMinifyEnabled = true
-                    isShrinkResources = true
-                    isProfileable = true
-                    signingConfig = debugBuildType.signingConfig
-                    enableAndroidTestCoverage = false
-                    enableUnitTestCoverage = false
-
-                    // The keep rule file is added later in the variants callback so that we can
-                    // generate it on-the-fly in the intermediates folder, if it wasn't specified in
-                    // the config.
-                }
-            )
-        }
-
-        // Creates a task to generate the keep rule file
-        val genKeepRuleTaskProvider = project
-            .tasks
-            .register(
-                "generateBaselineProfilesKeepRules",
-                GenerateKeepRulesForBaselineProfilesTask::class.java
-            ) {
-                it.keepRuleFile.set(
-                    project.layout.buildDirectory.file(
-                        "intermediates/baselineprofiles/baseline-profile-keep-rules.pro"
-                    )
+                val debugBuildType = applicationExtension.buildTypes.getByName("debug")
+                createNonObfuscatedBuildTypes(
+                    project = project,
+                    extension = applicationExtension,
+                    extendedBuildTypeToOriginalBuildTypeMapping = mutableMapOf(),
+                    filterBlock = { !it.isDebuggable },
+                    configureBlock = {
+                        isJniDebuggable = false
+                        isDebuggable = false
+                        isMinifyEnabled = false
+                        isShrinkResources = false
+                        isProfileable = true
+                        signingConfig = debugBuildType.signingConfig
+                        enableAndroidTestCoverage = false
+                        enableUnitTestCoverage = false
+                    }
                 )
             }
-
-        val keepRuleFileProvider = genKeepRuleTaskProvider.flatMap { it.keepRuleFile }
-
-        // Sets the keep rule file for the baseline profile variants
-        androidComponent.apply {
-
-            onVariants {
-
-                // We can skip the build types that were NOT created by this plugin.
-                if (it.buildType !in extendedTypeToOriginalTypeMapping.keys) {
-                    return@onVariants
-                }
-
-                // If the keep rule file was manually specified, then use that one
-                if (baselineProfilesExtension.keepRulesFile != null) {
-                    it.proguardFiles.add(
-                        project
-                            .layout
-                            .projectDirectory
-                            .file(baselineProfilesExtension.keepRulesFile!!)
-                    )
-                    return@onVariants
-                }
-
-                // Otherwise the keep rule file is generated and added to the list of keep rule
-                // files for the variant.
-                it.proguardFiles.add(keepRuleFileProvider)
-            }
-        }
     }
 }
diff --git a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/GenerateKeepRulesForBaselineProfilesTask.kt b/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/GenerateKeepRulesForBaselineProfilesTask.kt
deleted file mode 100644
index f49e9d0..0000000
--- a/benchmark/baseline-profiles-gradle-plugin/src/main/kotlin/androidx/baselineprofiles/gradle/buildprovider/GenerateKeepRulesForBaselineProfilesTask.kt
+++ /dev/null
@@ -1,54 +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.baselineprofiles.gradle.buildprovider
-
-import org.gradle.api.DefaultTask
-import org.gradle.api.file.RegularFileProperty
-import org.gradle.api.tasks.CacheableTask
-import org.gradle.api.tasks.OutputFile
-import org.gradle.api.tasks.TaskAction
-
-/**
- * This task generates a fixed keep rule file that simply disables obfuscation.
- * Applying this configuration to the baseline profiles build type ensures that we can produce
- * a minified, non obfuscated release build.
- */
-@CacheableTask
-abstract class GenerateKeepRulesForBaselineProfilesTask : DefaultTask() {
-
-    companion object {
-        private val KEEP_RULES = """
-            # Autogenerated for baseline profiles. Changes to this file will be overwritten.
-            -dontobfuscate
-
-        """.trimIndent()
-    }
-
-    @get:OutputFile
-    abstract val keepRuleFile: RegularFileProperty
-
-    init {
-        group = "Baseline Profiles"
-        description = "Generates the keep rules for the special baseline profiles rule."
-    }
-
-    @TaskAction
-    fun exec() {
-        keepRuleFile.get().asFile.writeText(KEEP_RULES)
-        logger.info("Generated keep rule file for baseline profiles build in ${keepRuleFile.get()}")
-    }
-}
diff --git a/benchmark/baseline-profiles-gradle-plugin/src/test/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPluginTest.kt b/benchmark/baseline-profiles-gradle-plugin/src/test/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPluginTest.kt
index a215b11..4c75385 100644
--- a/benchmark/baseline-profiles-gradle-plugin/src/test/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPluginTest.kt
+++ b/benchmark/baseline-profiles-gradle-plugin/src/test/kotlin/androidx/baselineprofiles/gradle/buildprovider/BaselineProfilesBuildProviderPluginTest.kt
@@ -18,7 +18,6 @@
 
 import androidx.testutils.gradle.ProjectSetupRule
 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
@@ -53,7 +52,7 @@
                     namespace 'com.example.namespace'
                 }
                 tasks.register("printNonObfuscatedReleaseBuildType") {
-                    android.buildTypes.nonObfuscatedRelease.properties.each {k,v->println(k+"="+v)}
+                    println(android.buildTypes.nonObfuscatedRelease)
                 }
             """.trimIndent(),
             suffix = ""
@@ -63,44 +62,9 @@
             .withArguments("printNonObfuscatedReleaseBuildType", "--stacktrace")
             .build()
             .output
-            .lines()
 
-        assertThat(buildTypeProperties).contains("shrinkResources=true")
-        assertThat(buildTypeProperties).contains("minifyEnabled=true")
+        assertThat(buildTypeProperties).contains("minifyEnabled=false")
         assertThat(buildTypeProperties).contains("testCoverageEnabled=false")
         assertThat(buildTypeProperties).contains("debuggable=false")
     }
-
-    @Test
-    fun generateBaselineProfilesKeepRuleFile() {
-        projectSetup.writeDefaultBuildGradle(
-            prefix = """
-                plugins {
-                    id("com.android.application")
-                    id("androidx.baselineprofiles.buildprovider")
-                }
-                android {
-                    namespace 'com.example.namespace'
-                }
-            """.trimIndent(),
-            suffix = ""
-        )
-
-        val outputLines = gradleRunner
-            .withArguments("generateBaselineProfilesKeepRules", "--stacktrace", "--info")
-            .build()
-            .output
-            .lines()
-
-        val find = "Generated keep rule file for baseline profiles build in"
-        val proguardFilePath = outputLines
-            .first { it.startsWith(find) }
-            .split(find)[1]
-            .trim()
-        val proguardContent = File(proguardFilePath)
-            .readText()
-            .lines()
-            .filter { !it.startsWith("#") && it.isNotBlank() }
-        assertThat(proguardContent).containsExactly("-dontobfuscate")
-    }
 }
diff --git a/benchmark/integration-tests/baselineprofiles-consumer/src/main/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofiles-consumer/src/main/expected-baseline-prof.txt
index 205f8c9..a23b650 100644
--- a/benchmark/integration-tests/baselineprofiles-consumer/src/main/expected-baseline-prof.txt
+++ b/benchmark/integration-tests/baselineprofiles-consumer/src/main/expected-baseline-prof.txt
@@ -1,84 +1,134 @@
-HSPLandroidx/appcompat/widget/TintTypedArray;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
-HSPLandroidx/appcompat/widget/TintTypedArray;->measure(Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
-HSPLandroidx/appcompat/widget/TintTypedArray;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;II)V
 HSPLandroidx/benchmark/integration/baselineprofiles/consumer/EmptyActivity;-><init>()V
 HSPLandroidx/benchmark/integration/baselineprofiles/consumer/EmptyActivity;->onCreate(Landroid/os/Bundle;)V
-HSPLandroidx/collection/ArrayMap$1;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/collection/ArrayMap$1;)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addSingleError(Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubject(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubjectInVariables(Landroidx/constraintlayout/solver/LinearSystem;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowEquals(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->ensurePositiveConstant()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->getKey()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->hasKeyVariable()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isEmpty()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isNew(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/LinearSystem;)Z
 HSPLandroidx/constraintlayout/solver/ArrayRow;->pivot(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->reset()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/SolverVariable;Z)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/SolverVariable;Z)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromSystem(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/Cache;-><init>()V
+HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;-><init>()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable$enumunboxing$(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addConstraint(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addRow(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addSingleError(Landroidx/constraintlayout/solver/ArrayRow;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->computeValues()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(ILjava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createObjectVariable(Ljava/lang/Object;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createRow()Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createSlackVariable()Landroidx/constraintlayout/solver/SolverVariable;
-HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->enforceBFS(Landroidx/constraintlayout/solver/LinearSystem$Row;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getCache()Landroidx/constraintlayout/solver/Cache;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getMetrics()Landroidx/constraintlayout/solver/Metrics;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Ljava/lang/Object;)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->increaseTableSize()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimize()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimizeGoal(Landroidx/constraintlayout/solver/LinearSystem$Row;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/LinearSystem$Row;Z)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->releaseRows()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->reset()V
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>()V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>(I)V
 HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->acquire()Ljava/lang/Object;
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Ljava/lang/Object;)Z
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->releaseAll([Ljava/lang/Object;I)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;Landroidx/constraintlayout/solver/PriorityGoalRow;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;F)Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->init(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->isNegative()Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->reset()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addError(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate([Z)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->clear()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate(Landroidx/constraintlayout/solver/LinearSystem;[Z)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->removeGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;->ordinal(I)I
-HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->addToRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->increaseErrorId()V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->removeFromRow(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->reset()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setFinalValue(Landroidx/constraintlayout/solver/LinearSystem;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setType(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->updateReferencesWithNewDefinition(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->add(Landroidx/constraintlayout/solver/SolverVariable;FZ)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addToHashMap(Landroidx/constraintlayout/solver/SolverVariable;I)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->clear()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->divideByAmount(F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->findEmptySlot()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->get(Landroidx/constraintlayout/solver/SolverVariable;)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getCurrentSize()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariableValue(I)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->indexOf(Landroidx/constraintlayout/solver/SolverVariable;)I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->insertVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->invert()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->put(Landroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->remove(Landroidx/constraintlayout/solver/SolverVariable;Z)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->removeFromHashMap(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->use(Landroidx/constraintlayout/solver/ArrayRow;Z)F
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(ILjava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;II)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIZ)Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getMargin()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getSolverVariable()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getTarget()Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->isConnected()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$1;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addAnchors()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addFirst()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints$enumunboxing$(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;ZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->createObjectVariables(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getAnchor(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour$enumunboxing$(I)I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getBaselineDistance()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getCompanionWidget()Ljava/lang/Object;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour(I)Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHorizontalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinWidth()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getParent()Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVerticalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVisibility()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getWidth()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getX()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getY()I
@@ -87,86 +137,165 @@
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInHorizontalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInVerticalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setBaselineDistance(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setCompanionWidget(Ljava/lang/Object;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setDimensionRatio(Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setFrame(IIII)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHasBaseline(Z)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHeight(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour$enumunboxing$(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour$enumunboxing$(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setInBarrier(IZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVisibility(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setX(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setY(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->updateFromSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;-><init>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getMeasurer()Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getOptimizationLevel()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isHeightMeasuredTooSmall()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isWidthMeasuredTooSmall()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->layout()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->measure(IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->optimizeFor(I)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetChains()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setOptimizationLevel(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setRtl(Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateChildrenFromSolver(Landroidx/constraintlayout/solver/LinearSystem;[Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateHierarchy()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->checkMatchParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->enabled(II)Z
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->add(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->removeAllChildren()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measure(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measureChildren(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Ljava/lang/String;II)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solverMeasure(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->updateHierarchy(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$1;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->resolveLayoutDirection(I)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->validate()V
-HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->captureLayoutInfos(IIIIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->didMeasures()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->measure(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->access$000(Landroidx/constraintlayout/widget/ConstraintLayout;)Ljava/util/ArrayList;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->applyConstraintsFromLayoutParams(ZLandroid/view/View;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;Landroid/util/SparseArray;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->dispatchDraw(Landroid/graphics/Canvas;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getPaddingWidth()I
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getViewWidget(Landroid/view/View;)Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->init(Landroid/util/AttributeSet;II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->isRtl()Z
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->markHierarchyDirty()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onLayout(ZIIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onMeasure(II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onViewAdded(Landroid/view/View;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->requestLayout()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveMeasuredDimension(IIIIZZ)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;III)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setChildrenConstraints()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setSelfDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->updateHierarchy()Z
 HSPLandroidx/constraintlayout/widget/R$styleable;-><clinit>()V
 HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->checkCompatWrapper(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
-HSPLandroidx/core/util/ObjectsCompat;-><clinit>()V
-Landroidx/appcompat/widget/TintTypedArray;
 Landroidx/benchmark/integration/baselineprofiles/consumer/EmptyActivity;
-Landroidx/collection/ArrayMap$1;
 Landroidx/constraintlayout/solver/ArrayLinkedVariables;
 Landroidx/constraintlayout/solver/ArrayRow$ArrayRowVariables;
 Landroidx/constraintlayout/solver/ArrayRow;
+Landroidx/constraintlayout/solver/Cache;
+Landroidx/constraintlayout/solver/LinearSystem$Row;
 Landroidx/constraintlayout/solver/LinearSystem$ValuesRow;
 Landroidx/constraintlayout/solver/LinearSystem;
+Landroidx/constraintlayout/solver/Pools$Pool;
 Landroidx/constraintlayout/solver/Pools$SimplePool;
 Landroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;
 Landroidx/constraintlayout/solver/PriorityGoalRow;
-Landroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;
+Landroidx/constraintlayout/solver/SolverVariable$Type;
 Landroidx/constraintlayout/solver/SolverVariable;
 Landroidx/constraintlayout/solver/SolverVariableValues;
 Landroidx/constraintlayout/solver/widgets/Barrier;
 Landroidx/constraintlayout/solver/widgets/ChainHead;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$1;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;
 Landroidx/constraintlayout/solver/widgets/Guideline;
 Landroidx/constraintlayout/solver/widgets/Helper;
 Landroidx/constraintlayout/solver/widgets/HelperWidget;
+Landroidx/constraintlayout/solver/widgets/Optimizer;
+Landroidx/constraintlayout/solver/widgets/VirtualLayout;
+Landroidx/constraintlayout/solver/widgets/WidgetContainer;
 Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;
 Landroidx/constraintlayout/solver/widgets/analyzer/Dependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;
 Landroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;
 Landroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;
+Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;
 Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;
 Landroidx/constraintlayout/widget/ConstraintHelper;
+Landroidx/constraintlayout/widget/ConstraintLayout$1;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;
 Landroidx/constraintlayout/widget/ConstraintLayout;
 Landroidx/constraintlayout/widget/Guideline;
+Landroidx/constraintlayout/widget/Placeholder;
 Landroidx/constraintlayout/widget/R$styleable;
-Landroidx/core/app/CoreComponentFactory;
-Landroidx/core/util/ObjectsCompat;
\ No newline at end of file
+Landroidx/constraintlayout/widget/VirtualLayout;
+Landroidx/core/app/CoreComponentFactory$CompatWrapped;
+Landroidx/core/app/CoreComponentFactory;
\ No newline at end of file
diff --git a/benchmark/integration-tests/baselineprofiles-flavors-consumer/src/main/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofiles-flavors-consumer/src/main/expected-baseline-prof.txt
index 49756602..75735f3 100644
--- a/benchmark/integration-tests/baselineprofiles-flavors-consumer/src/main/expected-baseline-prof.txt
+++ b/benchmark/integration-tests/baselineprofiles-flavors-consumer/src/main/expected-baseline-prof.txt
@@ -1,84 +1,134 @@
-HSPLandroidx/appcompat/widget/TintTypedArray;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
-HSPLandroidx/appcompat/widget/TintTypedArray;->measure(Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
-HSPLandroidx/appcompat/widget/TintTypedArray;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;II)V
 HSPLandroidx/benchmark/integration/baselineprofiles/flavors/consumer/EmptyActivity;-><init>()V
 HSPLandroidx/benchmark/integration/baselineprofiles/flavors/consumer/EmptyActivity;->onCreate(Landroid/os/Bundle;)V
-HSPLandroidx/collection/ArrayMap$1;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/collection/ArrayMap$1;)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addSingleError(Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubject(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubjectInVariables(Landroidx/constraintlayout/solver/LinearSystem;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowEquals(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->ensurePositiveConstant()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->getKey()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->hasKeyVariable()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isEmpty()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isNew(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/LinearSystem;)Z
 HSPLandroidx/constraintlayout/solver/ArrayRow;->pivot(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->reset()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/SolverVariable;Z)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/SolverVariable;Z)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromSystem(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/Cache;-><init>()V
+HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;-><init>()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable$enumunboxing$(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addConstraint(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addRow(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addSingleError(Landroidx/constraintlayout/solver/ArrayRow;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->computeValues()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(ILjava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createObjectVariable(Ljava/lang/Object;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createRow()Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createSlackVariable()Landroidx/constraintlayout/solver/SolverVariable;
-HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->enforceBFS(Landroidx/constraintlayout/solver/LinearSystem$Row;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getCache()Landroidx/constraintlayout/solver/Cache;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getMetrics()Landroidx/constraintlayout/solver/Metrics;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Ljava/lang/Object;)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->increaseTableSize()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimize()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimizeGoal(Landroidx/constraintlayout/solver/LinearSystem$Row;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/LinearSystem$Row;Z)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->releaseRows()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->reset()V
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>()V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>(I)V
 HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->acquire()Ljava/lang/Object;
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Ljava/lang/Object;)Z
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->releaseAll([Ljava/lang/Object;I)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;Landroidx/constraintlayout/solver/PriorityGoalRow;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;F)Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->init(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->isNegative()Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->reset()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addError(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate([Z)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->clear()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate(Landroidx/constraintlayout/solver/LinearSystem;[Z)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->removeGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;->ordinal(I)I
-HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->addToRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->increaseErrorId()V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->removeFromRow(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->reset()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setFinalValue(Landroidx/constraintlayout/solver/LinearSystem;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setType(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->updateReferencesWithNewDefinition(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->add(Landroidx/constraintlayout/solver/SolverVariable;FZ)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addToHashMap(Landroidx/constraintlayout/solver/SolverVariable;I)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->clear()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->divideByAmount(F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->findEmptySlot()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->get(Landroidx/constraintlayout/solver/SolverVariable;)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getCurrentSize()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariableValue(I)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->indexOf(Landroidx/constraintlayout/solver/SolverVariable;)I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->insertVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->invert()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->put(Landroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->remove(Landroidx/constraintlayout/solver/SolverVariable;Z)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->removeFromHashMap(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->use(Landroidx/constraintlayout/solver/ArrayRow;Z)F
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(ILjava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;II)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIZ)Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getMargin()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getSolverVariable()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getTarget()Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->isConnected()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$1;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addAnchors()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addFirst()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints$enumunboxing$(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;ZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->createObjectVariables(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getAnchor(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour$enumunboxing$(I)I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getBaselineDistance()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getCompanionWidget()Ljava/lang/Object;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour(I)Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHorizontalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinWidth()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getParent()Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVerticalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVisibility()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getWidth()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getX()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getY()I
@@ -87,86 +137,165 @@
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInHorizontalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInVerticalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setBaselineDistance(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setCompanionWidget(Ljava/lang/Object;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setDimensionRatio(Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setFrame(IIII)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHasBaseline(Z)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHeight(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour$enumunboxing$(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour$enumunboxing$(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setInBarrier(IZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVisibility(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setX(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setY(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->updateFromSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;-><init>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getMeasurer()Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getOptimizationLevel()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isHeightMeasuredTooSmall()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isWidthMeasuredTooSmall()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->layout()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->measure(IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->optimizeFor(I)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetChains()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setOptimizationLevel(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setRtl(Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateChildrenFromSolver(Landroidx/constraintlayout/solver/LinearSystem;[Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateHierarchy()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->checkMatchParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->enabled(II)Z
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->add(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->removeAllChildren()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measure(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measureChildren(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Ljava/lang/String;II)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solverMeasure(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->updateHierarchy(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$1;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->resolveLayoutDirection(I)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->validate()V
-HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->captureLayoutInfos(IIIIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->didMeasures()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->measure(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->access$000(Landroidx/constraintlayout/widget/ConstraintLayout;)Ljava/util/ArrayList;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->applyConstraintsFromLayoutParams(ZLandroid/view/View;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;Landroid/util/SparseArray;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->dispatchDraw(Landroid/graphics/Canvas;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getPaddingWidth()I
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getViewWidget(Landroid/view/View;)Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->init(Landroid/util/AttributeSet;II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->isRtl()Z
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->markHierarchyDirty()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onLayout(ZIIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onMeasure(II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onViewAdded(Landroid/view/View;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->requestLayout()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveMeasuredDimension(IIIIZZ)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;III)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setChildrenConstraints()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setSelfDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->updateHierarchy()Z
 HSPLandroidx/constraintlayout/widget/R$styleable;-><clinit>()V
 HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->checkCompatWrapper(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
-HSPLandroidx/core/util/ObjectsCompat;-><clinit>()V
-Landroidx/appcompat/widget/TintTypedArray;
 Landroidx/benchmark/integration/baselineprofiles/flavors/consumer/EmptyActivity;
-Landroidx/collection/ArrayMap$1;
 Landroidx/constraintlayout/solver/ArrayLinkedVariables;
 Landroidx/constraintlayout/solver/ArrayRow$ArrayRowVariables;
 Landroidx/constraintlayout/solver/ArrayRow;
+Landroidx/constraintlayout/solver/Cache;
+Landroidx/constraintlayout/solver/LinearSystem$Row;
 Landroidx/constraintlayout/solver/LinearSystem$ValuesRow;
 Landroidx/constraintlayout/solver/LinearSystem;
+Landroidx/constraintlayout/solver/Pools$Pool;
 Landroidx/constraintlayout/solver/Pools$SimplePool;
 Landroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;
 Landroidx/constraintlayout/solver/PriorityGoalRow;
-Landroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;
+Landroidx/constraintlayout/solver/SolverVariable$Type;
 Landroidx/constraintlayout/solver/SolverVariable;
 Landroidx/constraintlayout/solver/SolverVariableValues;
 Landroidx/constraintlayout/solver/widgets/Barrier;
 Landroidx/constraintlayout/solver/widgets/ChainHead;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$1;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;
 Landroidx/constraintlayout/solver/widgets/Guideline;
 Landroidx/constraintlayout/solver/widgets/Helper;
 Landroidx/constraintlayout/solver/widgets/HelperWidget;
+Landroidx/constraintlayout/solver/widgets/Optimizer;
+Landroidx/constraintlayout/solver/widgets/VirtualLayout;
+Landroidx/constraintlayout/solver/widgets/WidgetContainer;
 Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;
 Landroidx/constraintlayout/solver/widgets/analyzer/Dependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;
 Landroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;
 Landroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;
+Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;
 Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;
 Landroidx/constraintlayout/widget/ConstraintHelper;
+Landroidx/constraintlayout/widget/ConstraintLayout$1;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;
 Landroidx/constraintlayout/widget/ConstraintLayout;
 Landroidx/constraintlayout/widget/Guideline;
+Landroidx/constraintlayout/widget/Placeholder;
 Landroidx/constraintlayout/widget/R$styleable;
-Landroidx/core/app/CoreComponentFactory;
-Landroidx/core/util/ObjectsCompat;
\ No newline at end of file
+Landroidx/constraintlayout/widget/VirtualLayout;
+Landroidx/core/app/CoreComponentFactory$CompatWrapped;
+Landroidx/core/app/CoreComponentFactory;
\ No newline at end of file
diff --git a/benchmark/integration-tests/baselineprofiles-library-consumer/src/main/expected-baseline-prof.txt b/benchmark/integration-tests/baselineprofiles-library-consumer/src/main/expected-baseline-prof.txt
index 29e1de4..2dde749 100644
--- a/benchmark/integration-tests/baselineprofiles-library-consumer/src/main/expected-baseline-prof.txt
+++ b/benchmark/integration-tests/baselineprofiles-library-consumer/src/main/expected-baseline-prof.txt
@@ -1,84 +1,134 @@
-HSPLandroidx/appcompat/widget/TintTypedArray;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
-HSPLandroidx/appcompat/widget/TintTypedArray;->measure(Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
-HSPLandroidx/appcompat/widget/TintTypedArray;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;II)V
 HSPLandroidx/benchmark/integration/baselineprofiles/library/buildprovider/EmptyActivity;-><init>()V
 HSPLandroidx/benchmark/integration/baselineprofiles/library/buildprovider/EmptyActivity;->onCreate(Landroid/os/Bundle;)V
-HSPLandroidx/collection/ArrayMap$1;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/ArrayLinkedVariables;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/collection/ArrayMap$1;)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addError(Landroidx/constraintlayout/solver/LinearSystem;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->addSingleError(Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubject(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->chooseSubjectInVariables(Landroidx/constraintlayout/solver/LinearSystem;)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowEquals(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->createRowLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;I)Landroidx/constraintlayout/solver/ArrayRow;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->ensurePositiveConstant()V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->getKey()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/ArrayRow;->hasKeyVariable()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isEmpty()Z
+HSPLandroidx/constraintlayout/solver/ArrayRow;->isNew(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/LinearSystem;)Z
 HSPLandroidx/constraintlayout/solver/ArrayRow;->pivot(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->reset()V
-HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/SolverVariable;Z)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromFinalVariable(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/SolverVariable;Z)V
 HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/ArrayRow;->updateFromSystem(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/Cache;-><init>()V
+HSPLandroidx/constraintlayout/solver/LinearSystem$ValuesRow;-><init>(Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;-><init>()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable$enumunboxing$(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->acquireSolverVariable(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addCentering(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IFLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addConstraint(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;I)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addEquality(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addGreaterThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addLowerThan(Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;II)V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->addRow(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->addSingleError(Landroidx/constraintlayout/solver/ArrayRow;II)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->computeValues()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->createErrorVariable(ILjava/lang/String;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createObjectVariable(Ljava/lang/Object;)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createRow()Landroidx/constraintlayout/solver/ArrayRow;
 HSPLandroidx/constraintlayout/solver/LinearSystem;->createSlackVariable()Landroidx/constraintlayout/solver/SolverVariable;
-HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->enforceBFS(Landroidx/constraintlayout/solver/LinearSystem$Row;)I
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getCache()Landroidx/constraintlayout/solver/Cache;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getMetrics()Landroidx/constraintlayout/solver/Metrics;
+HSPLandroidx/constraintlayout/solver/LinearSystem;->getObjectVariableValue(Ljava/lang/Object;)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->increaseTableSize()V
-HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimize()V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->minimizeGoal(Landroidx/constraintlayout/solver/LinearSystem$Row;)V
+HSPLandroidx/constraintlayout/solver/LinearSystem;->optimize(Landroidx/constraintlayout/solver/LinearSystem$Row;Z)I
 HSPLandroidx/constraintlayout/solver/LinearSystem;->releaseRows()V
 HSPLandroidx/constraintlayout/solver/LinearSystem;->reset()V
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>()V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;-><init>(I)V
 HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->acquire()Ljava/lang/Object;
-HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->release(Ljava/lang/Object;)Z
+HSPLandroidx/constraintlayout/solver/Pools$SimplePool;->releaseAll([Ljava/lang/Object;I)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;-><init>(Landroidx/constraintlayout/solver/PriorityGoalRow;Landroidx/constraintlayout/solver/PriorityGoalRow;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;F)Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->init(Landroidx/constraintlayout/solver/SolverVariable;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->isNegative()Z
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;->reset()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;-><init>(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addError(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->addToGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
-HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate([Z)Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->clear()V
+HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->getPivotCandidate(Landroidx/constraintlayout/solver/LinearSystem;[Z)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->removeGoal(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/PriorityGoalRow;->updateFromRow(Landroidx/constraintlayout/solver/ArrayRow;Z)V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;->ordinal(I)I
-HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;-><init>(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->addToRow(Landroidx/constraintlayout/solver/ArrayRow;)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->increaseErrorId()V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->removeFromRow(Landroidx/constraintlayout/solver/ArrayRow;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->reset()V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setFinalValue(Landroidx/constraintlayout/solver/LinearSystem;F)V
+HSPLandroidx/constraintlayout/solver/SolverVariable;->setType(Landroidx/constraintlayout/solver/SolverVariable$Type;Ljava/lang/String;)V
 HSPLandroidx/constraintlayout/solver/SolverVariable;->updateReferencesWithNewDefinition(Landroidx/constraintlayout/solver/ArrayRow;)V
-HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;-><init>(Landroidx/constraintlayout/solver/ArrayRow;Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->add(Landroidx/constraintlayout/solver/SolverVariable;FZ)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addToHashMap(Landroidx/constraintlayout/solver/SolverVariable;I)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->addVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->clear()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->divideByAmount(F)V
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->findEmptySlot()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->get(Landroidx/constraintlayout/solver/SolverVariable;)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getCurrentSize()I
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariable(I)Landroidx/constraintlayout/solver/SolverVariable;
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->getVariableValue(I)F
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->indexOf(Landroidx/constraintlayout/solver/SolverVariable;)I
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->insertVariable(ILandroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->invert()V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->put(Landroidx/constraintlayout/solver/SolverVariable;F)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->remove(Landroidx/constraintlayout/solver/SolverVariable;Z)F
+HSPLandroidx/constraintlayout/solver/SolverVariableValues;->removeFromHashMap(Landroidx/constraintlayout/solver/SolverVariable;)V
 HSPLandroidx/constraintlayout/solver/SolverVariableValues;->use(Landroidx/constraintlayout/solver/ArrayRow;Z)F
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><clinit>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(ILjava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;II)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->connect(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIZ)Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getMargin()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getSolverVariable()Landroidx/constraintlayout/solver/SolverVariable;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->getTarget()Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->isConnected()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;->resetSolverVariable(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$1;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;-><init>(Ljava/lang/String;I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;->values()[Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addAnchors()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addFirst()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->addToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints$enumunboxing$(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;IZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->applyConstraints(Landroidx/constraintlayout/solver/LinearSystem;ZZZZLandroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/SolverVariable;Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;ZLandroidx/constraintlayout/solver/widgets/ConstraintAnchor;Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;IIIIFZZZZIIIIFZ)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->createObjectVariables(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getAnchor(Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;)Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour$enumunboxing$(I)I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getBaselineDistance()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getCompanionWidget()Ljava/lang/Object;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getDimensionBehaviour(I)Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getHorizontalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinHeight()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getMinWidth()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getParent()Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVerticalDimensionBehaviour()Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getVisibility()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getWidth()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getX()I
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->getY()I
@@ -87,86 +137,165 @@
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInHorizontalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->isInVerticalChain()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->reset()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setBaselineDistance(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setCompanionWidget(Ljava/lang/Object;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setDimensionRatio(Ljava/lang/String;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setFrame(IIII)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHasBaseline(Z)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHeight(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour$enumunboxing$(I)V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour$enumunboxing$(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setHorizontalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setInBarrier(IZ)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMaxWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinHeight(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setMinWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalBiasPercent(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalChainStyle(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalMatchStyle(IIIF)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVerticalWeight(F)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setVisibility(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setWidth(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setX(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->setY(I)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidget;->updateFromSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;-><init>()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->addChildrenToSolver(Landroidx/constraintlayout/solver/LinearSystem;)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getMeasurer()Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->getOptimizationLevel()I
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isHeightMeasuredTooSmall()Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->isWidthMeasuredTooSmall()Z
 HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->layout()V
-HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetSolverVariables(Landroidx/collection/ArrayMap$1;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->measure(IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->optimizeFor(I)Z
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->resetChains()V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setOptimizationLevel(I)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->setRtl(Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateChildrenFromSolver(Landroidx/constraintlayout/solver/LinearSystem;[Z)V
+HSPLandroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;->updateHierarchy()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->checkMatchParent(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Landroidx/constraintlayout/solver/LinearSystem;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/Optimizer;->enabled(II)Z
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->add(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->removeAllChildren()V
+HSPLandroidx/constraintlayout/solver/widgets/WidgetContainer;->resetSolverVariables(Landroidx/constraintlayout/solver/Cache;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;-><init>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measure(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Z)Z
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->measureChildren(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solveLinearSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;Ljava/lang/String;II)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->solverMeasure(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIIIIIIII)J
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;->updateHierarchy(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateGraph()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->invalidateMeasures()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;->setMeasurer(Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;-><init>(Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><clinit>()V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><clinit>()V
+HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;-><init>(Ljava/lang/String;I)V
 HSPLandroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;-><init>(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$1;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;-><clinit>()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->resolveLayoutDirection(I)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;->validate()V
-HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;-><init>(Landroidx/constraintlayout/widget/ConstraintLayout;Landroidx/constraintlayout/widget/ConstraintLayout;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->captureLayoutInfos(IIIIII)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->didMeasures()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout$Measurer;->measure(Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;-><init>(Landroid/content/Context;Landroid/util/AttributeSet;)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->access$000(Landroidx/constraintlayout/widget/ConstraintLayout;)Ljava/util/ArrayList;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->addView(Landroid/view/View;ILandroid/view/ViewGroup$LayoutParams;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->applyConstraintsFromLayoutParams(ZLandroid/view/View;Landroidx/constraintlayout/solver/widgets/ConstraintWidget;Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;Landroid/util/SparseArray;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->checkLayoutParams(Landroid/view/ViewGroup$LayoutParams;)Z
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->dispatchDraw(Landroid/graphics/Canvas;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroid/view/ViewGroup$LayoutParams;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->generateLayoutParams(Landroid/util/AttributeSet;)Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getPaddingWidth()I
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->getViewWidget(Landroid/view/View;)Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->init(Landroid/util/AttributeSet;II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->isRtl()Z
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->markHierarchyDirty()V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onLayout(ZIIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onMeasure(II)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->onViewAdded(Landroid/view/View;)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->requestLayout()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveMeasuredDimension(IIIIZZ)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->resolveSystem(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;III)V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setChildrenConstraints()V
+HSPLandroidx/constraintlayout/widget/ConstraintLayout;->setSelfDimensionBehaviour(Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;IIII)V
 HSPLandroidx/constraintlayout/widget/ConstraintLayout;->updateHierarchy()Z
 HSPLandroidx/constraintlayout/widget/R$styleable;-><clinit>()V
 HSPLandroidx/core/app/CoreComponentFactory;-><init>()V
+HSPLandroidx/core/app/CoreComponentFactory;->checkCompatWrapper(Ljava/lang/Object;)Ljava/lang/Object;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateActivity(Ljava/lang/ClassLoader;Ljava/lang/String;Landroid/content/Intent;)Landroid/app/Activity;
 HSPLandroidx/core/app/CoreComponentFactory;->instantiateApplication(Ljava/lang/ClassLoader;Ljava/lang/String;)Landroid/app/Application;
-HSPLandroidx/core/util/ObjectsCompat;-><clinit>()V
-Landroidx/appcompat/widget/TintTypedArray;
 Landroidx/benchmark/integration/baselineprofiles/library/buildprovider/EmptyActivity;
-Landroidx/collection/ArrayMap$1;
 Landroidx/constraintlayout/solver/ArrayLinkedVariables;
 Landroidx/constraintlayout/solver/ArrayRow$ArrayRowVariables;
 Landroidx/constraintlayout/solver/ArrayRow;
+Landroidx/constraintlayout/solver/Cache;
+Landroidx/constraintlayout/solver/LinearSystem$Row;
 Landroidx/constraintlayout/solver/LinearSystem$ValuesRow;
 Landroidx/constraintlayout/solver/LinearSystem;
+Landroidx/constraintlayout/solver/Pools$Pool;
 Landroidx/constraintlayout/solver/Pools$SimplePool;
 Landroidx/constraintlayout/solver/PriorityGoalRow$GoalVariableAccessor;
 Landroidx/constraintlayout/solver/PriorityGoalRow;
-Landroidx/constraintlayout/solver/SolverVariable$Type$EnumUnboxingSharedUtility;
+Landroidx/constraintlayout/solver/SolverVariable$Type;
 Landroidx/constraintlayout/solver/SolverVariable;
 Landroidx/constraintlayout/solver/SolverVariableValues;
 Landroidx/constraintlayout/solver/widgets/Barrier;
 Landroidx/constraintlayout/solver/widgets/ChainHead;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor$Type;
 Landroidx/constraintlayout/solver/widgets/ConstraintAnchor;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$1;
+Landroidx/constraintlayout/solver/widgets/ConstraintWidget$DimensionBehaviour;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidget;
 Landroidx/constraintlayout/solver/widgets/ConstraintWidgetContainer;
 Landroidx/constraintlayout/solver/widgets/Guideline;
 Landroidx/constraintlayout/solver/widgets/Helper;
 Landroidx/constraintlayout/solver/widgets/HelperWidget;
+Landroidx/constraintlayout/solver/widgets/Optimizer;
+Landroidx/constraintlayout/solver/widgets/VirtualLayout;
+Landroidx/constraintlayout/solver/widgets/WidgetContainer;
 Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measure;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure$Measurer;
+Landroidx/constraintlayout/solver/widgets/analyzer/BasicMeasure;
 Landroidx/constraintlayout/solver/widgets/analyzer/Dependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyGraph;
+Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode$Type;
 Landroidx/constraintlayout/solver/widgets/analyzer/DependencyNode;
 Landroidx/constraintlayout/solver/widgets/analyzer/DimensionDependency;
 Landroidx/constraintlayout/solver/widgets/analyzer/HorizontalWidgetRun;
 Landroidx/constraintlayout/solver/widgets/analyzer/VerticalWidgetRun;
+Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun$RunType;
 Landroidx/constraintlayout/solver/widgets/analyzer/WidgetRun;
 Landroidx/constraintlayout/widget/ConstraintHelper;
+Landroidx/constraintlayout/widget/ConstraintLayout$1;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams$Table;
 Landroidx/constraintlayout/widget/ConstraintLayout$LayoutParams;
 Landroidx/constraintlayout/widget/ConstraintLayout$Measurer;
 Landroidx/constraintlayout/widget/ConstraintLayout;
 Landroidx/constraintlayout/widget/Guideline;
+Landroidx/constraintlayout/widget/Placeholder;
 Landroidx/constraintlayout/widget/R$styleable;
-Landroidx/core/app/CoreComponentFactory;
-Landroidx/core/util/ObjectsCompat;
\ No newline at end of file
+Landroidx/constraintlayout/widget/VirtualLayout;
+Landroidx/core/app/CoreComponentFactory$CompatWrapped;
+Landroidx/core/app/CoreComponentFactory;
\ No newline at end of file
diff --git a/buildSrc-tests/build.gradle b/buildSrc-tests/build.gradle
index 443b0ef..5f63c3b 100644
--- a/buildSrc-tests/build.gradle
+++ b/buildSrc-tests/build.gradle
@@ -53,6 +53,7 @@
     testImplementation(libs.junit)
     testImplementation(libs.truth)
     testImplementation(project(":internal-testutils-gradle-plugin"))
+    testImplementation(project(":internal-testutils-truth"))
     testImplementation(gradleTestKit())
     testImplementation(libs.checkmark)
     testImplementation(libs.kotlinGradlePluginz)
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
index 85d9997..fd1a7c1 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/LibraryVersionsServiceTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
 import org.gradle.api.Project
 import org.gradle.kotlin.dsl.create
@@ -59,6 +60,24 @@
     }
 
     @Test
+    fun invalidToml() {
+        val service = createLibraryVersionsService(
+            """
+            [versions]
+            V1 = "1.2.3"
+            [groups]
+            G1 = { group = "g.g1", atomicGroupVersion = "versions.V1" }
+            G1 = { group = "g.g1"}
+        """.trimIndent()
+        )
+        assertThrows<Exception> {
+            service.libraryGroups["G1"]
+        }.hasMessageThat().contains(
+            "libraryversions.toml:line 5, column 1: G1 previously defined at line 4, column 1"
+        )
+    }
+
+    @Test
     fun withMultiplatformVersion() {
         val toml = """
             [versions]
@@ -369,7 +388,7 @@
             // create the service before extensions are created so that they'll use the test service
             // we've created.
             createLibraryVersionsService(
-                tomlFile = tomlFile,
+                tomlFileContents = tomlFile,
                 project = rootProject,
                 useMultiplatformGroupVersions = useKmpVersions
             )
@@ -387,7 +406,8 @@
     }
 
     private fun createLibraryVersionsService(
-        tomlFile: String,
+        tomlFileContents: String,
+        tomlFileName: String = "libraryversions.toml",
         composeCustomVersion: String? = null,
         composeCustomGroup: String? = null,
         useMultiplatformGroupVersions: Boolean = false,
@@ -396,9 +416,10 @@
         val serviceProvider = project.gradle.sharedServices.registerIfAbsent(
             "libraryVersionsService", LibraryVersionsService::class.java
         ) { spec ->
-            spec.parameters.tomlFile = project.provider {
-                tomlFile
+            spec.parameters.tomlFileContents = project.provider {
+                tomlFileContents
             }
+            spec.parameters.tomlFileName = tomlFileName
             spec.parameters.composeCustomVersion = project.provider {
                 composeCustomVersion
             }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
index 2303c2a..d6ec9a5 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXExtension.kt
@@ -46,7 +46,8 @@
     private val versionService: LibraryVersionsService
 
     init {
-        val toml = lazyReadFile("libraryversions.toml")
+        val tomlFileName = "libraryversions.toml"
+        val toml = lazyReadFile(tomlFileName)
 
         // These parameters are used when building pre-release binaries for androidxdev.
         // These parameters are only expected to be compatible with :compose:compiler:compiler .
@@ -60,7 +61,8 @@
             "libraryVersionsService",
             LibraryVersionsService::class.java
         ) { spec ->
-            spec.parameters.tomlFile = toml
+            spec.parameters.tomlFileName = tomlFileName
+            spec.parameters.tomlFileContents = toml
             spec.parameters.composeCustomVersion = composeCustomVersion
             spec.parameters.composeCustomGroup = composeCustomGroup
             spec.parameters.useMultiplatformGroupVersions = project.provider {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
index 4330757..b5b8118 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/FtlRunner.kt
@@ -69,6 +69,11 @@
     @get:Option(option = "className", description = "Fully qualified class name of a class to run")
     abstract val className: Property<String>
 
+    @get:Optional
+    @get:Input
+    @get:Option(option = "packageName", description = "Package name test classes to run")
+    abstract val packageName: Property<String>
+
     @get:Input
     abstract val device: Property<String>
 
@@ -94,36 +99,44 @@
                 "Missing gcloud, please follow go/androidx-dev#remote-build-cache to set it up"
             )
         }
+        val hasFilters = className.isPresent || packageName.isPresent
+        val filters = listOfNotNull(
+            if (className.isPresent) "class ${className.get()}" else null,
+            if (packageName.isPresent) "package ${packageName.get()}" else null,
+        ).joinToString(separator = ",")
         execOperations.exec {
             it.commandLine(
                 listOfNotNull(
-                "gcloud",
-                "--project",
-                "androidx-dev-prod",
-                "firebase",
-                "test",
-                "android",
-                "run",
-                "--type",
-                "instrumentation",
-                "--no-performance-metrics",
-                "--no-auto-google-login",
-                "--device",
-                "model=${device.get()},locale=en_US,orientation=portrait",
-                "--app",
-                appApkPath,
-                "--test",
-                testApkPath,
-                if (className.isPresent) "--test-targets" else null,
-                if (className.isPresent) "class ${className.get()}" else null,
+                    "gcloud",
+                    "--project",
+                    "androidx-dev-prod",
+                    "firebase",
+                    "test",
+                    "android",
+                    "run",
+                    "--type",
+                    "instrumentation",
+                    "--no-performance-metrics",
+                    "--no-auto-google-login",
+                    "--device",
+                    "model=${device.get()},locale=en_US,orientation=portrait",
+                    "--app",
+                    appApkPath,
+                    "--test",
+                    testApkPath,
+                    if (hasFilters) "--test-targets" else null,
+                    if (hasFilters) filters else null,
                 )
             )
         }
     }
 }
 
-private const val PIXEL2_API30_PREFIX = "ftlpixel2api30"
-private const val NEXUS4_API21_PREFIX = "ftlnexus4api21"
+private val devicesToRunOn = listOf(
+    "ftlpixel2api33" to "Pixel2.arm,version=33",
+    "ftlpixel2api30" to "Pixel2.arm,version=30",
+    "ftlnexus4api21" to "Nexus4,version=21",
+)
 
 fun Project.configureFtlRunner() {
     extensions.getByType(AndroidComponentsExtension::class.java).apply {
@@ -144,16 +157,12 @@
             if (name == null || artifacts == null) {
                 return@onVariants
             }
-            tasks.register("$PIXEL2_API30_PREFIX$name", FtlRunner::class.java) { task ->
-
-                task.device.set("Pixel2.arm,version=30")
-                task.testFolder.set(artifacts.get(SingleArtifact.APK))
-                task.testLoader.set(artifacts.getBuiltArtifactsLoader())
-            }
-            tasks.register("$NEXUS4_API21_PREFIX$name", FtlRunner::class.java) { task ->
-                task.device.set("Nexus4,version=21")
-                task.testFolder.set(artifacts.get(SingleArtifact.APK))
-                task.testLoader.set(artifacts.getBuiltArtifactsLoader())
+            devicesToRunOn.forEach { (taskPrefix, model) ->
+                tasks.register("$taskPrefix$name", FtlRunner::class.java) { task ->
+                    task.device.set(model)
+                    task.testFolder.set(artifacts.get(SingleArtifact.APK))
+                    task.testLoader.set(artifacts.getBuiltArtifactsLoader())
+                }
             }
         }
     }
@@ -162,15 +171,12 @@
 fun Project.addAppApkToFtlRunner() {
     extensions.getByType<ApplicationAndroidComponentsExtension>().apply {
         onVariants(selector().withBuildType("debug")) { appVariant ->
-            tasks.named("$PIXEL2_API30_PREFIX${appVariant.name}AndroidTest") { configTask ->
-                configTask as FtlRunner
-                configTask.appFolder.set(appVariant.artifacts.get(SingleArtifact.APK))
-                configTask.appLoader.set(appVariant.artifacts.getBuiltArtifactsLoader())
-            }
-            tasks.named("$NEXUS4_API21_PREFIX${appVariant.name}AndroidTest") { configTask ->
-                configTask as FtlRunner
-                configTask.appFolder.set(appVariant.artifacts.get(SingleArtifact.APK))
-                configTask.appLoader.set(appVariant.artifacts.getBuiltArtifactsLoader())
+            devicesToRunOn.forEach { (taskPrefix, _) ->
+                tasks.named("$taskPrefix${appVariant.name}AndroidTest") { configTask ->
+                    configTask as FtlRunner
+                    configTask.appFolder.set(appVariant.artifacts.get(SingleArtifact.APK))
+                    configTask.appLoader.set(appVariant.artifacts.getBuiltArtifactsLoader())
+                }
             }
         }
     }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
index 94bff7f..dbd9266 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LibraryVersionsService.kt
@@ -29,14 +29,22 @@
  */
 abstract class LibraryVersionsService : BuildService<LibraryVersionsService.Parameters> {
     interface Parameters : BuildServiceParameters {
-        var tomlFile: Provider<String>
+        var tomlFileName: String
+        var tomlFileContents: Provider<String>
         var composeCustomVersion: Provider<String>
         var composeCustomGroup: Provider<String>
         var useMultiplatformGroupVersions: Provider<Boolean>
     }
 
     private val parsedTomlFile: TomlParseResult by lazy {
-        Toml.parse(parameters.tomlFile.get())
+        val result = Toml.parse(parameters.tomlFileContents.get())
+        if (result.hasErrors()) {
+            val issues = result.errors().map {
+                "${parameters.tomlFileName}:${it.position()}: ${it.message}"
+            }.joinToString(separator = "\n")
+            throw Exception("${parameters.tomlFileName} file has issues.\n$issues")
+        }
+        result
     }
 
     val useMultiplatformGroupVersions
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 0161db3..5ba8cb5 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -91,6 +91,7 @@
     androidTestImplementation(project(":annotation:annotation-experimental"))
     androidTestImplementation(project(":camera:camera-lifecycle"))
     androidTestImplementation(project(":camera:camera-testing"))
+    androidTestImplementation(project(":camera:camera-video"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
     androidTestImplementation(project(":internal-testutils-truth"))
     androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
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 e5bccd9..a9e2aeb 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
@@ -17,16 +17,25 @@
 package androidx.camera.camera2.pipe.integration
 
 import android.content.Context
+import android.graphics.SurfaceTexture
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES
+import android.hardware.camera2.CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AE
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AF
 import android.hardware.camera2.CameraCharacteristics.CONTROL_MAX_REGIONS_AWB
+import android.hardware.camera2.CameraMetadata.CONTROL_AE_MODE_OFF
 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.CameraMetadata.CONTROL_AF_MODE_AUTO
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_CONTINUOUS_VIDEO
+import android.hardware.camera2.CameraMetadata.CONTROL_AF_MODE_OFF
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_EXPOSURE_COMPENSATION
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_MODE
 import android.hardware.camera2.CaptureRequest.CONTROL_AE_REGIONS
+import android.hardware.camera2.CaptureRequest.CONTROL_AF_MODE
 import android.hardware.camera2.CaptureRequest.CONTROL_AF_REGIONS
 import android.hardware.camera2.CaptureRequest.CONTROL_AWB_REGIONS
 import android.hardware.camera2.CaptureRequest.CONTROL_CAPTURE_INTENT
@@ -36,6 +45,7 @@
 import android.hardware.camera2.CaptureRequest.FLASH_MODE_TORCH
 import android.hardware.camera2.CaptureRequest.SCALER_CROP_REGION
 import android.os.Build
+import android.util.Size
 import androidx.camera.camera2.pipe.CameraGraph
 import androidx.camera.camera2.pipe.FrameInfo
 import androidx.camera.camera2.pipe.RequestMetadata
@@ -49,11 +59,15 @@
 import androidx.camera.core.FocusMeteringAction
 import androidx.camera.core.ImageAnalysis
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.Preview
 import androidx.camera.core.SurfaceOrientedMeteringPointFactory
 import androidx.camera.core.UseCase
 import androidx.camera.core.internal.CameraUseCaseAdapter
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraXUtil
+import androidx.camera.testing.SurfaceTextureProvider
+import androidx.camera.video.Recorder
+import androidx.camera.video.VideoCapture
 import androidx.concurrent.futures.await
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -188,7 +202,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta.request[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_AUTO_FLASH
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
             },
             TIMEOUT
         )
@@ -203,7 +217,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON)
             },
             TIMEOUT
         )
@@ -218,7 +232,7 @@
 
         waitForResult(captureCount = 60).verify(
             { requestMeta: RequestMetadata, _ ->
-                requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_ALWAYS_FLASH
+                requestMeta.isAeMode(CONTROL_AE_MODE_ON_ALWAYS_FLASH)
             },
             TIMEOUT
         )
@@ -234,7 +248,7 @@
         waitForResult(captureCount = 30).verify(
             { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
                 frameInfo.requestMetadata[FLASH_MODE] == FLASH_MODE_TORCH &&
-                    requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON
+                    requestMeta.isAeMode(CONTROL_AE_MODE_ON)
             },
             TIMEOUT
         )
@@ -250,7 +264,7 @@
         waitForResult(captureCount = 30).verify(
             { requestMeta: RequestMetadata, frameInfo: FrameInfo ->
                 frameInfo.requestMetadata[FLASH_MODE] != FLASH_MODE_TORCH &&
-                    requestMeta[CONTROL_AE_MODE] == CONTROL_AE_MODE_ON_AUTO_FLASH
+                    requestMeta.isAeMode(CONTROL_AE_MODE_ON_AUTO_FLASH)
             },
             TIMEOUT
         )
@@ -341,6 +355,32 @@
     }
 
     @Test
+    fun setTemplatePreview_afModeToContinuousPicture() = runBlocking {
+        bindUseCase(createPreview())
+
+        // Assert. Verify the afMode.
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+                requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_PICTURE)
+            },
+            TIMEOUT
+        )
+    }
+
+    @Test
+    fun setTemplateRecord_afModeToContinuousVideo() = runBlocking {
+        bindUseCase(createVideoCapture())
+
+        // Assert. Verify the afMode.
+        waitForResult(captureCount = 60).verify(
+            { requestMeta: RequestMetadata, _ ->
+                requestMeta.isAfMode(CONTROL_AF_MODE_CONTINUOUS_VIDEO)
+            },
+            TIMEOUT
+        )
+    }
+
+    @Test
     fun setZoomRatio_operationCanceledExceptionIfNoUseCase() {
         assertFutureFailedWithOperationCancellation(cameraControl.setZoomRatio(1.5f))
     }
@@ -436,4 +476,70 @@
         )
         cameraControl = camera.cameraControl as CameraControlAdapter
     }
+
+    private fun createVideoCapture(): VideoCapture<Recorder> {
+        return VideoCapture.withOutput(Recorder.Builder().build())
+    }
+
+    private suspend fun createPreview(): Preview =
+        Preview.Builder().build().also { preview ->
+            withContext(Dispatchers.Main) {
+                preview.setSurfaceProvider(getSurfaceProvider())
+            }
+        }
+
+    private fun getSurfaceProvider(): Preview.SurfaceProvider {
+        return SurfaceTextureProvider.createSurfaceTextureProvider(
+            object : SurfaceTextureProvider.SurfaceTextureCallback {
+                override fun onSurfaceTextureReady(
+                    surfaceTexture: SurfaceTexture,
+                    resolution: Size
+                ) {
+                    // No-op
+                }
+
+                override fun onSafeToRelease(surfaceTexture: SurfaceTexture) {
+                    surfaceTexture.release()
+                }
+            }
+        )
+    }
+
+    private fun RequestMetadata.isAfMode(afMode: Int): Boolean {
+        return if (characteristics.isAfModeSupported(afMode)) {
+            getOrDefault(CONTROL_AF_MODE, null) == afMode
+        } else {
+            val fallbackMode =
+                if (characteristics.isAfModeSupported(CONTROL_AF_MODE_CONTINUOUS_PICTURE)) {
+                    CONTROL_AF_MODE_CONTINUOUS_PICTURE
+                } else if (characteristics.isAfModeSupported(CONTROL_AF_MODE_AUTO)) {
+                    CONTROL_AF_MODE_AUTO
+                } else {
+                    CONTROL_AF_MODE_OFF
+                }
+            getOrDefault(CONTROL_AF_MODE, null) == fallbackMode
+        }
+    }
+
+    private fun RequestMetadata.isAeMode(aeMode: Int): Boolean {
+        return if (characteristics.isAeModeSupported(aeMode)) {
+            getOrDefault(CONTROL_AE_MODE, null) == aeMode
+        } else {
+            val fallbackMode =
+                if (characteristics.isAeModeSupported(CONTROL_AE_MODE_ON)) {
+                    CONTROL_AE_MODE_ON
+                } else {
+                    CONTROL_AE_MODE_OFF
+                }
+            getOrDefault(CONTROL_AE_MODE, null) == fallbackMode
+        }
+    }
+
+    private fun CameraCharacteristics.isAfModeSupported(
+        afMode: Int
+    ) = (get(CONTROL_AF_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(afMode)
+
+    private fun CameraCharacteristics.isAeModeSupported(
+        aeMode: Int
+    ) = (get(CONTROL_AE_AVAILABLE_MODES) ?: intArrayOf(-1)).contains(aeMode)
 }
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 4c1ef19..de3e23f 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
@@ -35,6 +35,7 @@
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
 import androidx.camera.camera2.pipe.integration.impl.FlashControl
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.TorchControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
@@ -67,6 +68,7 @@
         EvCompControl.Bindings::class,
         FlashControl.Bindings::class,
         FocusMeteringControl.Bindings::class,
+        State3AControl.Bindings::class,
         TorchControl.Bindings::class,
         Camera2CameraControlCompat.Bindings::class,
     ],
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
index 0a00d07..21fe187 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/FlashControl.kt
@@ -16,7 +16,6 @@
 
 package androidx.camera.camera2.pipe.integration.impl
 
-import android.hardware.camera2.CaptureRequest
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.integration.config.CameraScope
 import androidx.camera.core.CameraControl
@@ -25,12 +24,12 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
-private const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
+internal const val DEFAULT_FLASH_MODE = ImageCapture.FLASH_MODE_OFF
 
 /**
  * Implementation of Flash control exposed by [CameraControlInternal].
@@ -38,6 +37,7 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @CameraScope
 class FlashControl @Inject constructor(
+    private val state3AControl: State3AControl,
     private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
     private var _useCaseCamera: UseCaseCamera? = null
@@ -74,7 +74,7 @@
     fun setFlashAsync(flashMode: Int): Deferred<Unit> {
         val signal = CompletableDeferred<Unit>()
 
-        useCaseCamera?.let { useCaseCamera ->
+        useCaseCamera?.let {
 
             // Update _flashMode immediately so that CameraControlInternal#getFlashMode()
             // returns correct value.
@@ -84,24 +84,8 @@
                 stopRunningTask()
 
                 _updateSignal = signal
-                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
-                    //    )
-                    else -> CaptureRequest.CONTROL_AE_MODE_ON
-                }.let { aeMode ->
-                    // TODO: check the AE mode is supported before set it.
-                    useCaseCamera.requestControl.addParametersAsync(
-                        type = UseCaseCameraRequestControl.Type.FLASH,
-                        values = mapOf(
-                            CaptureRequest.CONTROL_AE_MODE to aeMode,
-                        )
-                    )
-                }.join()
+                state3AControl.flashMode = flashMode
+                state3AControl.updateSignal?.join()
 
                 signal.complete(Unit)
             }
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 43b6750..08fd80a 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
@@ -19,6 +19,7 @@
 import android.graphics.PointF
 import android.graphics.Rect
 import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CaptureRequest
 import android.hardware.camera2.CaptureResult
 import android.hardware.camera2.params.MeteringRectangle
 import android.util.Rational
@@ -54,8 +55,9 @@
 @CameraScope
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 class FocusMeteringControl @Inject constructor(
-    val cameraProperties: CameraProperties,
-    val threads: UseCaseThreads,
+    private val cameraProperties: CameraProperties,
+    private val state3AControl: State3AControl,
+    private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
     private var _useCaseCamera: UseCaseCamera? = null
 
@@ -155,6 +157,9 @@
                     )
                     return@launch
                 }
+                if (afRectangles.isNotEmpty()) {
+                    state3AControl.preferredFocusMode = CaptureRequest.CONTROL_AF_MODE_AUTO
+                }
                 val (isCancelEnabled, timeout) = if (action.isAutoCancelEnabled &&
                     action.autoCancelDurationInMillis < autoFocusTimeoutMs
                 ) {
@@ -259,6 +264,7 @@
         signalToCancel: CompletableDeferred<FocusMeteringResult>?,
     ): Result3A {
         signalToCancel?.setCancelException("Cancelled by cancelFocusAndMetering()")
+        state3AControl.preferredFocusMode = null
         return useCaseCamera.requestControl.cancelFocusAndMeteringAsync().await()
     }
 
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
new file mode 100644
index 0000000..740438c
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/State3AControl.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.camera.camera2.pipe.integration.impl
+
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
+import androidx.camera.camera2.pipe.integration.config.CameraScope
+import androidx.camera.core.ImageCapture
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.CaptureConfig
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.lifecycle.Observer
+import dagger.Binds
+import dagger.Module
+import dagger.multibindings.IntoSet
+import javax.inject.Inject
+import kotlin.properties.ObservableProperty
+import kotlin.reflect.KProperty
+import kotlinx.coroutines.CompletableDeferred
+import kotlinx.coroutines.Deferred
+
+@CameraScope
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+class State3AControl @Inject constructor(
+    val cameraProperties: CameraProperties,
+) : UseCaseCameraControl {
+    private var _useCaseCamera: UseCaseCamera? = null
+    override var useCaseCamera: UseCaseCamera?
+        get() = _useCaseCamera
+        set(value) {
+            val previousUseCaseCamera = _useCaseCamera
+            _useCaseCamera = value
+            CameraXExecutors.mainThreadExecutor().execute {
+                previousUseCaseCamera?.runningUseCasesLiveData?.removeObserver(
+                    useCaseChangeObserver
+                )
+                value?.let {
+                    it.runningUseCasesLiveData.observeForever(useCaseChangeObserver)
+                    invalidate() // Always apply the settings to the camera.
+                }
+            }
+        }
+
+    private val useCaseChangeObserver =
+        Observer<Set<UseCase>> { useCases -> useCases.updateTemplate() }
+    private val afModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AF_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AF_MODE_OFF)
+    ).asList()
+    private val aeModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AE_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AE_MODE_OFF)
+    ).asList()
+    private val awbModes = cameraProperties.metadata.getOrDefault(
+        CameraCharacteristics.CONTROL_AWB_AVAILABLE_MODES,
+        intArrayOf(CaptureRequest.CONTROL_AWB_MODE_OFF)
+    ).asList()
+
+    var updateSignal: Deferred<Unit>? = null
+        private set
+    var flashMode by updateOnPropertyChange(DEFAULT_FLASH_MODE)
+    var template by updateOnPropertyChange(DEFAULT_REQUEST_TEMPLATE)
+    var preferredAeMode: Int? by updateOnPropertyChange(null)
+    var preferredFocusMode: Int? by updateOnPropertyChange(null)
+
+    override fun reset() {
+        preferredAeMode = null
+        preferredFocusMode = null
+        flashMode = DEFAULT_FLASH_MODE
+        template = DEFAULT_REQUEST_TEMPLATE
+    }
+
+    private fun <T> updateOnPropertyChange(
+        initialValue: T
+    ) = object : ObservableProperty<T>(initialValue) {
+        override fun afterChange(property: KProperty<*>, oldValue: T, newValue: T) {
+            if (newValue != oldValue) {
+                invalidate()
+            }
+        }
+    }
+
+    fun invalidate() {
+        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
+            //    )
+            else -> CaptureRequest.CONTROL_AE_MODE_ON
+        }
+
+        val preferAfMode = preferredFocusMode ?: getDefaultAfMode()
+
+        updateSignal = useCaseCamera?.requestControl?.addParametersAsync(
+            values = mapOf(
+                CaptureRequest.CONTROL_AE_MODE to getSupportedAeMode(preferAeMode),
+                CaptureRequest.CONTROL_AF_MODE to getSupportedAfMode(preferAfMode),
+                CaptureRequest.CONTROL_AWB_MODE to getSupportedAwbMode(
+                    CaptureRequest.CONTROL_AWB_MODE_AUTO
+                ),
+            )
+        ) ?: CompletableDeferred(null)
+    }
+
+    private fun getDefaultAfMode(): Int = when (template) {
+        CameraDevice.TEMPLATE_RECORD -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_VIDEO
+        CameraDevice.TEMPLATE_PREVIEW -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+        else -> CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+    }
+
+    /**
+     * If preferredMode not available, priority is CONTINUOUS_PICTURE > AUTO > OFF
+     */
+    private fun getSupportedAfMode(preferredMode: Int) = when {
+        afModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        afModes.contains(CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE) -> {
+            CaptureRequest.CONTROL_AF_MODE_CONTINUOUS_PICTURE
+        }
+
+        afModes.contains(CaptureRequest.CONTROL_AF_MODE_AUTO) -> {
+            CaptureRequest.CONTROL_AF_MODE_AUTO
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AF_MODE_OFF
+        }
+    }
+
+    /**
+     * If preferredMode not available, priority is AE_ON > AE_OFF
+     */
+    private fun getSupportedAeMode(preferredMode: Int) = when {
+        aeModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        aeModes.contains(CaptureRequest.CONTROL_AE_MODE_ON) -> {
+            CaptureRequest.CONTROL_AE_MODE_ON
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AE_MODE_OFF
+        }
+    }
+
+    /**
+     * If preferredMode not available, priority is AWB_AUTO > AWB_OFF
+     */
+    private fun getSupportedAwbMode(preferredMode: Int) = when {
+        awbModes.contains(preferredMode) -> {
+            preferredMode
+        }
+
+        awbModes.contains(CaptureRequest.CONTROL_AWB_MODE_AUTO) -> {
+            CaptureRequest.CONTROL_AWB_MODE_AUTO
+        }
+
+        else -> {
+            CaptureRequest.CONTROL_AWB_MODE_OFF
+        }
+    }
+
+    private fun Collection<UseCase>.updateTemplate() {
+        SessionConfigAdapter(this).getValidSessionConfigOrNull()?.let {
+            val templateType = it.repeatingCaptureConfig.templateType
+            template = if (templateType != CaptureConfig.TEMPLATE_TYPE_NONE) {
+                templateType
+            } else {
+                DEFAULT_REQUEST_TEMPLATE
+            }
+        }
+    }
+
+    @Module
+    abstract class Bindings {
+        @Binds
+        @IntoSet
+        abstract fun provideControls(state3AControl: State3AControl): UseCaseCameraControl
+    }
+}
\ No newline at end of file
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 ffce1ecb..7981dc9 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
@@ -29,10 +29,10 @@
 import dagger.Binds
 import dagger.Module
 import dagger.multibindings.IntoSet
+import javax.inject.Inject
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.launch
-import javax.inject.Inject
 
 /**
  * Implementation of Torch control exposed by [CameraControlInternal].
@@ -41,6 +41,7 @@
 @CameraScope
 class TorchControl @Inject constructor(
     cameraProperties: CameraProperties,
+    private val state3AControl: State3AControl,
     private val threads: UseCaseThreads,
 ) : UseCaseCameraControl {
 
@@ -94,20 +95,10 @@
                 // TODO(b/209757083), handle the failed result of the setTorchAsync().
                 useCaseCamera.requestControl.setTorchAsync(torch).join()
 
-                if (torch) {
-                    // Hold the internal AE mode to ON while the torch is turned ON.
-                    useCaseCamera.requestControl.addParametersAsync(
-                        type = UseCaseCameraRequestControl.Type.TORCH,
-                        values = mapOf(
-                            CaptureRequest.CONTROL_AE_MODE to CaptureRequest.CONTROL_AE_MODE_ON,
-                        )
-                    )
-                } else {
-                    // Restore the AE mode after the torch control has been used.
-                    useCaseCamera.requestControl.setConfigAsync(
-                        type = UseCaseCameraRequestControl.Type.TORCH,
-                    )
-                }.join()
+                // Hold the internal AE mode to ON while the torch is turned ON.
+                state3AControl.preferredAeMode =
+                    if (torch) CaptureRequest.CONTROL_AE_MODE_ON else null
+                state3AControl.updateSignal?.join()
 
                 signal.complete(Unit)
             }
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 3d9377d..852b4f4 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
@@ -48,7 +48,7 @@
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.async
 
-private const val DEFAULT_REQUEST_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW
+internal const val DEFAULT_REQUEST_TEMPLATE = CameraDevice.TEMPLATE_PREVIEW
 
 /**
  * The RequestControl provides a couple of APIs to update the config of the camera, it also stores
@@ -66,8 +66,6 @@
     enum class Type {
         SESSION_CONFIG,
         DEFAULT,
-        FLASH,
-        TORCH,
         CAMERA2_CAMERA_CONTROL,
     }
 
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 8ca5d39..5c5af8b 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
@@ -31,6 +31,7 @@
 import androidx.camera.camera2.pipe.Result3A
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
 import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
@@ -1028,19 +1029,84 @@
         assertFutureFocusCompleted(future, false)
     }
 
+    @Test
+    fun startFocusMetering_afAutoModeIsSet() {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(CaptureRequest.CONTROL_AF_MODE_AUTO)
+    }
+
+    @Test
+    fun startFocusMetering_AfNotInvolved_afAutoModeNotSet() {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(
+            point1,
+            FocusMeteringAction.FLAG_AE or FocusMeteringAction.FLAG_AWB
+        ).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(null)
+    }
+
+    @Test
+    fun startAndThenCancel_afAutoModeNotSet(): Unit = runBlocking {
+        // Arrange.
+        val action = FocusMeteringAction.Builder(point1, FocusMeteringAction.FLAG_AF).build()
+        val state3AControl = createState3AControl(CAMERA_ID_0)
+        focusMeteringControl = initFocusMeteringControl(
+            CAMERA_ID_0,
+            setOf(createPreview(Size(1920, 1080))),
+            fakeUseCaseThreads,
+            state3AControl,
+        )
+
+        // Act.
+        focusMeteringControl.startFocusAndMetering(
+            action
+        )[5, TimeUnit.SECONDS]
+        focusMeteringControl.cancelFocusAndMeteringAsync().join()
+
+        // Assert.
+        assertThat(
+            state3AControl.preferredFocusMode
+        ).isEqualTo(null)
+    }
+
     // TODO: Port the following tests once their corresponding logics have been implemented.
     //  - [b/255679866] triggerAfWithTemplate, triggerAePrecaptureWithTemplate,
     //          cancelAfAeTriggerWithTemplate
     //  - startFocusAndMetering_AfRegionCorrectedByQuirk
     //  - [b/262225455] cropRegionIsSet_resultBasedOnCropRegion
-    //  The following ones will depend on how exactly they will be implemented.
-    //  - [b/264018162] addFocusMeteringOptions_hasCorrectAfMode,
-    //                  startFocusMetering_isAfAutoModeIsTrue,
-    //                  startFocusMetering_AfNotInvolved_isAfAutoModeIsSet,
-    //                  startAndThenCancel_isAfAutoModeIsFalse
-    //      (an alternative way can be checking the AF mode
-    //      at the frame with AF_TRIGGER_START request in capture callback, but this requires
-    //      invoking actual camera operations, ref: TapToFocusDeviceTest)
 
     private fun assertFutureFocusCompleted(
         future: ListenableFuture<FocusMeteringResult>,
@@ -1135,8 +1201,11 @@
         cameraId: String,
         useCases: Set<UseCase> = emptySet(),
         useCaseThreads: UseCaseThreads = fakeUseCaseThreads,
+        state3AControl: State3AControl = createState3AControl(cameraId),
     ) = FocusMeteringControl(
-            cameraPropertiesMap[cameraId]!!, useCaseThreads
+            cameraPropertiesMap[cameraId]!!,
+            state3AControl,
+            useCaseThreads
         ).apply {
             fakeUseCaseCamera.runningUseCasesLiveData.value = useCases
             useCaseCamera = fakeUseCaseCamera
@@ -1260,4 +1329,12 @@
                     StreamSpec.builder(suggestedStreamSpecResolution).build()
                 )
             }
+
+    private fun createState3AControl(
+        cameraId: String = CAMERA_ID_0,
+        properties: CameraProperties = cameraPropertiesMap[cameraId]!!,
+        useCaseCamera: UseCaseCamera = fakeUseCaseCamera,
+    ) = State3AControl(properties).apply {
+        this.useCaseCamera = useCaseCamera
+    }
 }
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 6337c98..f6a563e 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
@@ -177,15 +177,21 @@
 
     @Before
     fun setUp() {
+        val fakeUseCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
+        val fakeCameraProperties = FakeCameraProperties(
+            FakeCameraMetadata(
+                mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
+            )
+        )
+
         torchControl = TorchControl(
-            FakeCameraProperties(
-                FakeCameraMetadata(
-                    mapOf(CameraCharacteristics.FLASH_INFO_AVAILABLE to true),
-                )
-            ),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         ).also {
-            it.useCaseCamera = FakeUseCaseCamera(requestControl = fakeRequestControl)
+            it.useCaseCamera = fakeUseCaseCamera
 
             // Ensure the control is updated after the UseCaseCamera been set.
             assertThat(
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 be46710..6b24bc2 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
@@ -147,33 +147,50 @@
 
     @Before
     fun setUp() {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val fakeCameraProperties = FakeCameraProperties(metadata)
         torchControl = TorchControl(
-            FakeCameraProperties(metadata),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         )
-        torchControl.useCaseCamera = FakeUseCaseCamera()
+        torchControl.useCaseCamera = fakeUseCaseCamera
     }
 
     @Test
     fun enableTorch_whenNoFlashUnit(): Unit = runBlocking {
         assertThrows<IllegalStateException> {
+            val fakeUseCaseCamera = FakeUseCaseCamera()
+            val fakeCameraProperties = FakeCameraProperties()
+
             // Without a flash unit, this Job will complete immediately with a IllegalStateException
             TorchControl(
-                FakeCameraProperties(),
+                fakeCameraProperties,
+                State3AControl(fakeCameraProperties).apply {
+                    useCaseCamera = fakeUseCaseCamera
+                },
                 fakeUseCaseThreads,
             ).also {
-                it.useCaseCamera = FakeUseCaseCamera()
+                it.useCaseCamera = fakeUseCaseCamera
             }.setTorchAsync(true).await()
         }
     }
 
     @Test
     fun getTorchState_whenNoFlashUnit() {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val fakeCameraProperties = FakeCameraProperties()
+
         val torchState = TorchControl(
-            FakeCameraProperties(),
+            fakeCameraProperties,
+            State3AControl(fakeCameraProperties).apply {
+                useCaseCamera = fakeUseCaseCamera
+            },
             fakeUseCaseThreads,
         ).also {
-            it.useCaseCamera = FakeUseCaseCamera()
+            it.useCaseCamera = fakeUseCaseCamera
         }.torchStateLiveData.value
 
         Truth.assertThat(torchState).isEqualTo(TorchState.OFF)
@@ -182,8 +199,14 @@
     @Test
     fun enableTorch_whenInactive(): Unit = runBlocking {
         assertThrows<CameraControl.OperationCanceledException> {
+            val fakeUseCaseCamera = FakeUseCaseCamera()
+            val fakeCameraProperties = FakeCameraProperties(metadata)
+
             TorchControl(
-                FakeCameraProperties(metadata),
+                fakeCameraProperties,
+                State3AControl(fakeCameraProperties).apply {
+                    useCaseCamera = fakeUseCaseCamera
+                },
                 fakeUseCaseThreads,
             ).setTorchAsync(true).await()
         }
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 5c6e224..e98ffc8 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
@@ -29,6 +29,7 @@
 import androidx.camera.camera2.pipe.integration.impl.CameraProperties
 import androidx.camera.camera2.pipe.integration.impl.EvCompControl
 import androidx.camera.camera2.pipe.integration.impl.FocusMeteringControl
+import androidx.camera.camera2.pipe.integration.impl.State3AControl
 import androidx.camera.camera2.pipe.integration.impl.TorchControl
 import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
 import androidx.camera.camera2.pipe.integration.impl.ZoomControl
@@ -79,21 +80,28 @@
             cameraId
         ),
         zoomControl: ZoomControl = this.zoomControl,
-    ) = CameraInfoAdapter(
-        cameraProperties,
-        CameraConfig(cameraId),
-        CameraStateAdapter(),
-        CameraControlStateAdapter(
-            zoomControl,
-            EvCompControl(FakeEvCompCompat()),
-            TorchControl(cameraProperties, useCaseThreads),
-        ),
-        CameraCallbackMap(),
-        FocusMeteringControl(
-            cameraProperties,
-            useCaseThreads
-        ).apply {
-            useCaseCamera = FakeUseCaseCamera()
+    ): CameraInfoAdapter {
+        val fakeUseCaseCamera = FakeUseCaseCamera()
+        val state3AControl = State3AControl(cameraProperties).apply {
+            useCaseCamera = fakeUseCaseCamera
         }
-    )
+        return CameraInfoAdapter(
+            cameraProperties,
+            CameraConfig(cameraId),
+            CameraStateAdapter(),
+            CameraControlStateAdapter(
+                zoomControl,
+                EvCompControl(FakeEvCompCompat()),
+                TorchControl(cameraProperties, state3AControl, useCaseThreads),
+            ),
+            CameraCallbackMap(),
+            FocusMeteringControl(
+                cameraProperties,
+                state3AControl,
+                useCaseThreads
+            ).apply {
+                useCaseCamera = fakeUseCaseCamera
+            }
+        )
+    }
 }
diff --git a/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.kt
new file mode 100644
index 0000000..dd416ee
--- /dev/null
+++ b/camera/camera-extensions/src/androidTest/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheckTest.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.camera.extensions.internal.compat.workaround
+
+import android.os.SystemClock
+import androidx.camera.extensions.internal.compat.workaround.OnEnableDisableSessionDurationCheck.MIN_DURATION_FOR_ENABLE_DISABLE_SESSION
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import com.google.common.collect.Range
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@SmallTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class OnEnableDisableSessionDurationCheckTest {
+    companion object {
+        const val TOLERANCE = 15L
+    }
+
+    @Test
+    fun enabled_ensureMinimalDuration() {
+        // Arrange
+        val check = OnEnableDisableSessionDurationCheck(/* enabledMinimumDuration */true)
+
+        val duration = 40L
+        // Act
+        val startTime = SystemClock.elapsedRealtime()
+        check.onEnableSessionInvoked()
+        Thread.sleep(duration)
+        check.onDisableSessionInvoked()
+        val endTime = SystemClock.elapsedRealtime()
+
+        // Assert
+        assertThat((endTime - startTime))
+            .isIn(
+                Range.closed(
+                    MIN_DURATION_FOR_ENABLE_DISABLE_SESSION,
+                    MIN_DURATION_FOR_ENABLE_DISABLE_SESSION + TOLERANCE
+                ))
+    }
+
+    @Test
+    fun enabled_doNotWaitExtraIfDurationExceeds() {
+        // 1. Arrange
+        val check = OnEnableDisableSessionDurationCheck(/* enabledMinimumDuration */true)
+
+        // make the duration of onEnable to onDisable to be the minimal duration.
+        val duration = MIN_DURATION_FOR_ENABLE_DISABLE_SESSION
+
+        // 2. Act
+        val startTime = SystemClock.elapsedRealtime()
+        check.onEnableSessionInvoked()
+        // make the duration of onEnable to onDisable to be the minimal duration.
+        Thread.sleep(duration)
+        check.onDisableSessionInvoked()
+        val endTime = SystemClock.elapsedRealtime()
+
+        // 3. Assert: no extra time waited.
+        assertThat((endTime - startTime))
+            .isLessThan(
+                duration + TOLERANCE
+            )
+    }
+
+    @Test
+    fun disabled_doNotWait() {
+        // 1. Arrange
+        val check = OnEnableDisableSessionDurationCheck(/* enabledMinimumDuration */ false)
+
+        // 2. Act
+        val startTime = SystemClock.elapsedRealtime()
+        check.onEnableSessionInvoked()
+        check.onDisableSessionInvoked()
+        val endTime = SystemClock.elapsedRealtime()
+
+        // 3. Assert
+        assertThat((endTime - startTime))
+            .isLessThan(TOLERANCE)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/CrashWhenOnDisableTooSoon.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/CrashWhenOnDisableTooSoon.java
new file mode 100644
index 0000000..5ed2ce16
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/CrashWhenOnDisableTooSoon.java
@@ -0,0 +1,37 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.quirk;
+
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * <p>QuirkSummary
+ * Bug Id: b/245226085,
+ * Description: When enabling Extensions on devices that implement the Basic Extender. If
+ * onDisableSession is invoked within 50ms after onEnableSession, It could cause crashes like
+ * surface abandoned.
+ * Device(s): All Samsung devices
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class CrashWhenOnDisableTooSoon implements Quirk {
+    static boolean load() {
+        return Build.BRAND.equalsIgnoreCase("SAMSUNG");
+    }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
index bd85647..82bb2e7 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/DeviceQuirksLoader.java
@@ -44,6 +44,10 @@
             quirks.add(new ExtensionDisabledQuirk());
         }
 
+        if (CrashWhenOnDisableTooSoon.load()) {
+            quirks.add(new CrashWhenOnDisableTooSoon());
+        }
+
         return quirks;
     }
 }
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtensionDisabledQuirk.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtensionDisabledQuirk.java
index 992962c..c1ba5c4 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtensionDisabledQuirk.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/quirk/ExtensionDisabledQuirk.java
@@ -40,17 +40,16 @@
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public class ExtensionDisabledQuirk implements Quirk {
-    private final boolean mIsAdvancedInterface = isAdvancedExtenderSupported();
 
     static boolean load() {
-        return isPixel5() || isMoto() || isAdvancedExtenderSupported();
+        return isPixel5() || isMoto();
     }
 
     /**
      * Checks whether extension should be disabled.
      */
     public boolean shouldDisableExtension() {
-        if (isPixel5() && !mIsAdvancedInterface) {
+        if (isPixel5() && !isAdvancedExtenderSupported()) {
             // 1. Disables Pixel 5's Basic Extender capability.
             return true;
         } else if (isMoto() && ExtensionVersion.isMaximumCompatibleVersion(Version.VERSION_1_1)) {
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheck.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheck.java
new file mode 100644
index 0000000..ffc0791
--- /dev/null
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/compat/workaround/OnEnableDisableSessionDurationCheck.java
@@ -0,0 +1,90 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.extensions.internal.compat.workaround;
+
+import android.os.SystemClock;
+
+import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
+import androidx.camera.core.Logger;
+import androidx.camera.extensions.internal.compat.quirk.CrashWhenOnDisableTooSoon;
+import androidx.camera.extensions.internal.compat.quirk.DeviceQuirks;
+
+/**
+ * A workaround to ensure the duration of onEnableSession to onDisableSession is long enough.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public class OnEnableDisableSessionDurationCheck {
+    private static final String TAG = "OnEnableDisableSessionDurationCheck";
+    private final boolean mEnabledMinimumDuration;
+    private long mOnEnableSessionTimeStamp = 0;
+    @VisibleForTesting
+    static final long MIN_DURATION_FOR_ENABLE_DISABLE_SESSION = 100L;
+
+    public OnEnableDisableSessionDurationCheck() {
+        mEnabledMinimumDuration = DeviceQuirks.get(CrashWhenOnDisableTooSoon.class) != null;
+    }
+
+    @VisibleForTesting
+    OnEnableDisableSessionDurationCheck(boolean enabledMinimalDuration) {
+        mEnabledMinimumDuration = enabledMinimalDuration;
+    }
+
+    /**
+     * Notify onEnableSession is invoked.
+     */
+    public void onEnableSessionInvoked() {
+        if (mEnabledMinimumDuration) {
+            mOnEnableSessionTimeStamp = SystemClock.elapsedRealtime();
+        }
+    }
+
+    /**
+     * Notify onDisableSession is invoked.
+     */
+    public void onDisableSessionInvoked() {
+        if (mEnabledMinimumDuration) {
+            ensureMinDurationAfterOnEnableSession();
+        }
+    }
+
+    /**
+     * Ensures onDisableSession not invoked too soon after onDisableSession. OEMs usually
+     * releases resources at onDisableSession. Invoking onDisableSession too soon might cause
+     * some crash during the initialization triggered by onEnableSession or onInit.
+     *
+     * It will ensure the duration is at least 100 ms after onEnabledSession is invoked. If the
+     * camera is opened more than 100ms, then it won't add any extra delay. Since a regular
+     * camera session will take more than 100ms, this change shouldn't cause any real impact for
+     * the user. It only affects the auto testing by increasing a little bit delay which
+     * should be okay.
+     */
+    private void ensureMinDurationAfterOnEnableSession() {
+        long timeAfterOnEnableSession =
+                SystemClock.elapsedRealtime() - mOnEnableSessionTimeStamp;
+        if (timeAfterOnEnableSession < MIN_DURATION_FOR_ENABLE_DISABLE_SESSION) {
+            try {
+                long timeToWait =
+                        MIN_DURATION_FOR_ENABLE_DISABLE_SESSION - timeAfterOnEnableSession;
+                Logger.d(TAG, "onDisableSession too soon, wait " + timeToWait + " ms");
+                Thread.sleep(timeToWait);
+            } catch (InterruptedException e) {
+                Logger.e(TAG, "sleep interrupted");
+            }
+        }
+    }
+}
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
index 5049106..5452a8a 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/sessionprocessor/BasicExtenderSessionProcessor.java
@@ -49,6 +49,7 @@
 import androidx.camera.extensions.impl.PreviewExtenderImpl;
 import androidx.camera.extensions.impl.PreviewImageProcessorImpl;
 import androidx.camera.extensions.impl.RequestUpdateProcessorImpl;
+import androidx.camera.extensions.internal.compat.workaround.OnEnableDisableSessionDurationCheck;
 import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
@@ -90,6 +91,8 @@
     static AtomicInteger sLastOutputConfigId = new AtomicInteger(0);
     @GuardedBy("mLock")
     private final Map<CaptureRequest.Key<?>, Object> mParameters = new LinkedHashMap<>();
+    private OnEnableDisableSessionDurationCheck mOnEnableDisableSessionDurationCheck =
+            new OnEnableDisableSessionDurationCheck();
 
     public BasicExtenderSessionProcessor(@NonNull PreviewExtenderImpl previewExtenderImpl,
             @NonNull ImageCaptureExtenderImpl imageCaptureExtenderImpl,
@@ -252,6 +255,7 @@
         if (captureStage2 != null) {
             captureStages.add(captureStage2);
         }
+        mOnEnableDisableSessionDurationCheck.onEnableSessionInvoked();
 
         if (!captureStages.isEmpty()) {
             submitRequestByCaptureStages(requestProcessor, captureStages);
@@ -323,6 +327,7 @@
 
     @Override
     public void onCaptureSessionEnd() {
+        mOnEnableDisableSessionDurationCheck.onDisableSessionInvoked();
         List<CaptureStageImpl> captureStages = new ArrayList<>();
         CaptureStageImpl captureStage1 = mPreviewExtenderImpl.onDisableSession();
         Logger.d(TAG, "preview onDisableSession: " + captureStage1);
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/mocks/MockConsumer.java b/camera/camera-testing/src/main/java/androidx/camera/testing/mocks/MockConsumer.java
index a05d407..b3f57afc 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/mocks/MockConsumer.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/mocks/MockConsumer.java
@@ -18,6 +18,7 @@
 
 import static com.google.common.truth.Truth.assertWithMessage;
 
+import androidx.annotation.GuardedBy;
 import androidx.annotation.IntRange;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -47,9 +48,12 @@
 
     private static final long NO_TIMEOUT = 0;
 
+    @GuardedBy("mLock")
     private CountDownLatch mLatch;
-
+    private final Object mLock = new Object();
     private final List<T> mEventList = new ArrayList<>();
+    @NonNull
+    private List<T> mVerifyingEventList = new ArrayList<>();
     private final Map<Integer, Boolean> mIsEventVerifiedByIndex = new HashMap<>();
     private int mIndexLastVerifiedInOrder = -1;
 
@@ -58,10 +62,14 @@
     private boolean mInOrder = false;
 
     private int getMatchingEventCount() {
+        return getMatchingEventCount(mVerifyingEventList);
+    }
+
+    private int getMatchingEventCount(@NonNull List<T> eventList) {
         int count = 0;
         int startIndex = mInOrder ? mIndexLastVerifiedInOrder + 1 : 0;
-        for (int i = startIndex; i < mEventList.size(); i++) {
-            if (mClassTypeToVerify.isInstance(mEventList.get(i))) {
+        for (int i = startIndex; i < eventList.size(); i++) {
+            if (mClassTypeToVerify.isInstance(eventList.get(i))) {
                 count++;
             }
         }
@@ -70,8 +78,8 @@
 
     private int getLastVerifiedEventInOrder() {
         int count = 0;
-        for (int i = mIndexLastVerifiedInOrder + 1; i < mEventList.size(); i++) {
-            if (mClassTypeToVerify.isInstance(mEventList.get(i))) {
+        for (int i = mIndexLastVerifiedInOrder + 1; i < mVerifyingEventList.size(); i++) {
+            if (mClassTypeToVerify.isInstance(mVerifyingEventList.get(i))) {
                 count++;
             }
 
@@ -88,15 +96,19 @@
             return;
         }
 
-        for (int i = 0; i < mEventList.size(); i++) {
-            if (mClassTypeToVerify.isInstance(mEventList.get(i))) {
+        for (int i = 0; i < mVerifyingEventList.size(); i++) {
+            if (mClassTypeToVerify.isInstance(mVerifyingEventList.get(i))) {
                 mIsEventVerifiedByIndex.put(i, true);
             }
         }
     }
 
     private boolean isVerified() {
-        return mCallTimes.isSatisfied(getMatchingEventCount());
+        return isVerified(mVerifyingEventList);
+    }
+
+    private boolean isVerified(@NonNull List<T> eventList) {
+        return mCallTimes.isSatisfied(getMatchingEventCount(eventList));
     }
 
     /**
@@ -171,20 +183,30 @@
         mClassTypeToVerify = classType;
         mCallTimes = callTimes;
         mInOrder = inOrder;
+        snapshotVerifyingEventList();
 
-        if (!isVerified()) {
-            if (timeoutInMillis != NO_TIMEOUT) {
-                mLatch = new CountDownLatch(1);
-                try {
-                    assertWithMessage(
-                            "Test failed for a timeout of " + timeoutInMillis + " ms"
-                    ).that(mLatch.await(timeoutInMillis, TimeUnit.MILLISECONDS)).isTrue();
-                } catch (InterruptedException e) {
-                    throw new RuntimeException(e);
-                }
-                mLatch = null;
+        CountDownLatch latch = null;
+        boolean isVerified;
+        synchronized (mLock) {
+            isVerified = isVerified();
+            if (!isVerified && timeoutInMillis != NO_TIMEOUT) {
+                latch = mLatch = new CountDownLatch(1);
             }
+        }
+        if (latch != null) {
+            try {
+                assertWithMessage("Test failed for a timeout of " + timeoutInMillis + " ms")
+                        .that(latch.await(timeoutInMillis, TimeUnit.MILLISECONDS)).isTrue();
+            } catch (InterruptedException e) {
+                throw new RuntimeException(e);
+            } finally {
+                synchronized (mLock) {
+                    mLatch = null;
+                }
+            }
+        }
 
+        if (!isVerified) {
             assertWithMessage(
                     "accept() called " + getMatchingEventCount() + " time(s) with "
                             + classType.getSimpleName()
@@ -201,7 +223,7 @@
         }
 
         if (captor != null) {
-            captor.setArguments(mEventList);
+            captor.setArguments(new ArrayList<>(mVerifyingEventList));
         }
     }
 
@@ -212,16 +234,17 @@
      *                verification, {@code false} otherwise.
      */
     public void verifyNoMoreAcceptCalls(boolean inOrder) {
+        snapshotVerifyingEventList();
         if (inOrder) {
             assertWithMessage(
                     "There are extra accept() calls after the last in-order verification"
-            ).that(mIndexLastVerifiedInOrder).isEqualTo(mEventList.size() - 1);
+            ).that(mIndexLastVerifiedInOrder).isEqualTo(mVerifyingEventList.size() - 1);
         } else {
-            for (int i = 0; i < mEventList.size(); i++) {
+            for (int i = 0; i < mVerifyingEventList.size(); i++) {
                 assertWithMessage(
                         "There are extra accept() calls after the last verification"
                                 + "\nFirst such call is with "
-                                + mEventList.get(i).getClass().getSimpleName() + " event"
+                                + mVerifyingEventList.get(i).getClass().getSimpleName() + " event"
                 ).that(mIsEventVerifiedByIndex.get(i)).isTrue();
             }
         }
@@ -232,6 +255,7 @@
      */
     public void clearAcceptCalls() {
         mEventList.clear();
+        mVerifyingEventList.clear();
         mIsEventVerifiedByIndex.clear();
         mIndexLastVerifiedInOrder = -1;
     }
@@ -240,8 +264,16 @@
     public void accept(T event) {
         mEventList.add(event);
 
-        if (mLatch != null && isVerified()) {
-            mLatch.countDown();
+        synchronized (mLock) {
+            if (mLatch != null && isVerified(mEventList)) {
+                snapshotVerifyingEventList();
+                mLatch.countDown();
+                mLatch = null;
+            }
         }
     }
+
+    private void snapshotVerifyingEventList() {
+        mVerifyingEventList = new ArrayList<>(mEventList);
+    }
 }
diff --git a/camera/camera-viewfinder/api/1.1.0-beta03.txt b/camera/camera-viewfinder/api/1.1.0-beta03.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder/api/1.1.0-beta03.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder/api/1.1.0-beta04.txt b/camera/camera-viewfinder/api/1.1.0-beta04.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder/api/1.1.0-beta04.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder/api/1.2.0-beta01.txt b/camera/camera-viewfinder/api/1.2.0-beta01.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/1.2.0-beta01.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/1.2.0-beta02.txt b/camera/camera-viewfinder/api/1.2.0-beta02.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/1.2.0-beta02.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/1.2.0-beta03.txt b/camera/camera-viewfinder/api/1.2.0-beta03.txt
index d452929..04cecbd 100644
--- a/camera/camera-viewfinder/api/1.2.0-beta03.txt
+++ b/camera/camera-viewfinder/api/1.2.0-beta03.txt
@@ -1,41 +1,2 @@
 // Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
 
diff --git a/camera/camera-viewfinder/api/current.txt b/camera/camera-viewfinder/api/current.txt
index d452929..48504db 100644
--- a/camera/camera-viewfinder/api/current.txt
+++ b/camera/camera-viewfinder/api/current.txt
@@ -10,7 +10,6 @@
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
     method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
     method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
   }
 
@@ -28,14 +27,30 @@
     enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
   }
 
+  @RequiresApi(21) public final class CameraViewfinderExt {
+    method public suspend Object? requestSurface(androidx.camera.viewfinder.CameraViewfinder, androidx.camera.viewfinder.ViewfinderSurfaceRequest viewfinderSurfaceRequest, kotlin.coroutines.Continuation<? super android.view.Surface>);
+    field public static final androidx.camera.viewfinder.CameraViewfinderExt INSTANCE;
+  }
+
   @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
+    method public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode? getImplementationMode();
+    method public int getLensFacing();
     method public android.util.Size getResolution();
     method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
     method public void markSurfaceSafeToRelease();
   }
 
+  public static final class ViewfinderSurfaceRequest.Builder {
+    ctor public ViewfinderSurfaceRequest.Builder(android.util.Size);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest build();
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setLensFacing(int);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setSensorOrientation(int);
+  }
+
+  public final class ViewfinderSurfaceRequestUtil {
+    method @RequiresApi(21) public static androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder populateFromCharacteristics(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder, android.hardware.camera2.CameraCharacteristics cameraCharacteristics);
+  }
+
 }
 
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta03.txt b/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta03.txt
deleted file mode 100644
index 69b776e..0000000
--- a/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta03.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-// Signature format: 4.0
-package @androidx.camera.viewfinder.ExperimentalViewfinder androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalViewfinder {
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta04.txt b/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta04.txt
deleted file mode 100644
index 69b776e..0000000
--- a/camera/camera-viewfinder/api/public_plus_experimental_1.1.0-beta04.txt
+++ /dev/null
@@ -1,44 +0,0 @@
-// Signature format: 4.0
-package @androidx.camera.viewfinder.ExperimentalViewfinder androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresOptIn(level=androidx.annotation.RequiresOptIn.Level.ERROR) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.CLASS) public @interface ExperimentalViewfinder {
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta01.txt b/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta01.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta01.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta02.txt b/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta02.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta02.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta03.txt b/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta03.txt
index d452929..04cecbd 100644
--- a/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta03.txt
+++ b/camera/camera-viewfinder/api/public_plus_experimental_1.2.0-beta03.txt
@@ -1,41 +1,2 @@
 // Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
 
diff --git a/camera/camera-viewfinder/api/public_plus_experimental_current.txt b/camera/camera-viewfinder/api/public_plus_experimental_current.txt
index d452929..48504db 100644
--- a/camera/camera-viewfinder/api/public_plus_experimental_current.txt
+++ b/camera/camera-viewfinder/api/public_plus_experimental_current.txt
@@ -10,7 +10,6 @@
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
     method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
     method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
   }
 
@@ -28,14 +27,30 @@
     enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
   }
 
+  @RequiresApi(21) public final class CameraViewfinderExt {
+    method public suspend Object? requestSurface(androidx.camera.viewfinder.CameraViewfinder, androidx.camera.viewfinder.ViewfinderSurfaceRequest viewfinderSurfaceRequest, kotlin.coroutines.Continuation<? super android.view.Surface>);
+    field public static final androidx.camera.viewfinder.CameraViewfinderExt INSTANCE;
+  }
+
   @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
+    method public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode? getImplementationMode();
+    method public int getLensFacing();
     method public android.util.Size getResolution();
     method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
     method public void markSurfaceSafeToRelease();
   }
 
+  public static final class ViewfinderSurfaceRequest.Builder {
+    ctor public ViewfinderSurfaceRequest.Builder(android.util.Size);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest build();
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setLensFacing(int);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setSensorOrientation(int);
+  }
+
+  public final class ViewfinderSurfaceRequestUtil {
+    method @RequiresApi(21) public static androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder populateFromCharacteristics(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder, android.hardware.camera2.CameraCharacteristics cameraCharacteristics);
+  }
+
 }
 
diff --git a/camera/camera-viewfinder/api/res-1.1.0-beta04.txt b/camera/camera-viewfinder/api/res-1.1.0-beta04.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-viewfinder/api/res-1.1.0-beta04.txt
+++ /dev/null
diff --git a/camera/camera-viewfinder/api/res-1.2.0-beta02.txt b/camera/camera-viewfinder/api/res-1.2.0-beta02.txt
deleted file mode 100644
index e69de29..0000000
--- a/camera/camera-viewfinder/api/res-1.2.0-beta02.txt
+++ /dev/null
diff --git a/camera/camera-viewfinder/api/restricted_1.1.0-beta03.txt b/camera/camera-viewfinder/api/restricted_1.1.0-beta03.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder/api/restricted_1.1.0-beta03.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder/api/restricted_1.1.0-beta04.txt b/camera/camera-viewfinder/api/restricted_1.1.0-beta04.txt
deleted file mode 100644
index e6f50d0..0000000
--- a/camera/camera-viewfinder/api/restricted_1.1.0-beta04.txt
+++ /dev/null
@@ -1 +0,0 @@
-// Signature format: 4.0
diff --git a/camera/camera-viewfinder/api/restricted_1.2.0-beta01.txt b/camera/camera-viewfinder/api/restricted_1.2.0-beta01.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/restricted_1.2.0-beta01.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/restricted_1.2.0-beta02.txt b/camera/camera-viewfinder/api/restricted_1.2.0-beta02.txt
deleted file mode 100644
index d452929..0000000
--- a/camera/camera-viewfinder/api/restricted_1.2.0-beta02.txt
+++ /dev/null
@@ -1,41 +0,0 @@
-// Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
-
diff --git a/camera/camera-viewfinder/api/restricted_1.2.0-beta03.txt b/camera/camera-viewfinder/api/restricted_1.2.0-beta03.txt
index d452929..04cecbd 100644
--- a/camera/camera-viewfinder/api/restricted_1.2.0-beta03.txt
+++ b/camera/camera-viewfinder/api/restricted_1.2.0-beta03.txt
@@ -1,41 +1,2 @@
 // Signature format: 4.0
-package androidx.camera.viewfinder {
-
-  @RequiresApi(21) public final class CameraViewfinder extends android.widget.FrameLayout {
-    ctor @UiThread public CameraViewfinder(android.content.Context);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int);
-    ctor @UiThread public CameraViewfinder(android.content.Context, android.util.AttributeSet?, int, int);
-    method @UiThread public android.graphics.Bitmap? getBitmap();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
-    method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
-    method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
-    method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ImplementationMode {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode COMPATIBLE;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ImplementationMode PERFORMANCE;
-  }
-
-  @RequiresApi(21) public enum CameraViewfinder.ScaleType {
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FILL_START;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_CENTER;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_END;
-    enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
-  }
-
-  @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
-    method public android.util.Size getResolution();
-    method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
-    method public void markSurfaceSafeToRelease();
-  }
-
-}
 
diff --git a/camera/camera-viewfinder/api/restricted_current.txt b/camera/camera-viewfinder/api/restricted_current.txt
index d452929..48504db 100644
--- a/camera/camera-viewfinder/api/restricted_current.txt
+++ b/camera/camera-viewfinder/api/restricted_current.txt
@@ -10,7 +10,6 @@
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode getImplementationMode();
     method @UiThread public androidx.camera.viewfinder.CameraViewfinder.ScaleType getScaleType();
     method @UiThread public com.google.common.util.concurrent.ListenableFuture<android.view.Surface!> requestSurfaceAsync(androidx.camera.viewfinder.ViewfinderSurfaceRequest);
-    method @UiThread public void setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
     method @UiThread public void setScaleType(androidx.camera.viewfinder.CameraViewfinder.ScaleType);
   }
 
@@ -28,14 +27,30 @@
     enum_constant public static final androidx.camera.viewfinder.CameraViewfinder.ScaleType FIT_START;
   }
 
+  @RequiresApi(21) public final class CameraViewfinderExt {
+    method public suspend Object? requestSurface(androidx.camera.viewfinder.CameraViewfinder, androidx.camera.viewfinder.ViewfinderSurfaceRequest viewfinderSurfaceRequest, kotlin.coroutines.Continuation<? super android.view.Surface>);
+    field public static final androidx.camera.viewfinder.CameraViewfinderExt INSTANCE;
+  }
+
   @RequiresApi(21) public class ViewfinderSurfaceRequest {
-    ctor public ViewfinderSurfaceRequest(android.util.Size, android.hardware.camera2.CameraCharacteristics);
+    method public androidx.camera.viewfinder.CameraViewfinder.ImplementationMode? getImplementationMode();
+    method public int getLensFacing();
     method public android.util.Size getResolution();
     method public int getSensorOrientation();
-    method public boolean isFrontCamera();
-    method public boolean isLegacyDevice();
     method public void markSurfaceSafeToRelease();
   }
 
+  public static final class ViewfinderSurfaceRequest.Builder {
+    ctor public ViewfinderSurfaceRequest.Builder(android.util.Size);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest build();
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setImplementationMode(androidx.camera.viewfinder.CameraViewfinder.ImplementationMode);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setLensFacing(int);
+    method public androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder setSensorOrientation(int);
+  }
+
+  public final class ViewfinderSurfaceRequestUtil {
+    method @RequiresApi(21) public static androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder populateFromCharacteristics(androidx.camera.viewfinder.ViewfinderSurfaceRequest.Builder, android.hardware.camera2.CameraCharacteristics cameraCharacteristics);
+  }
+
 }
 
diff --git a/camera/camera-viewfinder/build.gradle b/camera/camera-viewfinder/build.gradle
index 449b256..ea7bfde 100644
--- a/camera/camera-viewfinder/build.gradle
+++ b/camera/camera-viewfinder/build.gradle
@@ -29,6 +29,7 @@
     implementation(libs.guavaListenableFuture)
     implementation("androidx.core:core:1.3.2")
     implementation("androidx.concurrent:concurrent-futures:1.0.0")
+    implementation(project(":concurrent:concurrent-futures-ktx"))
     implementation(libs.autoValueAnnotations)
     implementation("androidx.appcompat:appcompat:1.1.0")
     implementation("androidx.test.espresso:espresso-idling-resource:3.1.0")
diff --git a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
index 67ea991..25b8313 100644
--- a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
+++ b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/CameraViewfinderBitmapTest.kt
@@ -21,8 +21,6 @@
 import android.hardware.camera2.CameraManager
 import android.util.Size
 import android.view.Surface
-import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode.COMPATIBLE
-import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode.PERFORMANCE
 import androidx.camera.viewfinder.CameraViewfinder.ScaleType.FILL_CENTER
 import androidx.camera.viewfinder.internal.utils.futures.FutureCallback
 import androidx.camera.viewfinder.internal.utils.futures.Futures
@@ -79,7 +77,9 @@
         Assume.assumeTrue("No cameras found on device.", cameraIds.isNotEmpty())
         val cameraId = cameraIds[0]
         val characteristics = cameraManager.getCameraCharacteristics(cameraId)
-        mSurfaceRequest = ViewfinderSurfaceRequest(ANY_SIZE, characteristics)
+        mSurfaceRequest = ViewfinderSurfaceRequest.Builder(ANY_SIZE)
+            .populateFromCharacteristics(characteristics)
+            .build()
     }
 
     @After
@@ -90,7 +90,7 @@
     @Throws(Throwable::class)
     fun bitmapNotNull_whenViewfinderIsDisplaying_surfaceView() {
         // Arrange
-        val viewfinder: CameraViewfinder = setUpViewfinder(PERFORMANCE, FILL_CENTER)
+        val viewfinder: CameraViewfinder = setUpViewfinder(FILL_CENTER)
 
         // assert
         runOnMainThread(Runnable {
@@ -119,7 +119,7 @@
     @Throws(Throwable::class)
     fun bitmapNotNull_whenViewfinderIsDisplaying_textureView() {
         // Arrange
-        val viewfinder: CameraViewfinder = setUpViewfinder(COMPATIBLE, FILL_CENTER)
+        val viewfinder: CameraViewfinder = setUpViewfinder(FILL_CENTER)
 
         // assert
         runOnMainThread(Runnable {
@@ -145,7 +145,6 @@
     }
 
     private fun setUpViewfinder(
-        mode: CameraViewfinder.ImplementationMode,
         scaleType: CameraViewfinder.ScaleType
     ): CameraViewfinder {
         val viewfinderAtomicReference: AtomicReference<CameraViewfinder> =
@@ -153,7 +152,6 @@
         runOnMainThread {
             val viewfiner =
                 CameraViewfinder(ApplicationProvider.getApplicationContext<Context>())
-            viewfiner.setImplementationMode(mode)
             viewfiner.setScaleType(scaleType)
             mActivityRule.getScenario().onActivity(
                 ActivityAction<FakeActivity> { activity: FakeActivity ->
diff --git a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/SurfaceViewImplementationTest.kt b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/SurfaceViewImplementationTest.kt
index 16e57c2..7e0012c 100644
--- a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/SurfaceViewImplementationTest.kt
+++ b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/SurfaceViewImplementationTest.kt
@@ -72,7 +72,9 @@
         Assume.assumeTrue("No cameras found on device.", cameraIds.isNotEmpty())
         val cameraId = cameraIds[0]
         val characteristics = cameraManager.getCameraCharacteristics(cameraId)
-        mSurfaceRequest = ViewfinderSurfaceRequest(ANY_SIZE, characteristics)
+        mSurfaceRequest = ViewfinderSurfaceRequest.Builder(ANY_SIZE)
+            .populateFromCharacteristics(characteristics)
+            .build()
         mImplementation = SurfaceViewImplementation(mParent, ViewfinderTransformation())
     }
 
diff --git a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
index 3b6bae6..dbb8587 100644
--- a/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
+++ b/camera/camera-viewfinder/src/androidTest/java/androidx/camera/viewfinder/TextureViewImplementationTest.kt
@@ -56,7 +56,9 @@
                 val cameraId = cameraIds[0]
                 val characteristics = cameraManager.getCameraCharacteristics(cameraId)
                 _surfaceRequest =
-                    ViewfinderSurfaceRequest(ANY_SIZE, characteristics)
+                    ViewfinderSurfaceRequest.Builder(ANY_SIZE)
+                        .populateFromCharacteristics(characteristics)
+                        .build()
             }
             return _surfaceRequest!!
         }
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
index 03c4488..9a438c0 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinder.java
@@ -41,6 +41,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.UiThread;
+import androidx.annotation.VisibleForTesting;
 import androidx.camera.viewfinder.internal.quirk.DeviceQuirks;
 import androidx.camera.viewfinder.internal.quirk.SurfaceViewNotCroppedByParentQuirk;
 import androidx.camera.viewfinder.internal.quirk.SurfaceViewStretchedQuirk;
@@ -79,7 +80,7 @@
     @NonNull
     private final Looper mRequiredLooper = Looper.myLooper();
 
-    @NonNull ImplementationMode mImplementationMode = DEFAULT_IMPL_MODE;
+    @NonNull ImplementationMode mImplementationMode;
 
     // Synthetic access
     @SuppressWarnings("WeakerAccess")
@@ -116,8 +117,11 @@
             }
             Logger.d(TAG, "Surface requested by Viewfinder.");
 
-            mImplementation = shouldUseTextureView(
-                    surfaceRequest.isLegacyDevice(), mImplementationMode)
+            if (surfaceRequest.getImplementationMode() != null) {
+                mImplementationMode = surfaceRequest.getImplementationMode();
+            }
+
+            mImplementation = shouldUseTextureView(mImplementationMode)
                     ? new TextureViewImplementation(
                             CameraViewfinder.this, mViewfinderTransformation)
                     : new SurfaceViewImplementation(
@@ -130,10 +134,12 @@
                 mViewfinderTransformation.setTransformationInfo(
                         createTransformInfo(surfaceRequest.getResolution(),
                                 display,
-                                surfaceRequest.isFrontCamera(),
+                                surfaceRequest.getLensFacing()
+                                        == CameraCharacteristics.LENS_FACING_FRONT,
                                 surfaceRequest.getSensorOrientation()),
                         surfaceRequest.getResolution(),
-                        surfaceRequest.isFrontCamera());
+                        surfaceRequest.getLensFacing()
+                                == CameraCharacteristics.LENS_FACING_FRONT);
                 redrawViewfinder();
             }
         }
@@ -176,7 +182,7 @@
             int implementationModeId =
                     attributes.getInteger(R.styleable.Viewfinder_implementationMode,
                             DEFAULT_IMPL_MODE.getId());
-            setImplementationMode(ImplementationMode.fromId(implementationModeId));
+            mImplementationMode = ImplementationMode.fromId(implementationModeId);
         } finally {
             attributes.recycle();
         }
@@ -189,36 +195,15 @@
     }
 
     /**
-     * Sets the {@link ImplementationMode} for the {@link CameraViewfinder}.
-     *
-     * <p> This value can also be set in the layout XML file via the {@code app:implementationMode}
-     * attribute.
-     *
-     * <p> {@link CameraViewfinder} displays the viewfinder with a {@link TextureView} when the
-     * mode is {@link ImplementationMode#COMPATIBLE}, and tries to use a {@link SurfaceView} if
-     * it is {@link ImplementationMode#PERFORMANCE} when possible, which depends on the device's
-     * attributes (e.g. API level). If not set, the default mode is
-     * {@link ImplementationMode#PERFORMANCE}.
-     *
-     * <p> This method should be called after {@link CameraViewfinder} is inflated and before
-     * {@link CameraViewfinder#requestSurfaceAsync(ViewfinderSurfaceRequest)}. If a new
-     * {@link ImplementationMode} is set, the capture session needs to be recreated and new
-     * surface request needs to be sent to make it effective.
-     *
-     * @param implementationMode The {@link ImplementationMode} to apply to the viewfinder.
-     * @attr name app:implementationMode
-     */
-    @UiThread
-    public void setImplementationMode(@NonNull final ImplementationMode implementationMode) {
-        checkUiThread();
-        mImplementationMode = implementationMode;
-    }
-
-    /**
      * Returns the {@link ImplementationMode}.
      *
-     * <p> If nothing is set via {@link #setImplementationMode}, the default
-     * value is {@link ImplementationMode#PERFORMANCE}.
+     * <p> For each {@link ViewfinderSurfaceRequest} sent to {@link CameraViewfinder}, the
+     * {@link ImplementationMode} set in the {@link ViewfinderSurfaceRequest} will be used first.
+     * If it's not set, the {@code app:implementationMode} in the layout xml will be used. If
+     * it's not set in the layout xml, the default value {@link ImplementationMode#PERFORMANCE}
+     * will be used. Each {@link ViewfinderSurfaceRequest sent to {@link CameraViewfinder} can
+     * override the {@link ImplementationMode} once it has set the
+     * {@link ImplementationMode}.
      *
      * @return The {@link ImplementationMode} for {@link CameraViewfinder}.
      */
@@ -376,16 +361,15 @@
         stopListeningToDisplayChange();
     }
 
-    // Synthetic access
-    @SuppressWarnings("WeakerAccess")
-    static boolean shouldUseTextureView(
-            boolean isLegacyDevice,
-            @NonNull final ImplementationMode implementationMode) {
+    @VisibleForTesting
+    static boolean shouldUseTextureView(@NonNull final ImplementationMode implementationMode) {
         boolean hasSurfaceViewQuirk = DeviceQuirks.get(SurfaceViewStretchedQuirk.class) != null
                 ||  DeviceQuirks.get(SurfaceViewNotCroppedByParentQuirk.class) != null;
-        if (Build.VERSION.SDK_INT <= 24 || isLegacyDevice || hasSurfaceViewQuirk) {
+        if (Build.VERSION.SDK_INT <= 24 || hasSurfaceViewQuirk) {
             // Force to use TextureView when the device is running android 7.0 and below, legacy
             // level or SurfaceView has quirks.
+            Logger.d(TAG, "Implementation mode to set is not supported, forcing to use "
+                    + "TextureView, because transform APIs are not supported on these devices.");
             return true;
         }
         switch (implementationMode) {
@@ -624,7 +608,8 @@
                     mViewfinderTransformation.updateTransformInfo(
                             createTransformInfo(surfaceRequest.getResolution(),
                                     display,
-                                    surfaceRequest.isFrontCamera(),
+                                    surfaceRequest.getLensFacing()
+                                            == CameraCharacteristics.LENS_FACING_FRONT,
                                     surfaceRequest.getSensorOrientation()));
                     redrawViewfinder();
                 }
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinderExt.kt b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinderExt.kt
new file mode 100644
index 0000000..1193fc1
--- /dev/null
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/CameraViewfinderExt.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.camera.viewfinder
+
+import android.view.Surface
+import androidx.annotation.RequiresApi
+import androidx.concurrent.futures.await
+
+/**
+ * Provides a suspending function of [CameraViewfinder.requestSurfaceAsync] to request
+ * a [Surface] by sending a [ViewfinderSurfaceRequest].
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+object CameraViewfinderExt {
+    suspend fun CameraViewfinder.requestSurface(
+        viewfinderSurfaceRequest: ViewfinderSurfaceRequest
+    ): Surface = requestSurfaceAsync(viewfinderSurfaceRequest).await()
+}
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
index e0eb047..b23887a8 100644
--- a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequest.java
@@ -16,6 +16,10 @@
 
 package androidx.camera.viewfinder;
 
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_BACK;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_EXTERNAL;
+import static android.hardware.camera2.CameraMetadata.LENS_FACING_FRONT;
+
 import android.annotation.SuppressLint;
 import android.graphics.SurfaceTexture;
 import android.hardware.camera2.CameraCharacteristics;
@@ -31,6 +35,7 @@
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
+import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode;
 import androidx.camera.viewfinder.internal.surface.ViewfinderSurface;
 import androidx.camera.viewfinder.internal.utils.Logger;
 import androidx.camera.viewfinder.internal.utils.executor.CameraExecutors;
@@ -68,62 +73,39 @@
 
     private static final String TAG = "ViewfinderSurfaceRequest";
 
-    private final boolean mIsLegacyDevice;
-    private final boolean mIsFrontCamera;
-    private final int mSensorOrientation;
     @NonNull private final Size mResolution;
     @NonNull private final ViewfinderSurface mInternalViewfinderSurface;
     @NonNull private final CallbackToFutureAdapter.Completer<Void> mRequestCancellationCompleter;
     @NonNull private final ListenableFuture<Void> mSessionStatusFuture;
     @NonNull private final CallbackToFutureAdapter.Completer<Surface> mSurfaceCompleter;
-
+    @LensFacingValue private int mLensFacing;
+    @SensorOrientationDegreesValue private int mSensorOrientation;
+    @Nullable
+    private ImplementationMode mImplementationMode;
     @SuppressWarnings("WeakerAccess") /*synthetic accessor */
     @NonNull
     final ListenableFuture<Surface> mSurfaceFuture;
 
     /**
-     * Creates a new surface request with surface resolution and camera characteristics.
-     *
-     * <p>The resolution given here will be the default resolution of the Surface returned by
-     * {@link CameraViewfinder#requestSurfaceAsync(ViewfinderSurfaceRequest)}, which can then be
-     * passed to the camera API to set the camera viewfinder resolution.
-     *
-     * @param resolution The requested surface resolution.
-     * @param cameraCharacteristics The {@link CameraCharacteristics} to get device information
-     *                              e.g. hardware level, lens facing, sensor orientation, etc,.
-     */
-    public ViewfinderSurfaceRequest(
-            @NonNull Size resolution,
-            @NonNull CameraCharacteristics cameraCharacteristics) {
-        this(resolution,
-                cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
-                        == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY,
-                cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)
-                        == CameraCharacteristics.LENS_FACING_FRONT,
-                cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION));
-    }
-
-    /**
-     * Creates a new surface request with surface resolution, view display and camera device
-     * information.
+     * Creates a new surface request with surface resolution, camera device, lens facing and
+     * sensor orientation information.
      *
      * @param resolution The requested surface resolution. It is the output surface size
      *                   the camera is configured with, instead of {@link CameraViewfinder}
      *                   view size.
-     * {@link CameraViewfinder} view
-     * @param isLegacyDevice The device hardware level is legacy or not.
-     * @param isFrontCamera The camera is front facing or not.
+     * @param lensFacing The camera lens facing.
      * @param sensorOrientation THe camera sensor orientation.
+     * @param implementationMode The {@link ImplementationMode} to apply to the viewfinder.
      */
-    private ViewfinderSurfaceRequest(
+    ViewfinderSurfaceRequest(
             @NonNull Size resolution,
-            boolean isLegacyDevice,
-            boolean isFrontCamera,
-            int sensorOrientation) {
+            @LensFacingValue int lensFacing,
+            @SensorOrientationDegreesValue int sensorOrientation,
+            @Nullable ImplementationMode implementationMode) {
         mResolution = resolution;
-        mIsLegacyDevice = isLegacyDevice;
-        mIsFrontCamera = isFrontCamera;
+        mLensFacing = lensFacing;
         mSensorOrientation = sensorOrientation;
+        mImplementationMode = implementationMode;
 
         // To ensure concurrency and ordering, operations are chained. Completion can only be
         // triggered externally by the top-level completer (mSurfaceCompleter). The other future
@@ -164,7 +146,7 @@
             }
 
             @Override
-            public void onFailure(Throwable t) {
+            public void onFailure(@NonNull Throwable t) {
                 if (t instanceof RequestCancelledException) {
                     // Cancellation occurred. Notify listeners.
                     Preconditions.checkState(requestCancellationFuture.cancel(false));
@@ -259,6 +241,8 @@
     /**
      * Returns the resolution of the requested {@link Surface}.
      *
+     * <p>The value is set by {@link Builder#Builder(Size)}.
+     *
      * The surface which fulfills this request must have the resolution specified here in
      * order to fulfill the resource requirements of the camera.
      *
@@ -273,28 +257,40 @@
     /**
      * Returns the sensor orientation.
      *
+     * <p>The value is set by {@link Builder#setSensorOrientation(int)}, which can be retrieved from
+     * {@link CameraCharacteristics} by key {@link CameraCharacteristics#SENSOR_ORIENTATION}.
+     *
      * @return The sensor orientation.
      */
+    @SensorOrientationDegreesValue
     public int getSensorOrientation() {
         return mSensorOrientation;
     }
 
     /**
-     * Returns the status of camera lens facing.
+     * Returns the camera lens facing.
      *
-     * @return True if front camera, otherwise false.
+     * <p>The value is set by {@link Builder#setLensFacing(int)}, which can be retrieved from
+     * {@link CameraCharacteristics} by key {@link CameraCharacteristics#LENS_FACING}.
+     *
+     * @return The lens facing.
      */
-    public boolean isFrontCamera() {
-        return mIsFrontCamera;
+    @LensFacingValue
+    public int getLensFacing() {
+        return mLensFacing;
     }
 
     /**
-     * Returns the status of camera hardware level.
+     * Returns the {@link ImplementationMode}.
      *
-     * @return True if legacy device, otherwise false.
+     * <p>The value is set by {@link Builder#setImplementationMode(ImplementationMode)}.
+     *
+     * @return {@link ImplementationMode}. The value will be null if it's not set via
+     * {@link Builder#setImplementationMode(ImplementationMode)}.
      */
-    public boolean isLegacyDevice() {
-        return mIsLegacyDevice;
+    @Nullable
+    public ImplementationMode getImplementationMode() {
+        return mImplementationMode;
     }
 
     /**
@@ -362,7 +358,7 @@
                 }
 
                 @Override
-                public void onFailure(Throwable t) {
+                public void onFailure(@NonNull Throwable t) {
                     Preconditions.checkState(t instanceof RequestCancelledException, "Camera "
                             + "surface session should only fail with request "
                             + "cancellation. Instead failed due to:\n" + t);
@@ -414,6 +410,114 @@
                         + "will not complete."));
     }
 
+    /**
+     * Builder for {@link ViewfinderSurfaceRequest}.
+     */
+    public static final class Builder {
+
+        @NonNull private final Size mResolution;
+        @LensFacingValue private int mLensFacing = LENS_FACING_BACK;
+        @SensorOrientationDegreesValue private int mSensorOrientation = 0;
+        @Nullable private ImplementationMode mImplementationMode;
+
+        public Builder(@NonNull Size resolution) {
+            mResolution = resolution;
+        }
+
+        /**
+         * Sets the {@link ImplementationMode}.
+         *
+         * <p><b>Possible values:</b></p>
+         * <ul>
+         *   <li>{@link ImplementationMode#PERFORMANCE PERFORMANCE}</li>
+         *   <li>{@link ImplementationMode#COMPATIBLE COMPATIBLE}</li>
+         * </ul>
+         *
+         * <p>If not set, the {@link ImplementationMode} set via {@code app:implementationMode} in
+         * layout xml will be used for {@link CameraViewfinder}. If not set in the layout xml,
+         * the default value {@link ImplementationMode#PERFORMANCE} will be used in
+         * {@link CameraViewfinder}.
+         *
+         * @param implementationMode The {@link ImplementationMode}.
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setImplementationMode(@NonNull ImplementationMode implementationMode) {
+            mImplementationMode = implementationMode;
+            return this;
+        }
+
+        /**
+         * Sets the lens facing.
+         *
+         * <p><b>Possible values:</b></p>
+         * <ul>
+         *   <li>{@link CameraMetadata#LENS_FACING_FRONT FRONT}</li>
+         *   <li>{@link CameraMetadata#LENS_FACING_BACK BACK}</li>
+         *   <li>{@link CameraMetadata#LENS_FACING_EXTERNAL EXTERNAL}</li>
+         * </ul>
+         *
+         * <p>The value can be retrieved from {@link CameraCharacteristics} by key
+         * {@link CameraCharacteristics#LENS_FACING}. If not set,
+         * {@link CameraMetadata#LENS_FACING_BACK} will be used by default.
+         *
+         * @param lensFacing The lens facing.
+         * @return This builder.
+         */
+        @NonNull
+        public Builder setLensFacing(@LensFacingValue int lensFacing) {
+            mLensFacing = lensFacing;
+            return this;
+        }
+
+        /**
+         * Sets the sensor orientation.
+         *
+         * <p><b>Range of valid values:</b><br>
+         * 0, 90, 180, 270</p>
+         *
+         * <p>The value can be retrieved from {@link CameraCharacteristics} by key
+         * {@link CameraCharacteristics#SENSOR_ORIENTATION}. If it is not
+         * set, 0 will be used by default.
+         *
+         * @param sensorOrientation
+         * @return this builder.
+         */
+        @NonNull
+        public Builder setSensorOrientation(@SensorOrientationDegreesValue int sensorOrientation) {
+            mSensorOrientation = sensorOrientation;
+            return this;
+        }
+
+        /**
+         * Builds the {@link ViewfinderSurfaceRequest}.
+         * @return the instance of {@link ViewfinderSurfaceRequest}.
+         */
+        @NonNull
+        public ViewfinderSurfaceRequest build() {
+            if (mLensFacing != LENS_FACING_FRONT
+                    && mLensFacing != LENS_FACING_BACK
+                    && mLensFacing != LENS_FACING_EXTERNAL) {
+                throw new IllegalArgumentException("Lens facing value: " + mLensFacing + " is "
+                        + "invalid");
+            }
+
+            if (mSensorOrientation != 0
+                    && mSensorOrientation != 90
+                    && mSensorOrientation != 180
+                    && mSensorOrientation != 270) {
+                throw new IllegalArgumentException("Sensor orientation value: "
+                        + mSensorOrientation + " is invalid");
+            }
+
+            return new ViewfinderSurfaceRequest(
+                    mResolution,
+                    mLensFacing,
+                    mSensorOrientation,
+                    mImplementationMode);
+        }
+    }
+
     static final class RequestCancelledException extends RuntimeException {
         RequestCancelledException(@NonNull String message, @NonNull Throwable cause) {
             super(message, cause);
@@ -541,4 +645,20 @@
         Result() {
         }
     }
+
+    /**
+     * Valid integer sensor orientation degrees values.
+     */
+    @IntDef({0, 90, 180, 270})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface SensorOrientationDegreesValue {
+    }
+
+    /**
+     * Valid integer sensor orientation degrees values.
+     */
+    @IntDef({LENS_FACING_FRONT, LENS_FACING_BACK, LENS_FACING_EXTERNAL})
+    @Retention(RetentionPolicy.SOURCE)
+    @interface LensFacingValue {
+    }
 }
diff --git a/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequestExt.kt b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequestExt.kt
new file mode 100644
index 0000000..4b8112e
--- /dev/null
+++ b/camera/camera-viewfinder/src/main/java/androidx/camera/viewfinder/ViewfinderSurfaceRequestExt.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.
+ */
+@file:JvmName("ViewfinderSurfaceRequestUtil")
+
+package androidx.camera.viewfinder
+
+import android.annotation.SuppressLint
+import android.hardware.camera2.CameraCharacteristics
+import android.hardware.camera2.CameraMetadata
+import androidx.annotation.RequiresApi
+import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode
+
+/**
+ * Populates [ViewfinderSurfaceRequest.Builder] from [CameraCharacteristics].
+ *
+ * <p>The [CameraCharacteristics] will be used to populate information including lens facing,
+ * sensor orientation and [ImplementationMode]. If the hardware level is legacy,
+ * the [ImplementationMode] will be set to [ImplementationMode.COMPATIBLE].
+ */
+@SuppressLint("ClassVerificationFailure")
+@RequiresApi(21)
+fun ViewfinderSurfaceRequest.Builder.populateFromCharacteristics(
+    cameraCharacteristics: CameraCharacteristics
+): ViewfinderSurfaceRequest.Builder {
+    setLensFacing(cameraCharacteristics.get(CameraCharacteristics.LENS_FACING)!!)
+    setSensorOrientation(
+        cameraCharacteristics.get(CameraCharacteristics.SENSOR_ORIENTATION)!!)
+    if (cameraCharacteristics.get(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL)
+        == CameraMetadata.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY) {
+        setImplementationMode(ImplementationMode.COMPATIBLE)
+    }
+    return this
+}
\ No newline at end of file
diff --git a/camera/camera-viewfinder/src/test/java/androidx/camera/viewfinder/CameraViewfinderTest.java b/camera/camera-viewfinder/src/test/java/androidx/camera/viewfinder/CameraViewfinderTest.java
index 897fcde0..2528c8d 100644
--- a/camera/camera-viewfinder/src/test/java/androidx/camera/viewfinder/CameraViewfinderTest.java
+++ b/camera/camera-viewfinder/src/test/java/androidx/camera/viewfinder/CameraViewfinderTest.java
@@ -46,7 +46,6 @@
     public void surfaceViewNormal_useSurfaceView() {
         // Assert: SurfaceView is used.
         assertThat(CameraViewfinder.shouldUseTextureView(
-                /* isLegacyDevice = */ false,
                 CameraViewfinder.ImplementationMode.PERFORMANCE)).isFalse();
     }
 
@@ -57,7 +56,6 @@
 
         // Assert: TextureView is used even the SurfaceRequest is compatible with SurfaceView.
         assertThat(CameraViewfinder.shouldUseTextureView(
-                /* isLegacyDevice = */ false,
                 CameraViewfinder.ImplementationMode.PERFORMANCE)).isTrue();
     }
 
@@ -68,7 +66,6 @@
 
         // Assert: TextureView is used even the SurfaceRequest is compatible with SurfaceView.
         assertThat(CameraViewfinder.shouldUseTextureView(
-                /* isLegacyDevice = */ false,
                 CameraViewfinder.ImplementationMode.PERFORMANCE)).isTrue();
     }
 
@@ -79,7 +76,6 @@
 
         // Assert: TextureView is used even the SurfaceRequest is compatible with SurfaceView.
         assertThat(CameraViewfinder.shouldUseTextureView(
-                /* isLegacyDevice = */ true,
                 CameraViewfinder.ImplementationMode.COMPATIBLE)).isTrue();
     }
 }
diff --git a/camera/integration-tests/viewfindertestapp/src/main/java/androidx/camera/integration/viewfinder/CameraViewfinderFoldableFragment.kt b/camera/integration-tests/viewfindertestapp/src/main/java/androidx/camera/integration/viewfinder/CameraViewfinderFoldableFragment.kt
index 22a0b7c..cc628ac 100644
--- a/camera/integration-tests/viewfindertestapp/src/main/java/androidx/camera/integration/viewfinder/CameraViewfinderFoldableFragment.kt
+++ b/camera/integration-tests/viewfindertestapp/src/main/java/androidx/camera/integration/viewfinder/CameraViewfinderFoldableFragment.kt
@@ -45,6 +45,7 @@
 import android.os.HandlerThread
 import android.provider.MediaStore
 import android.util.Log
+import android.util.Size
 import android.view.LayoutInflater
 import android.view.Menu
 import android.view.MenuInflater
@@ -52,13 +53,16 @@
 import android.view.Surface
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewTreeObserver
 import android.widget.Toast
 import androidx.appcompat.app.AlertDialog
 import androidx.camera.core.impl.utils.CompareSizesByArea
-import androidx.camera.core.impl.utils.futures.FutureCallback
-import androidx.camera.core.impl.utils.futures.Futures
 import androidx.camera.viewfinder.CameraViewfinder
+import androidx.camera.viewfinder.CameraViewfinder.ImplementationMode
+import androidx.camera.viewfinder.CameraViewfinder.ScaleType
+import androidx.camera.viewfinder.CameraViewfinderExt.requestSurface
 import androidx.camera.viewfinder.ViewfinderSurfaceRequest
+import androidx.camera.viewfinder.populateFromCharacteristics
 import androidx.core.app.ActivityCompat
 import androidx.core.content.ContextCompat
 import androidx.fragment.app.DialogFragment
@@ -70,18 +74,15 @@
 import androidx.window.layout.WindowInfoTracker
 import androidx.window.layout.WindowLayoutInfo
 import com.google.common.base.Objects
-import com.google.common.util.concurrent.ListenableFuture
 import java.io.Closeable
 import java.io.File
 import java.io.FileOutputStream
 import java.io.IOException
 import java.text.SimpleDateFormat
-import java.util.Arrays
 import java.util.Collections
 import java.util.Date
 import java.util.Locale
 import java.util.concurrent.ArrayBlockingQueue
-import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeoutException
 import kotlin.coroutines.resume
 import kotlin.coroutines.resumeWithException
@@ -89,6 +90,7 @@
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
+import kotlinx.coroutines.sync.Mutex
 import kotlinx.coroutines.withContext
 
 /**
@@ -97,53 +99,55 @@
 class CameraViewfinderFoldableFragment : Fragment(), View.OnClickListener,
     ActivityCompat.OnRequestPermissionsResultCallback {
 
-    private lateinit var cameraThread: HandlerThread
-
-    private lateinit var cameraHandler: Handler
-
-    private lateinit var imageReaderThread: HandlerThread
-
-    private lateinit var imageReaderHandler: Handler
-
-    private val cameraOpenCloseLock = Semaphore(1)
+    private val cameraOpenCloseLock = Mutex()
 
     private val onImageAvailableListener = ImageReader.OnImageAvailableListener {
-        cameraHandler.post(
+        cameraHandler?.post(
             ImageSaver(
                 it.acquireNextImage(),
-                file
+                checkNotNull(file) { "file cannot be null when saving image" }
             )
         )
     }
 
-    private lateinit var camera: CameraDevice
-
-    private lateinit var characteristics: CameraCharacteristics
-
-    private lateinit var cameraId: String
-
     private lateinit var cameraManager: CameraManager
 
     private lateinit var cameraViewfinder: CameraViewfinder
 
-    private lateinit var file: File
-
-    private lateinit var imageReader: ImageReader
-
-    private lateinit var relativeOrientation: OrientationLiveData
-
-    private lateinit var session: CameraCaptureSession
-
-    private lateinit var surfaceListenableFuture: ListenableFuture<Surface>
-
     private lateinit var windowInfoTracker: WindowInfoTracker
 
+    private var cameraThread: HandlerThread? = null
+
+    private var cameraHandler: Handler? = null
+
+    private var imageReaderThread: HandlerThread? = null
+
+    private var imageReaderHandler: Handler? = null
+
+    private var camera: CameraDevice? = null
+
+    private var characteristics: CameraCharacteristics? = null
+
+    private var cameraId: String? = null
+
+    private var file: File? = null
+
+    private var imageReader: ImageReader? = null
+
+    private var relativeOrientation: OrientationLiveData? = null
+
+    private var session: CameraCaptureSession? = null
+
     private var activeWindowLayoutInfo: WindowLayoutInfo? = null
 
     private var isViewfinderInLeftTop = true
 
     private var viewfinderSurfaceRequest: ViewfinderSurfaceRequest? = null
 
+    private var resolution: Size? = null
+
+    private var layoutChangedListener: ViewTreeObserver.OnGlobalLayoutListener? = null
+
     @Deprecated("Deprecated in Java")
     @Suppress("DEPRECATION")
     override fun onCreate(savedInstanceState: Bundle?) {
@@ -171,19 +175,22 @@
     override fun onOptionsItemSelected(item: MenuItem): Boolean {
         when (item.itemId) {
             R.id.implementationMode -> {
-                cameraViewfinder.implementationMode =
+                val implementationMode =
                     when (cameraViewfinder.implementationMode) {
-                        CameraViewfinder.ImplementationMode.PERFORMANCE ->
-                            CameraViewfinder.ImplementationMode.COMPATIBLE
-                        else -> CameraViewfinder.ImplementationMode.PERFORMANCE
+                        ImplementationMode.PERFORMANCE ->
+                            ImplementationMode.COMPATIBLE
+                        else -> ImplementationMode.PERFORMANCE
                     }
-                closeCamera()
-                sendSurfaceRequest(false)
+
+                lifecycleScope.launch {
+                    closeCamera()
+                    sendSurfaceRequest(implementationMode, false)
+                }
             }
-            R.id.fitCenter -> cameraViewfinder.scaleType = CameraViewfinder.ScaleType.FIT_CENTER
-            R.id.fillCenter -> cameraViewfinder.scaleType = CameraViewfinder.ScaleType.FILL_CENTER
-            R.id.fitStart -> cameraViewfinder.scaleType = CameraViewfinder.ScaleType.FIT_START
-            R.id.fitEnd -> cameraViewfinder.scaleType = CameraViewfinder.ScaleType.FIT_END
+            R.id.fitCenter -> cameraViewfinder.scaleType = ScaleType.FIT_CENTER
+            R.id.fillCenter -> cameraViewfinder.scaleType = ScaleType.FILL_CENTER
+            R.id.fitStart -> cameraViewfinder.scaleType = ScaleType.FIT_START
+            R.id.fitEnd -> cameraViewfinder.scaleType = ScaleType.FIT_END
         }
         return super.onOptionsItemSelected(item)
     }
@@ -209,9 +216,13 @@
     override fun onResume() {
         super.onResume()
         cameraThread = HandlerThread("CameraThread").apply { start() }
-        cameraHandler = Handler(cameraThread.looper)
+        cameraHandler = Handler(checkNotNull(cameraThread) {
+            "camera thread cannot be null"
+        }.looper)
         imageReaderThread = HandlerThread("ImageThread").apply { start() }
-        imageReaderHandler = Handler(imageReaderThread.looper)
+        imageReaderHandler = Handler(checkNotNull(imageReaderThread) {
+            "image reader thread cannot be null"
+        }.looper)
 
         // Request Permission
         val cameraPermission = activity?.let {
@@ -234,7 +245,13 @@
             }
         }
 
-        sendSurfaceRequest(false)
+        layoutChangedListener = ViewTreeObserver.OnGlobalLayoutListener {
+            cameraViewfinder.viewTreeObserver.removeOnGlobalLayoutListener(layoutChangedListener)
+            layoutChangedListener = null
+
+            sendSurfaceRequest(null, false)
+        }
+        cameraViewfinder.viewTreeObserver.addOnGlobalLayoutListener(layoutChangedListener)
 
         lifecycleScope.launch {
             windowInfoTracker.windowLayoutInfo(requireActivity())
@@ -247,10 +264,12 @@
     }
 
     override fun onPause() {
-        closeCamera()
-        cameraThread.quitSafely()
-        imageReaderThread.quitSafely()
-        viewfinderSurfaceRequest?.markSurfaceSafeToRelease()
+        lifecycleScope.launch {
+            closeCamera()
+            cameraThread?.quitSafely()
+            imageReaderThread?.quitSafely()
+            viewfinderSurfaceRequest?.markSurfaceSafeToRelease()
+        }
         super.onPause()
     }
 
@@ -302,28 +321,20 @@
     }
 
     // ------------- Create Capture Session --------------
-    private fun sendSurfaceRequest(toggleCamera: Boolean) {
-        cameraViewfinder.post {
-            if (isAdded && context != null) {
-                setUpCameraOutputs(toggleCamera)
-
-                val context = requireContext()
-                surfaceListenableFuture =
-                    cameraViewfinder.requestSurfaceAsync(viewfinderSurfaceRequest!!)
-
-                Futures.addCallback(surfaceListenableFuture, object : FutureCallback<Surface?> {
-                    override fun onSuccess(surface: Surface?) {
-                        Log.d(TAG, "request onSurfaceAvailable surface = $surface")
-                        if (surface != null) {
-                            initializeCamera(surface)
-                        }
-                    }
-
-                    override fun onFailure(t: Throwable) {
-                        Log.e(TAG, "request onSurfaceClosed")
-                    }
-                }, ContextCompat.getMainExecutor(context))
+    private fun sendSurfaceRequest(
+        implementationMode: ImplementationMode?,
+        toggleCamera: Boolean
+    ) = lifecycleScope.launch {
+        if (isAdded && context != null) {
+            setUpCameraOutputs(toggleCamera)
+            val builder = ViewfinderSurfaceRequest.Builder(resolution!!)
+                .populateFromCharacteristics(characteristics!!)
+            if (implementationMode != null) {
+                builder.setImplementationMode(implementationMode)
             }
+            viewfinderSurfaceRequest = builder.build()
+            val surface = cameraViewfinder.requestSurface(viewfinderSurfaceRequest!!)
+            initializeCamera(surface)
         }
     }
 
@@ -331,34 +342,44 @@
         try {
             for (cameraId in cameraManager.cameraIdList) {
                 characteristics = cameraManager.getCameraCharacteristics(cameraId)
-                relativeOrientation = OrientationLiveData(requireContext(), characteristics).apply {
+                relativeOrientation = OrientationLiveData(requireContext(),
+                    checkNotNull(characteristics) {
+                        "camera characteristics cannot be null"
+                    }).apply {
                     observe(viewLifecycleOwner, Observer { orientation ->
                         Log.d(TAG, "Orientation changed: $orientation")
                     })
                 }
 
-                val facing = characteristics.get(CameraCharacteristics.LENS_FACING)
+                val facing = checkNotNull(characteristics) {
+                    "camera characteristics cannot be null"
+                }.get(CameraCharacteristics.LENS_FACING)
 
                 // Toggle the front and back camera
                 if (toggleCamera) {
-                    val currentFacing: Int? = cameraManager.getCameraCharacteristics(this.cameraId)
+                    val currentFacing: Int? = cameraManager.getCameraCharacteristics(
+                        checkNotNull(this.cameraId) {
+                            "camera id cannot be null"
+                        })
                         .get<Int>(CameraCharacteristics.LENS_FACING)
                     if (Objects.equal(currentFacing, facing)) {
                         continue
                     }
                 }
 
-                val map = characteristics.get(
+                val map = checkNotNull(characteristics) {
+                    "camera characteristics cannot be null"
+                }.get(
                     CameraCharacteristics.SCALER_STREAM_CONFIGURATION_MAP
                 ) ?: continue
 
                 // For still image captures, we use the largest available size.
-                val largest = Collections.max(
-                    /* coll = */ Arrays.asList(*map.getOutputSizes(ImageFormat.JPEG)),
+                resolution = Collections.max(
+                    /* coll = */ listOf(*map.getOutputSizes(ImageFormat.JPEG)),
                     /* comp = */ CompareSizesByArea()
                 )
                 imageReader = ImageReader.newInstance(
-                    largest.width, largest.height,
+                    resolution!!.width, resolution!!.height,
                     ImageFormat.JPEG, /*maxImages*/ 2
                 ).apply {
                     setOnImageAvailableListener(onImageAvailableListener, imageReaderHandler)
@@ -366,9 +387,6 @@
 
                 this.cameraId = cameraId
                 this.characteristics = cameraManager.getCameraCharacteristics(cameraId)
-                viewfinderSurfaceRequest = ViewfinderSurfaceRequest(largest, characteristics)
-
-                Log.d(TAG, "viewfinderSurfaceRequest created = $viewfinderSurfaceRequest")
                 return
             }
         } catch (e: CameraAccessException) {
@@ -376,31 +394,42 @@
         }
     }
 
-    private fun initializeCamera(surface: Surface) = lifecycleScope.launch(Dispatchers.IO) {
-        cameraOpenCloseLock.acquire()
+    private suspend fun initializeCamera(surface: Surface) {
+        cameraOpenCloseLock.lock()
 
-        // Open the selected camera
-        camera = openCamera(cameraManager, cameraId, cameraHandler)
+        withContext(Dispatchers.IO) {
+            // Open the selected camera
+            camera = openCamera(cameraManager, checkNotNull(cameraId) {
+                "camera id cannot be null"
+            }, cameraHandler)
 
-        // Creates list of Surfaces where the camera will output frames
-        val targets = listOf(surface, imageReader.surface)
+            // Creates list of Surfaces where the camera will output frames
+            val targets = listOf(surface, checkNotNull(imageReader?.surface) {
+                "image reader surface cannot be null"
+            })
 
-        try {
-            // Start a capture session using our open camera and list of Surfaces where frames will go
-            session = createCaptureSession(camera, targets, cameraHandler)
+            try {
+                // Start a capture session using our open camera and list of Surfaces where frames will go
+                session = createCaptureSession(checkNotNull(camera) {
+                    "camera cannot be null"
+                }, targets, cameraHandler)
 
-            val captureRequest = camera.createCaptureRequest(
-                CameraDevice.TEMPLATE_PREVIEW).apply { addTarget(surface) }
+                val captureRequest = checkNotNull(camera) {
+                    "camera cannot be null"
+                }.createCaptureRequest(
+                    CameraDevice.TEMPLATE_PREVIEW
+                ).apply { addTarget(surface) }
 
-            // This will keep sending the capture request as frequently as possible until the
-            // session is torn down or session.stopRepeating() is called
-            session.setRepeatingRequest(captureRequest.build(), null, cameraHandler)
-        } catch (e: CameraAccessException) {
-            Log.e(TAG, "createCaptureSession CameraAccessException")
-        } catch (e: IllegalArgumentException) {
-            Log.e(TAG, "createCaptureSession IllegalArgumentException")
-        } catch (e: SecurityException) {
-            Log.e(TAG, "createCaptureSession SecurityException")
+                // This will keep sending the capture request as frequently as possible until the
+                // session is torn down or session.stopRepeating() is called
+                session?.setRepeatingRequest(captureRequest.build(), null, cameraHandler)
+            } catch (e: CameraAccessException) {
+                Log.e(TAG, "createCaptureSession CameraAccessException")
+            } catch (e: IllegalArgumentException) {
+                Log.e(TAG, "createCaptureSession IllegalArgumentException")
+            } catch (e: SecurityException) {
+                Log.e(TAG, "createCaptureSession SecurityException")
+            }
         }
     }
 
@@ -409,58 +438,55 @@
         manager: CameraManager,
         cameraId: String,
         handler: Handler? = null
-    ): CameraDevice = suspendCancellableCoroutine { cont ->
-        try {
-            manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
-                override fun onOpened(device: CameraDevice) {
-                    cameraOpenCloseLock.release()
-                    cont.resume(device)
-                }
-
-                override fun onDisconnected(device: CameraDevice) {
-                    Log.w(TAG, "Camera $cameraId has been disconnected")
-                    cameraOpenCloseLock.release()
-                }
-
-                override fun onError(device: CameraDevice, error: Int) {
-                    val msg = when (error) {
-                        ERROR_CAMERA_DEVICE -> "Fatal (device)"
-                        ERROR_CAMERA_DISABLED -> "Device policy"
-                        ERROR_CAMERA_IN_USE -> "Camera in use"
-                        ERROR_CAMERA_SERVICE -> "Fatal (service)"
-                        ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
-                        else -> "Unknown"
+    ): CameraDevice = withContext(Dispatchers.IO) {
+        suspendCancellableCoroutine { cont ->
+            try {
+                manager.openCamera(cameraId, object : CameraDevice.StateCallback() {
+                    override fun onOpened(device: CameraDevice) {
+                        cameraOpenCloseLock.unlock()
+                        cont.resume(device)
                     }
-                    Log.e(TAG, "Camera $cameraId error: ($error) $msg")
-                }
-            }, handler)
-        } catch (e: CameraAccessException) {
-            Log.e(TAG, "openCamera CameraAccessException")
-        } catch (e: IllegalArgumentException) {
-            Log.e(TAG, "openCamera IllegalArgumentException")
-        } catch (e: SecurityException) {
-            Log.e(TAG, "openCamera SecurityException")
+
+                    override fun onDisconnected(device: CameraDevice) {
+                        Log.w(TAG, "Camera $cameraId has been disconnected")
+                        cameraOpenCloseLock.unlock()
+                    }
+
+                    override fun onError(device: CameraDevice, error: Int) {
+                        val msg = when (error) {
+                            ERROR_CAMERA_DEVICE -> "Fatal (device)"
+                            ERROR_CAMERA_DISABLED -> "Device policy"
+                            ERROR_CAMERA_IN_USE -> "Camera in use"
+                            ERROR_CAMERA_SERVICE -> "Fatal (service)"
+                            ERROR_MAX_CAMERAS_IN_USE -> "Maximum cameras in use"
+                            else -> "Unknown"
+                        }
+                        Log.e(TAG, "Camera $cameraId error: ($error) $msg")
+                    }
+                }, handler)
+            } catch (e: CameraAccessException) {
+                Log.e(TAG, "openCamera CameraAccessException")
+            } catch (e: IllegalArgumentException) {
+                Log.e(TAG, "openCamera IllegalArgumentException")
+            } catch (e: SecurityException) {
+                Log.e(TAG, "openCamera SecurityException")
+            }
         }
     }
 
-    private fun closeCamera() {
+    private suspend fun closeCamera() = withContext(Dispatchers.IO) {
         try {
-            cameraOpenCloseLock.acquire()
-            if (::session.isInitialized) {
-                session.close()
-            }
-
-            if (::camera.isInitialized) {
-                camera.close()
-            }
-
-            if (::imageReader.isInitialized) {
-                imageReader.close()
-            }
+            cameraOpenCloseLock.lock()
+            session?.close()
+            camera?.close()
+            imageReader?.close()
+            session = null
+            camera = null
+            imageReader = null
         } catch (exc: Throwable) {
             Log.e(TAG, "Error closing camera", exc)
         } finally {
-            cameraOpenCloseLock.release()
+            cameraOpenCloseLock.unlock()
         }
     }
 
@@ -469,26 +495,30 @@
         device: CameraDevice,
         targets: List<Surface>,
         handler: Handler? = null
-    ): CameraCaptureSession = suspendCoroutine { cont ->
+    ): CameraCaptureSession = withContext(Dispatchers.IO) {
+        suspendCoroutine { cont ->
 
-        // Create a capture session using the predefined targets; this also involves defining the
-        // session state callback to be notified of when the session is ready
-        device.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
+            // Create a capture session using the predefined targets; this also involves defining the
+            // session state callback to be notified of when the session is ready
+            device.createCaptureSession(targets, object : CameraCaptureSession.StateCallback() {
 
-            override fun onConfigured(session: CameraCaptureSession) = cont.resume(session)
+                override fun onConfigured(session: CameraCaptureSession) = cont.resume(session)
 
-            override fun onConfigureFailed(session: CameraCaptureSession) {
-                val exc = RuntimeException("Camera ${device.id} session configuration failed")
-                Log.e(TAG, exc.message, exc)
-                cont.resumeWithException(exc)
-            }
-        }, handler)
+                override fun onConfigureFailed(session: CameraCaptureSession) {
+                    val exc = RuntimeException("Camera ${device.id} session configuration failed")
+                    Log.e(TAG, exc.message, exc)
+                    cont.resumeWithException(exc)
+                }
+            }, handler)
+        }
     }
 
     // ------------- Toggle Camera -----------
     private fun toggleCamera() {
-        closeCamera()
-        sendSurfaceRequest(true)
+        lifecycleScope.launch {
+            closeCamera()
+            sendSurfaceRequest(null, true)
+        }
     }
 
     // ------------- Save Bitmap ------------
@@ -509,8 +539,10 @@
             val contentUri = MediaStore.Images.Media.EXTERNAL_CONTENT_URI
             val uri = resolver.insert(contentUri, values)
             try {
-                val fos = resolver.openOutputStream(uri!!)
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos!!)
+                val fos = resolver.openOutputStream(checkNotNull(uri) { "uri cannot be null" })
+                bitmap.compress(Bitmap.CompressFormat.PNG, 100, checkNotNull(fos) {
+                    "fos cannot be null"
+                })
                 fos.close()
                 showToast("Saved: $displayName")
             } catch (e: IOException) {
@@ -745,83 +777,89 @@
 
         // Flush any images left in the image reader
         @Suppress("ControlFlowWithEmptyBody")
-        while (imageReader.acquireNextImage() != null) {
+        while (imageReader?.acquireNextImage() != null) {
         }
 
         // Start a new image queue
         val imageQueue = ArrayBlockingQueue<Image>(IMAGE_BUFFER_SIZE)
-        imageReader.setOnImageAvailableListener({ reader ->
+        imageReader?.setOnImageAvailableListener({ reader ->
             val image = reader.acquireNextImage()
             Log.d(TAG, "Image available in queue: ${image.timestamp}")
             imageQueue.add(image)
         }, imageReaderHandler)
 
-        val captureRequest = session.device.createCaptureRequest(
-            CameraDevice.TEMPLATE_STILL_CAPTURE).apply { addTarget(imageReader.surface) }
-        session.capture(captureRequest.build(), object : CameraCaptureSession.CaptureCallback() {
+        val captureRequest = session?.device?.createCaptureRequest(
+            CameraDevice.TEMPLATE_STILL_CAPTURE).apply {
+            imageReader?.surface?.let { this?.addTarget(it) } }
+        if (captureRequest != null) {
+            session?.capture(captureRequest.build(),
+                object : CameraCaptureSession.CaptureCallback() {
 
-            override fun onCaptureStarted(
-                session: CameraCaptureSession,
-                request: CaptureRequest,
-                timestamp: Long,
-                frameNumber: Long
-            ) {
-                super.onCaptureStarted(session, request, timestamp, frameNumber)
-            }
+                override fun onCaptureStarted(
+                    session: CameraCaptureSession,
+                    request: CaptureRequest,
+                    timestamp: Long,
+                    frameNumber: Long
+                ) {
+                    super.onCaptureStarted(session, request, timestamp, frameNumber)
+                }
 
-            override fun onCaptureCompleted(
-                session: CameraCaptureSession,
-                request: CaptureRequest,
-                result: TotalCaptureResult
-            ) {
-                super.onCaptureCompleted(session, request, result)
-                val resultTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP)
-                Log.d(TAG, "Capture result received: $resultTimestamp")
+                override fun onCaptureCompleted(
+                    session: CameraCaptureSession,
+                    request: CaptureRequest,
+                    result: TotalCaptureResult
+                ) {
+                    super.onCaptureCompleted(session, request, result)
+                    val resultTimestamp = result.get(CaptureResult.SENSOR_TIMESTAMP)
+                    Log.d(TAG, "Capture result received: $resultTimestamp")
 
-                // Set a timeout in case image captured is dropped from the pipeline
-                val exc = TimeoutException("Image dequeuing took too long")
-                val timeoutRunnable = Runnable { cont.resumeWithException(exc) }
-                imageReaderHandler.postDelayed(timeoutRunnable, IMAGE_CAPTURE_TIMEOUT_MILLIS)
+                    // Set a timeout in case image captured is dropped from the pipeline
+                    val exc = TimeoutException("Image dequeuing took too long")
+                    val timeoutRunnable = Runnable { cont.resumeWithException(exc) }
+                    imageReaderHandler?.postDelayed(timeoutRunnable, IMAGE_CAPTURE_TIMEOUT_MILLIS)
 
-                // Loop in the coroutine's context until an image with matching timestamp comes
-                // We need to launch the coroutine context again because the callback is done in
-                //  the handler provided to the `capture` method, not in our coroutine context
-                @Suppress("BlockingMethodInNonBlockingContext")
-                lifecycleScope.launch(cont.context) {
-                    while (true) {
+                    // Loop in the coroutine's context until an image with matching timestamp comes
+                    // We need to launch the coroutine context again because the callback is done in
+                    //  the handler provided to the `capture` method, not in our coroutine context
+                    @Suppress("BlockingMethodInNonBlockingContext")
+                    lifecycleScope.launch(cont.context) {
+                        while (true) {
 
-                        // Dequeue images while timestamps don't match
-                        val image = imageQueue.take()
-                        // if (image.timestamp != resultTimestamp) continue
-                        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
-                            image.format != ImageFormat.DEPTH_JPEG &&
-                            image.timestamp != resultTimestamp) continue
-                        Log.d(TAG, "Matching image dequeued: ${image.timestamp}")
+                            // Dequeue images while timestamps don't match
+                            val image = imageQueue.take()
+                            // if (image.timestamp != resultTimestamp) continue
+                            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q &&
+                                image.format != ImageFormat.DEPTH_JPEG &&
+                                image.timestamp != resultTimestamp) continue
+                            Log.d(TAG, "Matching image dequeued: ${image.timestamp}")
 
-                        // Unset the image reader listener
-                        imageReaderHandler.removeCallbacks(timeoutRunnable)
-                        imageReader.setOnImageAvailableListener(null, null)
+                            // Unset the image reader listener
+                            imageReaderHandler?.removeCallbacks(timeoutRunnable)
+                            imageReader?.setOnImageAvailableListener(null, null)
 
-                        // Clear the queue of images, if there are left
-                        while (imageQueue.size > 0) {
-                            imageQueue.take().close()
+                            // Clear the queue of images, if there are left
+                            while (imageQueue.size > 0) {
+                                imageQueue.take().close()
+                            }
+
+                            // Compute EXIF orientation metadata
+                            val rotation = relativeOrientation?.value ?: 0
+                            val mirrored = characteristics?.get(
+                                CameraCharacteristics.LENS_FACING) ==
+                                CameraCharacteristics.LENS_FACING_FRONT
+                            val exifOrientation = computeExifOrientation(rotation, mirrored)
+
+                            // Build the result and resume progress
+                            cont.resume(CombinedCaptureResult(
+                                image, result, exifOrientation, checkNotNull(imageReader) {
+                                    "image reader cannot be null"
+                                }.imageFormat))
+                            // There is no need to break out of the loop, this coroutine will suspend
                         }
-
-                        // Compute EXIF orientation metadata
-                        val rotation = relativeOrientation.value ?: 0
-                        val mirrored = characteristics.get(CameraCharacteristics.LENS_FACING) ==
-                            CameraCharacteristics.LENS_FACING_FRONT
-                        val exifOrientation = computeExifOrientation(rotation, mirrored)
-
-                        // Build the result and resume progress
-                        cont.resume(CombinedCaptureResult(
-                            image, result, exifOrientation, imageReader.imageFormat))
-
-                        // There is no need to break out of the loop, this coroutine will suspend
                     }
                 }
-            }
-        }, cameraHandler)
+            }, cameraHandler)
+        }
     }
 
     /** Helper function used to save a [CombinedCaptureResult] into a [File] */
@@ -844,7 +882,9 @@
 
             // When the format is RAW we use the DngCreator utility library
             ImageFormat.RAW_SENSOR -> {
-                val dngCreator = DngCreator(characteristics, result.metadata)
+                val dngCreator = DngCreator(checkNotNull(characteristics) {
+                    "camera characteristics cannot be null"
+                }, result.metadata)
                 try {
                     val output = createFile("dng")
                     FileOutputStream(output).use { dngCreator.writeImage(it, result.image) }
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
index 51e24b5..439cb47 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/CarMessage.java
@@ -24,11 +24,13 @@
 import androidx.annotation.Nullable;
 import androidx.car.app.annotations.CarProtocol;
 import androidx.car.app.annotations.ExperimentalCarApi;
+import androidx.car.app.annotations.KeepFields;
 import androidx.car.app.annotations.RequiresCarApi;
 import androidx.car.app.model.CarText;
-import androidx.car.app.annotations.KeepFields;
 import androidx.core.app.Person;
 
+import java.util.Objects;
+
 /** Represents a single message in a {@link ConversationItem} */
 @ExperimentalCarApi
 @CarProtocol
@@ -42,6 +44,72 @@
     private final long mReceivedTimeEpochMillis;
     private final boolean mIsRead;
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                getPersonHashCode(getSender()),
+                mBody,
+                mReceivedTimeEpochMillis,
+                mIsRead
+        );
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof CarMessage)) {
+            return false;
+        }
+        CarMessage otherCarMessage = (CarMessage) other;
+
+        return
+                arePeopleEqual(getSender(), otherCarMessage.getSender())
+                        && Objects.equals(mBody, otherCarMessage.mBody)
+                        && mReceivedTimeEpochMillis == otherCarMessage.mReceivedTimeEpochMillis
+                        && mIsRead == otherCarMessage.mIsRead
+                ;
+    }
+
+    // TODO(b/266877597): Move to androidx.core.app.Person
+    private static boolean arePeopleEqual(Person person1, Person person2) {
+        // If a unique ID was provided, use it
+        String key1 = person1.getKey();
+        String key2 = person2.getKey();
+        if (key1 != null || key2 != null) {
+            return Objects.equals(key1, key2);
+        }
+
+        // CharSequence doesn't have well-defined "equals" behavior -- convert to String instead
+        String name1 = Objects.toString(person1.getName());
+        String name2 = Objects.toString(person2.getName());
+
+        // Fallback: Compare field-by-field
+        return
+                Objects.equals(name1, name2)
+                        && Objects.equals(person1.getUri(), person2.getUri())
+                        && Objects.equals(person1.isBot(), person2.isBot())
+                        && Objects.equals(person1.isImportant(), person2.isImportant());
+    }
+
+    // TODO(b/266877597): Move to androidx.core.app.Person
+    private static int getPersonHashCode(Person person) {
+        // If a unique ID was provided, use it
+        String key = person.getKey();
+        if (key != null) {
+            return key.hashCode();
+        }
+
+        // Fallback: Use hash code for individual fields
+        return Objects.hash(
+                person.getName(),
+                person.getUri(),
+                person.isBot(),
+                person.isImportant()
+        );
+    }
+
     CarMessage(@NonNull Builder builder) {
         this.mSender = requireNonNull(builder.mSender).toBundle();
         this.mBody = requireNonNull(builder.mBody);
diff --git a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
index ea41250..4c98df4 100644
--- a/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/messaging/model/ConversationItem.java
@@ -35,6 +35,7 @@
 
 import java.util.ArrayList;
 import java.util.List;
+import java.util.Objects;
 
 /** Represents a conversation */
 @ExperimentalCarApi
@@ -54,6 +55,36 @@
     @NonNull
     private final ConversationCallbackDelegate mConversationCallbackDelegate;
 
+    @Override
+    public int hashCode() {
+        return Objects.hash(
+                mId,
+                mTitle,
+                mIcon,
+                mIsGroupConversation,
+                mMessages
+        );
+    }
+
+    @Override
+    public boolean equals(@Nullable Object other) {
+        if (this == other) {
+            return true;
+        }
+        if (!(other instanceof ConversationItem)) {
+            return false;
+        }
+        ConversationItem otherConversationItem = (ConversationItem) other;
+
+        return
+                Objects.equals(mId, otherConversationItem.mId)
+                        && Objects.equals(mTitle, otherConversationItem.mTitle)
+                        && Objects.equals(mIcon, otherConversationItem.mIcon)
+                        && mIsGroupConversation == otherConversationItem.mIsGroupConversation
+                        && Objects.equals(mMessages, otherConversationItem.mMessages)
+                ;
+    }
+
     ConversationItem(@NonNull Builder builder) {
         this.mId = requireNonNull(builder.mId);
         this.mTitle = requireNonNull(builder.mTitle);
diff --git a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
index 67ad8dc..edf1329 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/constraints/ActionsConstraints.java
@@ -138,6 +138,7 @@
     public static final ActionsConstraints ACTIONS_CONSTRAINTS_ROW =
             new ActionsConstraints.Builder()
                     .setMaxActions(1)
+                    .setMaxCustomTitles(1)
                     .addAllowedActionType(Action.TYPE_CUSTOM)
                     .setRequireActionIcons(true)
                     .setOnClickListenerAllowed(true)
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java
new file mode 100644
index 0000000..f250727
--- /dev/null
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/CarMessageTest.java
@@ -0,0 +1,200 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.messaging.model;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.car.app.model.CarText;
+import androidx.core.app.Person;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.robolectric.RobolectricTestRunner;
+import org.robolectric.annotation.internal.DoNotInstrument;
+
+/** Tests for {@link CarMessage}. */
+@RunWith(RobolectricTestRunner.class)
+@DoNotInstrument
+public class CarMessageTest {
+    /** Ensure the builder does not fail for the minimum set of required fields. */
+    @Test
+    public void build_withRequiredFieldsOnly() {
+        TestConversationFactory.createMinimalMessage();
+
+        // assert no crash
+    }
+
+    /** Ensure the builder does not fail when all fields are assigned. */
+    @Test
+    public void build_withAllFields() {
+        TestConversationFactory.createFullyPopulatedMessage();
+
+        // assert no crash
+    }
+
+    // Ignore nullability, so we can null out a builder field
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    public void build_throwsException_ifSenderMissing() {
+        assertThrows(
+                NullPointerException.class,
+                () -> TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(null)
+                        .build()
+        );
+    }
+
+    // Ignore nullability, so we can null out a builder field
+    @SuppressWarnings("ConstantConditions")
+    @Test
+    public void build_throwsException_ifMessageBodyMissing() {
+        assertThrows(
+                NullPointerException.class,
+                () -> TestConversationFactory.createMinimalMessageBuilder()
+                        .setBody(null)
+                        .build()
+        );
+    }
+
+    // region .equals() & .hashCode()
+    @Test
+    public void equalsAndHashCode_areEqual_forMinimalMessage() {
+        CarMessage message1 =
+                TestConversationFactory.createMinimalMessage();
+        CarMessage message2 =
+                TestConversationFactory.createMinimalMessage();
+
+        assertEqual(message1, message2);
+    }
+
+    @Test
+    public void equalsAndHashCode_areEqual_forFullyPopulatedMessage() {
+        CarMessage message1 =
+                TestConversationFactory.createFullyPopulatedMessage();
+        CarMessage message2 =
+                TestConversationFactory.createFullyPopulatedMessage();
+
+        assertEqual(message1, message2);
+    }
+
+    @Test
+    public void equalsAndHashCode_produceCorrectResult_ifIndividualFieldDiffers() {
+        // Create base item, for comparison
+        CarMessage fullyPopulatedMessage =
+                TestConversationFactory.createFullyPopulatedMessage();
+
+        // Create various non-equal items
+        CarMessage modifiedSender =
+                TestConversationFactory
+                        .createFullyPopulatedMessageBuilder()
+                        .setSender(
+                                TestConversationFactory
+                                        .createMinimalPersonBuilder()
+                                        .setKey("Modified Key")
+                                        .build()
+                        )
+                        .build();
+        CarMessage modifiedBody =
+                TestConversationFactory
+                        .createFullyPopulatedMessageBuilder()
+                        .setBody(CarText.create("Modified Message Body"))
+                        .build();
+        CarMessage modifiedReceivedTimeEpochMillis =
+                TestConversationFactory
+                        .createFullyPopulatedMessageBuilder()
+                        .setReceivedTimeEpochMillis(
+                                // Guaranteed to be different :)
+                                fullyPopulatedMessage.getReceivedTimeEpochMillis() + 1
+                        )
+                        .build();
+        CarMessage modifiedIsRead =
+                TestConversationFactory
+                        .createFullyPopulatedMessageBuilder()
+                        .setRead(!fullyPopulatedMessage.isRead())
+                        .build();
+
+        // Verify (lack of) equality
+        assertNotEqual(fullyPopulatedMessage, modifiedSender);
+        assertNotEqual(fullyPopulatedMessage, modifiedBody);
+        assertNotEqual(fullyPopulatedMessage, modifiedReceivedTimeEpochMillis);
+        assertNotEqual(fullyPopulatedMessage, modifiedIsRead);
+    }
+
+    @Test
+    public void equalsAndHashCode_produceCorrectResult_ifSenderIdMissing() {
+        Person.Builder senderWithoutId = TestConversationFactory
+                .createMinimalPersonBuilder()
+                .setKey(null);
+        Person.Builder senderWithId = TestConversationFactory
+                .createMinimalPersonBuilder()
+                .setKey("Test Sender Key");
+
+        // Create Test Data
+        CarMessage senderKeyMissingWithStandardData =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithoutId.build())
+                        .build();
+        CarMessage senderKeyMissingWithStandardData2 =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithoutId.build())
+                        .build();
+        CarMessage senderKeyMissingWithModifiedData =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithoutId.build())
+                        .setBody(CarText.create("Modified Message Body"))
+                        .build();
+        CarMessage senderKeyProvidedWithStandardData =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithId.build())
+                        .build();
+        CarMessage senderKeyProvidedWithStandardData2 =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithId.build())
+                        .build();
+        CarMessage senderKeyProvidedWithModifiedData =
+                TestConversationFactory.createMinimalMessageBuilder()
+                        .setSender(senderWithId.build())
+                        .setBody(CarText.create("Modified Message Body"))
+                        .build();
+
+        // Verify (in)equality
+
+        // Sender & message content are equal: == EQUAL ==
+        assertEqual(senderKeyMissingWithStandardData, senderKeyMissingWithStandardData2);
+        assertEqual(senderKeyProvidedWithStandardData, senderKeyProvidedWithStandardData2);
+
+        // One sender is missing a key: == NOT EQUAL ==
+        assertNotEqual(senderKeyMissingWithStandardData, senderKeyProvidedWithStandardData);
+
+        // Sender is equal, but message content is not: == NOT EQUAL ==
+        assertNotEqual(senderKeyMissingWithStandardData, senderKeyMissingWithModifiedData);
+        assertNotEqual(senderKeyProvidedWithStandardData, senderKeyProvidedWithModifiedData);
+    }
+
+    private void assertEqual(CarMessage message1, CarMessage message2) {
+        assertThat(message1).isEqualTo(message2);
+        assertThat(message1.hashCode()).isEqualTo(message2.hashCode());
+    }
+
+    private void assertNotEqual(CarMessage message1, CarMessage message2) {
+        assertThat(message1).isNotEqualTo(message2);
+        assertThat(message1.hashCode()).isNotEqualTo(message2.hashCode());
+    }
+    // endregion
+}
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
index dc250ea..9f61ade 100644
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/ConversationItemTest.java
@@ -16,9 +16,13 @@
 
 package androidx.car.app.messaging.model;
 
+import static com.google.common.truth.Truth.assertThat;
+
 import static org.junit.Assert.assertThrows;
 
+import androidx.annotation.NonNull;
 import androidx.car.app.model.CarIcon;
+import androidx.car.app.model.CarText;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -26,6 +30,7 @@
 import org.robolectric.annotation.internal.DoNotInstrument;
 
 import java.util.ArrayList;
+import java.util.List;
 
 /** Tests for {@link ConversationItem}. */
 @RunWith(RobolectricTestRunner.class)
@@ -34,7 +39,7 @@
     /** Ensure the builder does not fail for the minimum set of required fields. */
     @Test
     public void build_withRequiredFieldsOnly() {
-        TestConversationFactory.createMinimalConversationItemBuilder().build();
+        TestConversationFactory.createMinimalConversationItem();
 
         // assert no crash
     }
@@ -42,10 +47,7 @@
     /** Ensure the builder does not fail when all fields are assigned. */
     @Test
     public void build_withAllFields() {
-        TestConversationFactory.createMinimalConversationItemBuilder()
-                .setIcon(CarIcon.APP_ICON) // icon is chosen arbitrarily for testing purposes
-                .setGroupConversation(true)
-                .build();
+        TestConversationFactory.createFullyPopulatedConversationItem();
 
         // assert no crash
     }
@@ -59,4 +61,102 @@
                         .build()
         );
     }
+
+    // region .equals() & .hashCode()
+    @Test
+    public void equalsAndHashCode_areEqual_forMinimalConversationItem() {
+        ConversationItem item1 =
+                TestConversationFactory.createMinimalConversationItem();
+        ConversationItem item2 =
+                TestConversationFactory.createMinimalConversationItem();
+
+        assertEqual(item1, item2);
+    }
+
+    @Test
+    public void equalsAndHashCode_areEqual_forFullyPopulatedConversationItem() {
+        ConversationItem item1 =
+                TestConversationFactory.createFullyPopulatedConversationItem();
+        ConversationItem item2 =
+                TestConversationFactory.createFullyPopulatedConversationItem();
+
+        assertEqual(item1, item2);
+    }
+
+    @Test
+    public void equalsAndHashCode_produceCorrectResult_ifIndividualFieldDiffers() {
+        // Create base item, for comparison
+        ConversationItem fullyPopulatedItem =
+                TestConversationFactory.createFullyPopulatedConversationItem();
+
+        // Create various non-equal items
+        ConversationItem modifiedId =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setId("Modified ID")
+                        .build();
+        ConversationItem modifiedTitle =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setTitle(CarText.create("Modified Title"))
+                        .build();
+        ConversationItem modifiedIcon =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setIcon(CarIcon.ALERT)
+                        .build();
+        ConversationItem modifiedGroupStatus =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setGroupConversation(!fullyPopulatedItem.isGroupConversation())
+                        .build();
+        List<CarMessage> modifiedMessages = new ArrayList<>(1);
+        modifiedMessages.add(
+                TestConversationFactory
+                        .createMinimalMessageBuilder()
+                        .setBody(CarText.create("Modified Message Body"))
+                        .build()
+        );
+        ConversationItem modifiedMessageList =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setMessages(modifiedMessages)
+                        .build();
+        ConversationItem modifiedConversationCallback =
+                TestConversationFactory
+                        .createFullyPopulatedConversationItemBuilder()
+                        .setConversationCallback(new ConversationCallback() {
+                            @Override
+                            public void onMarkAsRead() {
+
+                            }
+
+                            @Override
+                            public void onTextReply(@NonNull String replyText) {
+
+                            }
+                        })
+                        .build();
+
+        // Verify (lack of) equality
+        assertNotEqual(fullyPopulatedItem, modifiedId);
+        assertNotEqual(fullyPopulatedItem, modifiedTitle);
+        assertNotEqual(fullyPopulatedItem, modifiedIcon);
+        assertNotEqual(fullyPopulatedItem, modifiedGroupStatus);
+        assertNotEqual(fullyPopulatedItem, modifiedMessageList);
+
+        // NOTE: Conversation Callback does not affect equality
+        assertEqual(fullyPopulatedItem, modifiedConversationCallback);
+    }
+
+    private void assertEqual(ConversationItem item1, ConversationItem item2) {
+        assertThat(item1).isEqualTo(item2);
+        assertThat(item1.hashCode()).isEqualTo(item2.hashCode());
+    }
+
+    private void assertNotEqual(ConversationItem item1, ConversationItem item2) {
+        assertThat(item1).isNotEqualTo(item2);
+        assertThat(item1.hashCode()).isNotEqualTo(item2.hashCode());
+    }
+    // endregion
 }
diff --git a/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java b/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
index 8eb5a4a..19359bb 100644
--- a/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
+++ b/car/app/app/src/test/java/androidx/car/app/messaging/model/TestConversationFactory.java
@@ -16,16 +16,26 @@
 
 package androidx.car.app.messaging.model;
 
+import android.graphics.Bitmap;
+import android.net.Uri;
+
 import androidx.annotation.NonNull;
+import androidx.car.app.model.CarIcon;
 import androidx.car.app.model.CarText;
 import androidx.car.app.model.ItemList;
 import androidx.core.app.Person;
+import androidx.core.graphics.drawable.IconCompat;
 
 import java.util.ArrayList;
 import java.util.List;
 
 /** Factory for creating {@link ConversationItem} and related data in tests */
 public final class TestConversationFactory {
+    private static final IconCompat TEST_SENDER_ICON =
+            IconCompat.createWithBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888));
+
+    private static final Uri TEST_SENDER_URI =
+            Uri.parse("http://foo.com/test/sender/uri");
     private static final ConversationCallback EMPTY_CONVERSATION_CALLBACK =
             new ConversationCallback() {
                 @Override
@@ -37,13 +47,59 @@
                 }
             };
 
+    // region Person
+    /**
+     * Creates a {@link Person.Builder} instance for testing
+     *
+     * <p>This method fills in the minimum required data to create a valid {@link Person}.
+     */
+    public static Person.Builder createMinimalPersonBuilder() {
+        return new Person.Builder().setName("Person Name");
+    }
+
     /**
      * Creates a {@link Person} instance for testing
      *
      * <p>This method fills in the minimum required data to create a valid {@link Person}.
      */
     private static Person createMinimalPerson() {
-        return new Person.Builder().setName("Person Name").build();
+        return createMinimalPersonBuilder().build();
+    }
+
+    /**
+     * Creates a {@link Person.Builder} instance for testing
+     *
+     * <p>This method fills in the minimum required data to create a valid {@link Person}.
+     */
+    public static Person.Builder createFullyPopulatedPersonBuilder() {
+        return createMinimalPersonBuilder()
+                .setKey("Foo Person")
+                .setIcon(TEST_SENDER_ICON)
+                .setUri(TEST_SENDER_URI.toString())
+                .setBot(true)
+                .setImportant(true);
+    }
+
+    /**
+     * Creates a {@link Person} instance for testing
+     *
+     * <p>This method fills in the minimum required data to create a valid {@link Person}.
+     */
+    private static Person createFullyPopulatedPerson() {
+        return createFullyPopulatedPersonBuilder().build();
+    }
+    // endregion
+
+    // region Message
+    /**
+     * Creates a {@link CarMessage.Builder} instance for testing
+     *
+     * <p>This method fills in the minimum required data to create a valid {@link CarMessage}.
+     */
+    public static CarMessage.Builder createMinimalMessageBuilder() {
+        return new CarMessage.Builder()
+                .setSender(createMinimalPerson())
+                .setBody(CarText.create("Message body"));
     }
 
     /**
@@ -51,14 +107,33 @@
      *
      * <p>This method fills in the minimum required data to create a valid {@link CarMessage}.
      */
-    private static CarMessage createMinimalMessage() {
-        return new CarMessage.Builder()
-                .setSender(createMinimalPerson())
-                .setBody(CarText.create("Message body"))
-                .build();
+    public static CarMessage createMinimalMessage() {
+        return createMinimalMessageBuilder().build();
     }
 
     /**
+     * Creates a {@link CarMessage.Builder} instance for testing
+     *
+     * <p>This method populates every field in  {@link CarMessage.Builder}.
+     */
+    public static CarMessage.Builder createFullyPopulatedMessageBuilder() {
+        return createMinimalMessageBuilder()
+                .setRead(true)
+                .setReceivedTimeEpochMillis(12345);
+    }
+
+    /**
+     * Creates a {@link CarMessage} instance for testing
+     *
+     * <p>This method populates every field in  {@link CarMessage.Builder}.
+     */
+    public static CarMessage createFullyPopulatedMessage() {
+        return createFullyPopulatedMessageBuilder().build();
+    }
+    // endregion
+
+    // region ConversationItem
+    /**
      * Creates a {@link ConversationItem.Builder} instance for testing
      *
      * <p>This method fills in the minimum required data to create a valid {@link
@@ -84,6 +159,29 @@
         return createMinimalConversationItemBuilder().build();
     }
 
+    /**
+     * Creates a {@link ConversationItem.Builder} instance for testing
+     *
+     * <p>This method populates every field in {@link ConversationItem.Builder}.
+     */
+    public static ConversationItem.Builder createFullyPopulatedConversationItemBuilder() {
+        return createMinimalConversationItemBuilder()
+                // APP_ICON was chosen because it is easy to access in code
+                // In the future, it may make more sense to add a "realistic" contact photo here.
+                .setIcon(CarIcon.APP_ICON)
+                .setGroupConversation(true);
+    }
+
+    /**
+     * Creates a {@link ConversationItem} instance for testing
+     *
+     * <p>This method populates every field in {@link ConversationItem.Builder}.
+     */
+    public static ConversationItem createFullyPopulatedConversationItem() {
+        return createFullyPopulatedConversationItemBuilder().build();
+    }
+    // endregion
+
     public static ItemList createItemListWithConversationItem() {
         return new ItemList.Builder().addItem(createMinimalConversationItem()).build();
     }
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 798c0219..68825f3 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
@@ -3931,16 +3931,15 @@
             @Composable
             fun <T> provided(value: T, %composer: Composer?, %changed: Int): State<T> {
               %composer.startReplaceableGroup(<>)
-              sourceInformation(%composer, "C(provided):Test.kt")
+              sourceInformation(%composer, "C(provided)*<rememb...>:Test.kt")
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
-              val tmp0 = %composer.cache(false) {
+              val tmp0 = remember({
                 mutableStateOf(
                   value = value
                 )
-              }
-              .apply {
+              }, %composer, 0).apply {
                 %this%apply.value = value
               }
               if (isTraceInProgress()) {
@@ -5769,7 +5768,7 @@
             @Composable
             fun Test(start: Int, end: Int, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
-              sourceInformation(%composer, "C(Test)P(1)*<get(bK...>:Test.kt")
+              sourceInformation(%composer, "C(Test)P(1)<rememb...>,*<get(bK...>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(start)) 0b0100 else 0b0010
@@ -5781,9 +5780,9 @@
                 if (isTraceInProgress()) {
                   traceEventStart(<>, %changed, -1, <>)
                 }
-                val a = %composer.cache(false) {
+                val a = remember({
                   A()
-                }
+                }, %composer, 0)
                 val tmp0_iterator = start until end.iterator()
                 while (tmp0_iterator.hasNext()) {
                   val i = tmp0_iterator.next()
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
index e8d6037..87e5fde 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/LambdaMemoizationTransformTests.kt
@@ -215,7 +215,7 @@
             @Composable
             fun Err(y: Int, z: Int, %composer: Composer?, %changed: Int) {
               %composer.startReplaceableGroup(<>)
-              sourceInformation(%composer, "C(Err):Test.kt")
+              sourceInformation(%composer, "C(Err)<{>:Test.kt")
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
@@ -225,11 +225,11 @@
                   return x + y + w
                 }
               }
-              %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(y) || %changed and 0b0110 === 0b0100 or %changed and 0b01110000 xor 0b00110000 > 32 && %composer.changed(z) || %changed and 0b00110000 === 0b00100000) {
+              remember(y, z, {
                 {
                   Local().something(2)
                 }
-              }
+              }, %composer, 0b1110 and %changed or 0b01110000 and %changed)
               if (isTraceInProgress()) {
                 traceEventEnd()
               }
@@ -260,18 +260,18 @@
             @Composable
             fun Example(z: Int, %composer: Composer?, %changed: Int) {
               %composer.startReplaceableGroup(<>)
-              sourceInformation(%composer, "C(Example):Test.kt")
+              sourceInformation(%composer, "C(Example)<{>:Test.kt")
               if (isTraceInProgress()) {
                 traceEventStart(<>, %changed, -1, <>)
               }
               class Foo(val x: Int) {
                 val y: Int = z
               }
-              val lambda = %composer.cache(%changed and 0b1110 xor 0b0110 > 4 && %composer.changed(z) || %changed and 0b0110 === 0b0100) {
+              val lambda = remember(z, {
                 {
                   Foo(1)
                 }
-              }
+              }, %composer, 0b1110 and %changed)
               if (isTraceInProgress()) {
                 traceEventEnd()
               }
@@ -622,7 +622,7 @@
             @Composable
             fun Example(x: Int, %composer: Composer?, %changed: Int) {
               %composer = %composer.startRestartGroup(<>)
-              sourceInformation(%composer, "C(Example):Test.kt")
+              sourceInformation(%composer, "C(Example)<{>:Test.kt")
               val %dirty = %changed
               if (%changed and 0b1110 === 0) {
                 %dirty = %dirty or if (%composer.changed(x)) 0b0100 else 0b0010
@@ -634,11 +634,11 @@
                 fun foo() {
                   use(x)
                 }
-                val shouldMemoize = %composer.cache(%dirty and 0b1110 === 0b0100) {
+                val shouldMemoize = remember(x, {
                   {
                     foo
                   }
-                }
+                }, %composer, 0b1110 and %dirty)
                 if (isTraceInProgress()) {
                   traceEventEnd()
                 }
diff --git a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
index 4a60e10..0b41128 100644
--- a/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
+++ b/compose/compiler/compiler-hosted/integration-tests/src/test/java/androidx/compose/compiler/plugins/kotlin/RememberIntrinsicTransformTests.kt
@@ -17,9 +17,15 @@
 package androidx.compose.compiler.plugins.kotlin
 
 import org.intellij.lang.annotations.Language
+import org.jetbrains.kotlin.config.CompilerConfiguration
 import org.junit.Test
 
 class RememberIntrinsicTransformTests : AbstractIrTransformTest() {
+    override fun CompilerConfiguration.updateConfiguration() {
+        put(ComposeConfiguration.SOURCE_INFORMATION_ENABLED_KEY, true)
+        put(ComposeConfiguration.INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_KEY, true)
+    }
+
     private fun comparisonPropagation(
         @Language("kotlin")
         unchecked: String,
diff --git a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
index 2eb933f..666f74d 100644
--- a/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
+++ b/compose/compiler/compiler-hosted/src/main/java/androidx/compose/compiler/plugins/kotlin/ComposePlugin.kt
@@ -329,7 +329,7 @@
             )
             val intrinsicRememberEnabled = configuration.get(
                 ComposeConfiguration.INTRINSIC_REMEMBER_OPTIMIZATION_ENABLED_KEY,
-                true
+                false
             )
             val decoysEnabled = configuration.getBoolean(
                 ComposeConfiguration.DECOYS_ENABLED_KEY,
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
index 90e6259..d1a4e31 100644
--- a/compose/foundation/foundation/api/current.ignore
+++ b/compose/foundation/foundation/api/current.ignore
@@ -1,4 +1,8 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMainAxisItemSpacing():
+    Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMainAxisItemSpacing()
+
+
 RemovedClass: androidx.compose.foundation.lazy.LazyListPinningModifierKt:
     Removed class androidx.compose.foundation.lazy.LazyListPinningModifierKt
 RemovedClass: androidx.compose.foundation.lazy.layout.PinnableParentKt:
diff --git a/compose/foundation/foundation/api/current.txt b/compose/foundation/foundation/api/current.txt
index d087b0b..1c8281c 100644
--- a/compose/foundation/foundation/api/current.txt
+++ b/compose/foundation/foundation/api/current.txt
@@ -484,6 +484,7 @@
   @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyListLayoutInfo {
     method public default int getAfterContentPadding();
     method public default int getBeforeContentPadding();
+    method public default int getMainAxisItemSpacing();
     method public default androidx.compose.foundation.gestures.Orientation getOrientation();
     method public default boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -493,6 +494,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.LazyListItemInfo> getVisibleItemsInfo();
     property public default int afterContentPadding;
     property public default int beforeContentPadding;
+    property public default int mainAxisItemSpacing;
     property public default androidx.compose.foundation.gestures.Orientation orientation;
     property public default boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -624,6 +626,7 @@
   public sealed interface LazyGridLayoutInfo {
     method public int getAfterContentPadding();
     method public int getBeforeContentPadding();
+    method public int getMainAxisItemSpacing();
     method public androidx.compose.foundation.gestures.Orientation getOrientation();
     method public boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -633,6 +636,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.grid.LazyGridItemInfo> getVisibleItemsInfo();
     property public abstract int afterContentPadding;
     property public abstract int beforeContentPadding;
+    property public abstract int mainAxisItemSpacing;
     property public abstract androidx.compose.foundation.gestures.Orientation orientation;
     property public abstract boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -706,6 +710,9 @@
   public final class LazyLayoutKt {
   }
 
+  public final class LazyLayoutPinnableItemKt {
+  }
+
   public final class LazyLayoutPrefetcher_androidKt {
   }
 
@@ -715,9 +722,6 @@
   public final class LazyNearestItemsRangeKt {
   }
 
-  public final class LazyPinnableContainerProviderKt {
-  }
-
   public final class LazySaveableStateHolderKt {
   }
 
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 8b4e97f..d74aeec 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -578,6 +578,7 @@
   @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyListLayoutInfo {
     method public default int getAfterContentPadding();
     method public default int getBeforeContentPadding();
+    method public default int getMainAxisItemSpacing();
     method public default androidx.compose.foundation.gestures.Orientation getOrientation();
     method public default boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -587,6 +588,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.LazyListItemInfo> getVisibleItemsInfo();
     property public default int afterContentPadding;
     property public default int beforeContentPadding;
+    property public default int mainAxisItemSpacing;
     property public default androidx.compose.foundation.gestures.Orientation orientation;
     property public default boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -721,6 +723,7 @@
   public sealed interface LazyGridLayoutInfo {
     method public int getAfterContentPadding();
     method public int getBeforeContentPadding();
+    method public int getMainAxisItemSpacing();
     method public androidx.compose.foundation.gestures.Orientation getOrientation();
     method public boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -730,6 +733,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.grid.LazyGridItemInfo> getVisibleItemsInfo();
     property public abstract int afterContentPadding;
     property public abstract int beforeContentPadding;
+    property public abstract int mainAxisItemSpacing;
     property public abstract androidx.compose.foundation.gestures.Orientation orientation;
     property public abstract boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -852,6 +856,19 @@
     method @androidx.compose.runtime.Stable public default long toSp(float);
   }
 
+  public final class LazyLayoutPinnableItemKt {
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyLayoutPinnableItem(int index, androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList pinnedItemList, kotlin.jvm.functions.Function0<kotlin.Unit> content);
+  }
+
+  @androidx.compose.foundation.ExperimentalFoundationApi public final class LazyLayoutPinnedItemList implements kotlin.jvm.internal.markers.KMappedMarker java.util.List<androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList.PinnedItem> {
+    ctor public LazyLayoutPinnedItemList();
+  }
+
+  @androidx.compose.foundation.ExperimentalFoundationApi public static sealed interface LazyLayoutPinnedItemList.PinnedItem {
+    method public int getIndex();
+    property public abstract int index;
+  }
+
   @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Stable public final class LazyLayoutPrefetchState {
     ctor public LazyLayoutPrefetchState();
     method public androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle schedulePrefetch(int index, long constraints);
@@ -871,9 +888,6 @@
     method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static androidx.compose.runtime.State<kotlin.ranges.IntRange> rememberLazyNearestItemsRangeState(kotlin.jvm.functions.Function0<java.lang.Integer> firstVisibleItemIndex, kotlin.jvm.functions.Function0<java.lang.Integer> slidingWindowSize, kotlin.jvm.functions.Function0<java.lang.Integer> extraItemCount);
   }
 
-  public final class LazyPinnableContainerProviderKt {
-  }
-
   public final class LazySaveableStateHolderKt {
   }
 
@@ -931,6 +945,7 @@
   @androidx.compose.foundation.ExperimentalFoundationApi public sealed interface LazyStaggeredGridLayoutInfo {
     method public int getAfterContentPadding();
     method public int getBeforeContentPadding();
+    method public int getMainAxisItemSpacing();
     method public androidx.compose.foundation.gestures.Orientation getOrientation();
     method public int getTotalItemsCount();
     method public int getViewportEndOffset();
@@ -939,6 +954,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemInfo> getVisibleItemsInfo();
     property public abstract int afterContentPadding;
     property public abstract int beforeContentPadding;
+    property public abstract int mainAxisItemSpacing;
     property public abstract androidx.compose.foundation.gestures.Orientation orientation;
     property public abstract int totalItemsCount;
     property public abstract int viewportEndOffset;
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
index 90e6259..d1a4e31 100644
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ b/compose/foundation/foundation/api/restricted_current.ignore
@@ -1,4 +1,8 @@
 // Baseline format: 1.0
+AddedAbstractMethod: androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo#getMainAxisItemSpacing():
+    Added method androidx.compose.foundation.lazy.grid.LazyGridLayoutInfo.getMainAxisItemSpacing()
+
+
 RemovedClass: androidx.compose.foundation.lazy.LazyListPinningModifierKt:
     Removed class androidx.compose.foundation.lazy.LazyListPinningModifierKt
 RemovedClass: androidx.compose.foundation.lazy.layout.PinnableParentKt:
diff --git a/compose/foundation/foundation/api/restricted_current.txt b/compose/foundation/foundation/api/restricted_current.txt
index d087b0b..1c8281c 100644
--- a/compose/foundation/foundation/api/restricted_current.txt
+++ b/compose/foundation/foundation/api/restricted_current.txt
@@ -484,6 +484,7 @@
   @kotlin.jvm.JvmDefaultWithCompatibility public interface LazyListLayoutInfo {
     method public default int getAfterContentPadding();
     method public default int getBeforeContentPadding();
+    method public default int getMainAxisItemSpacing();
     method public default androidx.compose.foundation.gestures.Orientation getOrientation();
     method public default boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -493,6 +494,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.LazyListItemInfo> getVisibleItemsInfo();
     property public default int afterContentPadding;
     property public default int beforeContentPadding;
+    property public default int mainAxisItemSpacing;
     property public default androidx.compose.foundation.gestures.Orientation orientation;
     property public default boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -624,6 +626,7 @@
   public sealed interface LazyGridLayoutInfo {
     method public int getAfterContentPadding();
     method public int getBeforeContentPadding();
+    method public int getMainAxisItemSpacing();
     method public androidx.compose.foundation.gestures.Orientation getOrientation();
     method public boolean getReverseLayout();
     method public int getTotalItemsCount();
@@ -633,6 +636,7 @@
     method public java.util.List<androidx.compose.foundation.lazy.grid.LazyGridItemInfo> getVisibleItemsInfo();
     property public abstract int afterContentPadding;
     property public abstract int beforeContentPadding;
+    property public abstract int mainAxisItemSpacing;
     property public abstract androidx.compose.foundation.gestures.Orientation orientation;
     property public abstract boolean reverseLayout;
     property public abstract int totalItemsCount;
@@ -706,6 +710,9 @@
   public final class LazyLayoutKt {
   }
 
+  public final class LazyLayoutPinnableItemKt {
+  }
+
   public final class LazyLayoutPrefetcher_androidKt {
   }
 
@@ -715,9 +722,6 @@
   public final class LazyNearestItemsRangeKt {
   }
 
-  public final class LazyPinnableContainerProviderKt {
-  }
-
   public final class LazySaveableStateHolderKt {
   }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
index cb9ca4f..13415a1 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/CoreTextFieldKeyboardScrollableInteractionTest.kt
@@ -55,6 +55,7 @@
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.MediumTest
+import androidx.test.filters.RequiresDevice
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -94,6 +95,7 @@
     private val ListTag = "list"
     private val keyboardHelper = KeyboardHelper(rule)
 
+    @RequiresDevice // b/266736632
     @Test
     fun test() {
         rule.setContent {
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
index c582ec6..aecaba0 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/textfield/TextFieldVisualTransformationSelectionBoundsTest.kt
@@ -184,7 +184,7 @@
         assertValidMessage(error, sourceIndex = 0, toTransformed = false)
     }
 
-    @FlakyTest(bugId = 241572024)
+    @Ignore // b/241572024
     @Test
     fun selectionEnd_throws_onStart_whenInvalidOriginalToTransformed() {
         rule.runOnIdle {
@@ -213,6 +213,7 @@
         assertValidMessage(error, sourceIndex = 0, toTransformed = false)
     }
 
+    @Ignore // b/241572024
     @Test
     fun selectionStart_throws_onDrag_whenInvalidOriginalToTransformed() {
         rule.onNodeWithTag(testTag).performTouchInput { longClick() }
@@ -228,6 +229,7 @@
         assertValidMessage(error, sourceIndex = 0, toTransformed = true)
     }
 
+    @Ignore // b/241572024
     @Test
     fun selectionStart_throws_onDrag_whenInvalidTransformedToOriginal() {
         rule.onNodeWithTag(testTag).performTouchInput { longClick() }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemProvider.kt
index f2b57ef..0691a11 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListItemProvider.kt
@@ -20,7 +20,7 @@
 import androidx.compose.foundation.lazy.layout.DelegatingLazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.IntervalList
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
-import androidx.compose.foundation.lazy.layout.LazyPinnableContainerProvider
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnableItem
 import androidx.compose.foundation.lazy.layout.rememberLazyNearestItemsRangeState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
@@ -80,7 +80,7 @@
         intervals = intervals,
         nearestItemsRange = nearestItemsRange,
         itemContent = { interval, index ->
-            LazyPinnableContainerProvider(state.pinnedItems, index) {
+            LazyLayoutPinnableItem(index, state.pinnedItems) {
                 interval.value.item.invoke(itemScope, index - interval.startIndex)
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo.kt
index 730dab9..be71365 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListLayoutInfo.kt
@@ -82,4 +82,9 @@
      * For example it is a bottom content padding for LazyColumn with reverseLayout set to false.
      */
     val afterContentPadding: Int get() = 0
+
+    /**
+     * The spacing between items in the direction of scrolling.
+     */
+    val mainAxisItemSpacing: Int get() = 0
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
index 5c9ea54..d580e73 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasure.kt
@@ -16,10 +16,11 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.fastFilter
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.lazy.layout.LazyPinnedItem
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
@@ -37,6 +38,7 @@
  * Measures and calculates the positions for the requested items. The result is produced
  * as a [LazyListMeasureResult] which contains all the calculations.
  */
+@OptIn(ExperimentalFoundationApi::class)
 internal fun measureLazyList(
     itemsCount: Int,
     itemProvider: LazyMeasuredItemProvider,
@@ -57,7 +59,7 @@
     placementAnimator: LazyListItemPlacementAnimator,
     beyondBoundsInfo: LazyListBeyondBoundsInfo,
     beyondBoundsItemCount: Int,
-    pinnedItems: List<LazyPinnedItem>,
+    pinnedItems: LazyLayoutPinnedItemList,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyListMeasureResult {
     require(beforeContentPadding >= 0)
@@ -76,7 +78,8 @@
             totalItemsCount = 0,
             reverseLayout = reverseLayout,
             orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            mainAxisItemSpacing = spaceBetweenItems
         )
     } else {
         var currentFirstItemIndex = firstVisibleItemIndex
@@ -327,18 +330,20 @@
             totalItemsCount = itemsCount,
             reverseLayout = reverseLayout,
             orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            mainAxisItemSpacing = spaceBetweenItems
         )
     }
 }
 
+@OptIn(ExperimentalFoundationApi::class)
 private fun createItemsAfterList(
     beyondBoundsInfo: LazyListBeyondBoundsInfo,
     visibleItems: MutableList<LazyMeasuredItem>,
     itemProvider: LazyMeasuredItemProvider,
     itemsCount: Int,
     beyondBoundsItemCount: Int,
-    pinnedItems: List<LazyPinnedItem>
+    pinnedItems: LazyLayoutPinnedItemList
 ): List<LazyMeasuredItem> {
     fun LazyListBeyondBoundsInfo.endIndex() = min(end, itemsCount - 1)
 
@@ -363,22 +368,23 @@
         addItem(i)
     }
 
-    pinnedItems.fastForEach {
-        if (it.index > end && it.index < itemsCount) {
-            addItem(it.index)
+    pinnedItems.fastForEach { item ->
+        if (item.index > end && item.index < itemsCount) {
+            addItem(item.index)
         }
     }
 
     return list ?: emptyList()
 }
 
+@OptIn(ExperimentalFoundationApi::class)
 private fun createItemsBeforeList(
     beyondBoundsInfo: LazyListBeyondBoundsInfo,
     currentFirstItemIndex: DataIndex,
     itemProvider: LazyMeasuredItemProvider,
     itemsCount: Int,
     beyondBoundsItemCount: Int,
-    pinnedItems: List<LazyPinnedItem>
+    pinnedItems: LazyLayoutPinnedItemList
 ): List<LazyMeasuredItem> {
     fun LazyListBeyondBoundsInfo.startIndex() = min(start, itemsCount - 1)
 
@@ -403,9 +409,9 @@
         addItem(i)
     }
 
-    pinnedItems.fastForEach {
-        if (it.index < start) {
-            addItem(it.index)
+    pinnedItems.fastForEach { item ->
+        if (item.index < start) {
+            addItem(item.index)
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
index 00805368..7faf587 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListMeasureResult.kt
@@ -49,7 +49,9 @@
     /** see [LazyListLayoutInfo.orientation] */
     override val orientation: Orientation,
     /** see [LazyListLayoutInfo.afterContentPadding] */
-    override val afterContentPadding: Int
+    override val afterContentPadding: Int,
+    /** see [LazyListLayoutInfo.mainAxisItemSpacing] */
+    override val mainAxisItemSpacing: Int
 ) : LazyListLayoutInfo, MeasureResult by measureResult {
     override val viewportSize: IntSize
         get() = IntSize(width, height)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
index d1507e2..c0756a3 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyListState.kt
@@ -24,7 +24,7 @@
 import androidx.compose.foundation.interaction.InteractionSource
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
-import androidx.compose.foundation.lazy.layout.LazyPinnedItemContainer
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -222,9 +222,9 @@
     internal var premeasureConstraints by mutableStateOf(Constraints())
 
     /**
-     * List of extra items to compose during the measure pass.
+     * Stores currently pinned items which are always composed.
      */
-    internal val pinnedItems = LazyPinnedItemContainer()
+    internal val pinnedItems = LazyLayoutPinnedItemList()
 
     /**
      * Instantly brings the item at [index] to the top of the viewport, offset by [scrollOffset]
@@ -432,6 +432,7 @@
     override val reverseLayout = false
     override val beforeContentPadding = 0
     override val afterContentPadding = 0
+    override val mainAxisItemSpacing = 0
 }
 
 internal class AwaitFirstLayoutModifier : OnGloballyPositionedModifier {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
index 562daad..4f148e7 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridItemProvider.kt
@@ -20,7 +20,7 @@
 import androidx.compose.foundation.lazy.layout.DelegatingLazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.IntervalList
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
-import androidx.compose.foundation.lazy.layout.LazyPinnableContainerProvider
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnableItem
 import androidx.compose.foundation.lazy.layout.rememberLazyNearestItemsRangeState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.State
@@ -88,7 +88,7 @@
     intervals = intervals,
     nearestItemsRange = nearestItemsRange,
     itemContent = { interval, index ->
-        LazyPinnableContainerProvider(state.pinnedItems, index) {
+        LazyLayoutPinnableItem(index, state.pinnedItems) {
             interval.value.item.invoke(LazyGridItemScopeImpl, index - interval.startIndex)
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfo.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfo.kt
index 62a9bfa..a09de86 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfo.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridLayoutInfo.kt
@@ -80,4 +80,9 @@
      * For example it is a bottom content padding for LazyVerticalGrid with reverseLayout set to false.
      */
     val afterContentPadding: Int
+
+    /**
+     * The spacing between lines in the direction of scrolling.
+     */
+    val mainAxisItemSpacing: Int
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
index 12c9769..c08c7d0 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasure.kt
@@ -16,10 +16,11 @@
 
 package androidx.compose.foundation.lazy.grid
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.fastFilter
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.layout.Arrangement
-import androidx.compose.foundation.lazy.layout.LazyPinnedItem
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.ui.layout.MeasureResult
 import androidx.compose.ui.layout.Placeable
 import androidx.compose.ui.unit.Constraints
@@ -38,6 +39,7 @@
  * Measures and calculates the positions for the currently visible items. The result is produced
  * as a [LazyGridMeasureResult] which contains all the calculations.
  */
+@OptIn(ExperimentalFoundationApi::class)
 internal fun measureLazyGrid(
     itemsCount: Int,
     measuredLineProvider: LazyMeasuredLineProvider,
@@ -57,7 +59,7 @@
     density: Density,
     placementAnimator: LazyGridItemPlacementAnimator,
     spanLayoutProvider: LazyGridSpanLayoutProvider,
-    pinnedItems: List<LazyPinnedItem>,
+    pinnedItems: LazyLayoutPinnedItemList,
     layout: (Int, Int, Placeable.PlacementScope.() -> Unit) -> MeasureResult
 ): LazyGridMeasureResult {
     require(beforeContentPadding >= 0)
@@ -76,7 +78,8 @@
             totalItemsCount = 0,
             reverseLayout = reverseLayout,
             orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            mainAxisItemSpacing = spaceBetweenLines
         )
     } else {
         var currentFirstLineIndex = firstVisibleLineIndex
@@ -291,28 +294,30 @@
             totalItemsCount = itemsCount,
             reverseLayout = reverseLayout,
             orientation = if (isVertical) Orientation.Vertical else Orientation.Horizontal,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            mainAxisItemSpacing = spaceBetweenLines
         )
     }
 }
 
+@ExperimentalFoundationApi
 private inline fun calculateExtraItems(
-    pinnedItems: List<LazyPinnedItem>,
+    pinnedItems: LazyLayoutPinnedItemList,
     itemProvider: LazyMeasuredItemProvider,
     itemConstraints: (ItemIndex) -> Constraints,
     filter: (Int) -> Boolean
 ): List<LazyGridMeasuredItem> {
     var items: MutableList<LazyGridMeasuredItem>? = null
 
-    pinnedItems.fastForEach {
-        val index = ItemIndex(it.index)
-        if (filter(it.index)) {
-            val constraints = itemConstraints(index)
-            val item = itemProvider.getAndMeasure(index, constraints = constraints)
+    pinnedItems.fastForEach { item ->
+        if (filter(item.index)) {
+            val itemIndex = ItemIndex(item.index)
+            val constraints = itemConstraints(itemIndex)
+            val measuredItem = itemProvider.getAndMeasure(itemIndex, constraints = constraints)
             if (items == null) {
                 items = mutableListOf()
             }
-            items?.add(item)
+            items?.add(measuredItem)
         }
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasureResult.kt
index 769a7da..00dfd71 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridMeasureResult.kt
@@ -51,7 +51,9 @@
     /** see [LazyGridLayoutInfo.orientation] */
     override val orientation: Orientation,
     /** see [LazyGridLayoutInfo.afterContentPadding] */
-    override val afterContentPadding: Int
+    override val afterContentPadding: Int,
+    /** see [LazyGridLayoutInfo.mainAxisItemSpacing] */
+    override val mainAxisItemSpacing: Int
 ) : LazyGridLayoutInfo, MeasureResult by measureResult {
     override val viewportSize: IntSize
         get() = IntSize(width, height)
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
index 89ac78a..25fd5f5 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/grid/LazyGridState.kt
@@ -25,7 +25,7 @@
 import androidx.compose.foundation.interaction.MutableInteractionSource
 import androidx.compose.foundation.lazy.AwaitFirstLayoutModifier
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
-import androidx.compose.foundation.lazy.layout.LazyPinnedItemContainer
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.Stable
@@ -228,9 +228,9 @@
     private val animateScrollScope = LazyGridAnimateScrollScope(this)
 
     /**
-     * Pinned items are measured and placed even when they are beyond bounds of lazy layout.
+     * Stores currently pinned items which are always composed.
      */
-    internal val pinnedItems = LazyPinnedItemContainer()
+    internal val pinnedItems = LazyLayoutPinnedItemList()
 
     /**
      * Instantly brings the item at [index] to the top of the viewport, offset by [scrollOffset]
@@ -455,4 +455,5 @@
     override val reverseLayout = false
     override val beforeContentPadding: Int = 0
     override val afterContentPadding: Int = 0
+    override val mainAxisItemSpacing = 0
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyPinnableContainerProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
similarity index 64%
rename from compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyPinnableContainerProvider.kt
rename to compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
index 5c22dc1..37bd32f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyPinnableContainerProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutPinnableItem.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.foundation.lazy.layout
 
+import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.DisposableEffect
@@ -28,29 +29,22 @@
 import androidx.compose.ui.layout.LocalPinnableContainer
 import androidx.compose.ui.layout.PinnableContainer
 
-internal interface LazyPinnedItem {
-    val index: Int
-}
-
-internal class LazyPinnedItemContainer(
-    private val pinnedItems: MutableList<LazyPinnedItem> = SnapshotStateList()
-) : List<LazyPinnedItem> by pinnedItems {
-    fun pin(item: LazyPinnedItem) {
-        pinnedItems.add(item)
-    }
-
-    fun unpin(item: LazyPinnedItem) {
-        pinnedItems.remove(item)
-    }
-}
-
+/**
+ * Wrapper supporting [PinnableContainer] in lazy layout items. Each pinned item
+ * is considered important to keep alive even if it would be discarded otherwise.
+ *
+ * @param index current index of the item inside the lazy layout
+ * @param pinnedItemList container to keep currently pinned items
+ * @param content inner content of this item
+ */
+@ExperimentalFoundationApi
 @Composable
-internal fun LazyPinnableContainerProvider(
-    owner: LazyPinnedItemContainer,
+fun LazyLayoutPinnableItem(
     index: Int,
+    pinnedItemList: LazyLayoutPinnedItemList,
     content: @Composable () -> Unit
 ) {
-    val pinnableItem = remember(owner) { LazyPinnableItem(owner) }
+    val pinnableItem = remember(pinnedItemList) { LazyLayoutPinnableItem(pinnedItemList) }
     pinnableItem.index = index
     pinnableItem.parentPinnableContainer = LocalPinnableContainer.current
     DisposableEffect(pinnableItem) { onDispose { pinnableItem.onDisposed() } }
@@ -59,9 +53,43 @@
     )
 }
 
-private class LazyPinnableItem(
-    private val owner: LazyPinnedItemContainer,
-) : PinnableContainer, PinnableContainer.PinnedHandle, LazyPinnedItem {
+/**
+ * Read-only list of pinned items in a lazy layout.
+ * The items are modified internally by the [PinnableContainer] consumers, for example if something
+ * inside item content is focused.
+ */
+@ExperimentalFoundationApi
+class LazyLayoutPinnedItemList private constructor(
+    private val items: MutableList<PinnedItem>
+) : List<LazyLayoutPinnedItemList.PinnedItem> by items {
+    constructor() : this(SnapshotStateList())
+
+    internal fun pin(item: PinnedItem) {
+        items.add(item)
+    }
+
+    internal fun release(item: PinnedItem) {
+        items.remove(item)
+    }
+
+    /**
+     * Item pinned in a lazy layout. Pinned item should be always measured and laid out,
+     * even if the item is beyond the boundaries of the layout.
+     */
+    @ExperimentalFoundationApi
+    sealed interface PinnedItem {
+        /**
+         * Index of the pinned item.
+         * Note: it is possible for index to change during lifetime of the object.
+         */
+        val index: Int
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private class LazyLayoutPinnableItem(
+    private val pinnedItemList: LazyLayoutPinnedItemList,
+) : PinnableContainer, PinnableContainer.PinnedHandle, LazyLayoutPinnedItemList.PinnedItem {
     /**
      * Current index associated with this item.
      */
@@ -99,7 +127,7 @@
 
     override fun pin(): PinnableContainer.PinnedHandle {
         if (pinsCount == 0) {
-            owner.pin(this)
+            pinnedItemList.pin(this)
             parentHandle = parentPinnableContainer?.pin()
         }
         pinsCount++
@@ -110,7 +138,7 @@
         check(pinsCount > 0) { "Release should only be called once" }
         pinsCount--
         if (pinsCount == 0) {
-            owner.unpin(this)
+            pinnedItemList.release(this)
             parentHandle?.release()
             parentHandle = null
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
index 7ce5cff..87cf120 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridItemProvider.kt
@@ -19,7 +19,7 @@
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.lazy.layout.DelegatingLazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
-import androidx.compose.foundation.lazy.layout.LazyPinnableContainerProvider
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnableItem
 import androidx.compose.foundation.lazy.layout.rememberLazyNearestItemsRangeState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.derivedStateOf
@@ -50,7 +50,7 @@
                 scope.intervals,
                 nearestItemsRangeState.value,
                 itemContent = { interval, index ->
-                    LazyPinnableContainerProvider(state.pinnedItems, index) {
+                    LazyLayoutPinnableItem(index, state.pinnedItems) {
                         interval.value.item.invoke(
                             LazyStaggeredGridItemScopeImpl,
                             index - interval.startIndex
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
index c24b674..1c38c19 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasure.kt
@@ -21,7 +21,6 @@
 import androidx.compose.foundation.fastMaxOfOrNull
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutMeasureScope
-import androidx.compose.foundation.lazy.layout.LazyPinnedItem
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.FullSpan
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
 import androidx.compose.runtime.snapshots.Snapshot
@@ -216,6 +215,7 @@
                 viewportEndOffset = mainAxisAvailableSize + afterContentPadding,
                 beforeContentPadding = beforeContentPadding,
                 afterContentPadding = afterContentPadding,
+                mainAxisItemSpacing = mainAxisSpacing
             )
         }
 
@@ -638,7 +638,6 @@
 
         var extraItemOffset = itemScrollOffsets[0]
         val extraItemsBefore = calculateExtraItems(
-            state.pinnedItems,
             position = {
                 extraItemOffset -= it.sizeWithSpacings
                 it.position(0, extraItemOffset, 0)
@@ -662,7 +661,6 @@
 
         extraItemOffset = itemScrollOffsets[0]
         val extraItemsAfter = calculateExtraItems(
-            state.pinnedItems,
             position = {
                 val positionedItem = it.position(0, extraItemOffset, 0)
                 extraItemOffset += it.sizeWithSpacings
@@ -738,7 +736,8 @@
             viewportStartOffset = minOffset,
             viewportEndOffset = maxOffset,
             beforeContentPadding = beforeContentPadding,
-            afterContentPadding = afterContentPadding
+            afterContentPadding = afterContentPadding,
+            mainAxisItemSpacing = mainAxisSpacing
         )
     }
 }
@@ -784,20 +783,21 @@
     return positionedItems
 }
 
+@ExperimentalFoundationApi
 private inline fun LazyStaggeredGridMeasureContext.calculateExtraItems(
-    pinnedItems: List<LazyPinnedItem>,
     position: (LazyStaggeredGridMeasuredItem) -> LazyStaggeredGridPositionedItem,
     filter: (itemIndex: Int) -> Boolean
 ): List<LazyStaggeredGridPositionedItem> {
     var result: MutableList<LazyStaggeredGridPositionedItem>? = null
 
-    pinnedItems.fastForEach {
-        if (filter(it.index)) {
-            val spanRange = itemProvider.getSpanRange(it.index, 0)
+    val pinnedItems = state.pinnedItems
+    pinnedItems.fastForEach { item ->
+        if (filter(item.index)) {
+            val spanRange = itemProvider.getSpanRange(item.index, 0)
             if (result == null) {
                 result = mutableListOf()
             }
-            val measuredItem = measuredItemProvider.getAndMeasure(it.index, spanRange)
+            val measuredItem = measuredItemProvider.getAndMeasure(item.index, spanRange)
             result?.add(position(measuredItem))
         }
     }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
index 352a762..254168f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasureResult.kt
@@ -109,6 +109,11 @@
      * Content padding in pixels applied after the items in scroll direction.
      */
     val afterContentPadding: Int
+
+    /**
+     * The spacing between items in scroll direction.
+     */
+    val mainAxisItemSpacing: Int
 }
 
 @OptIn(ExperimentalFoundationApi::class)
@@ -142,7 +147,8 @@
     override val viewportStartOffset: Int,
     override val viewportEndOffset: Int,
     override val beforeContentPadding: Int,
-    override val afterContentPadding: Int
+    override val afterContentPadding: Int,
+    override val mainAxisItemSpacing: Int
 ) : LazyStaggeredGridLayoutInfo, MeasureResult by measureResult {
     override val orientation: Orientation =
         if (isVertical) Orientation.Vertical else Orientation.Horizontal
@@ -157,5 +163,6 @@
     override val viewportEndOffset: Int = 0
     override val beforeContentPadding: Int = 0
     override val afterContentPadding: Int = 0
+    override val mainAxisItemSpacing: Int = 0
     override val orientation: Orientation = Orientation.Vertical
 }
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
index 82c3e4b..9bada77 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridState.kt
@@ -26,7 +26,7 @@
 import androidx.compose.foundation.lazy.layout.LazyLayoutItemProvider
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState
 import androidx.compose.foundation.lazy.layout.LazyLayoutPrefetchState.PrefetchHandle
-import androidx.compose.foundation.lazy.layout.LazyPinnedItemContainer
+import androidx.compose.foundation.lazy.layout.LazyLayoutPinnedItemList
 import androidx.compose.foundation.lazy.layout.animateScrollToItem
 import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridLaneInfo.Companion.Unset
 import androidx.compose.runtime.Composable
@@ -207,9 +207,9 @@
     internal val mutableInteractionSource = MutableInteractionSource()
 
     /**
-     * List of extra items to compose during the measure pass.
+     * Stores currently pinned items which are always composed.
      */
-    internal val pinnedItems = LazyPinnedItemContainer()
+    internal val pinnedItems = LazyLayoutPinnedItemList()
 
     /**
      * Call this function to take control of scrolling and gain the ability to send scroll events
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
index 56cb761..13a2201 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/pager/PagerState.kt
@@ -434,6 +434,7 @@
     override val viewportStartOffset: Int = 0
     override val viewportEndOffset: Int = 0
     override val totalItemsCount: Int = 0
+    override val mainAxisItemSpacing: Int = 0
 }
 
 private val UnitDensity = object : Density {
diff --git a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
index c969613..fa4fa68 100644
--- a/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
+++ b/compose/material/material-ripple/src/androidMain/kotlin/androidx/compose/material/ripple/RippleHostView.android.kt
@@ -32,6 +32,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.toArgb
 import java.lang.reflect.Method
+import kotlin.math.roundToInt
 
 /**
  * Empty [View] that hosts a [RippleDrawable] as its background. This is needed as
@@ -180,8 +181,8 @@
         val newBounds = Rect(
             0,
             0,
-            size.width.toInt(),
-            size.height.toInt()
+            size.width.roundToInt(),
+            size.height.roundToInt()
         )
         // Drawing the background causes the view to update the bounds of the drawable
         // based on the view's bounds, so we need to adjust the view itself to match the
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index e54f249..b032320 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -985,7 +985,7 @@
 
   public final class PullRefreshKt {
     method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.ui.Modifier pullRefresh(androidx.compose.ui.Modifier, androidx.compose.material.pullrefresh.PullRefreshState state, optional boolean enabled);
-    method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.ui.Modifier pullRefresh(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> onPull, kotlin.jvm.functions.Function2<? super java.lang.Float,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> onRelease, optional boolean enabled);
+    method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.ui.Modifier pullRefresh(androidx.compose.ui.Modifier, kotlin.jvm.functions.Function1<? super java.lang.Float,java.lang.Float> onPull, kotlin.jvm.functions.Function2<? super java.lang.Float,? super kotlin.coroutines.Continuation<? super java.lang.Float>,?> onRelease, optional boolean enabled);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class PullRefreshState {
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/PullRefreshSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/PullRefreshSamples.kt
index 2abcf1f..cc5a0b7 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/PullRefreshSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/PullRefreshSamples.kt
@@ -115,16 +115,25 @@
         }
     }
 
-    suspend fun onRelease() {
-        if (refreshing) return // Already refreshing - don't call refresh again.
+    fun onRelease(velocity: Float): Float {
+        if (refreshing) return 0f // Already refreshing - don't call refresh again.
         if (currentDistance > threshold) refresh()
 
-        animate(initialValue = currentDistance, targetValue = 0f) { value, _ ->
-            currentDistance = value
+        refreshScope.launch {
+            animate(initialValue = currentDistance, targetValue = 0f) { value, _ ->
+                currentDistance = value
+            }
+        }
+
+        // Only consume if the fling is downwards and the indicator is visible
+        return if (velocity > 0f && currentDistance > 0f) {
+            velocity
+        } else {
+            0f
         }
     }
 
-    Box(Modifier.pullRefresh(::onPull, { onRelease() })) {
+    Box(Modifier.pullRefresh(::onPull, ::onRelease)) {
         LazyColumn {
             if (!refreshing) {
                 items(itemCount) {
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt
index 318ea0a..d9c5610 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt
@@ -17,11 +17,17 @@
 package androidx.compose.material.pullrefresh
 
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material.ExperimentalMaterialApi
 import androidx.compose.material.Text
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollDispatcher
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.nestedScroll
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.testTag
@@ -29,11 +35,14 @@
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.abs
 import kotlin.math.pow
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -166,7 +175,7 @@
             assertThat(refreshCount).isEqualTo(0)
         }
 
-        state.onRelease()
+        state.onRelease(0f)
 
         rule.runOnIdle {
             assertThat(state.progress).isEqualTo(0f)
@@ -216,7 +225,7 @@
             assertThat(refreshCount).isEqualTo(0)
         }
 
-        state.onRelease()
+        state.onRelease(0f)
 
         rule.runOnIdle {
             assertThat(state.progress).isEqualTo(0f)
@@ -255,7 +264,7 @@
             assertThat(refreshCount).isEqualTo(0)
         }
 
-        state.onRelease()
+        state.onRelease(0f)
 
         rule.runOnIdle {
             assertThat(state.progress).isEqualTo(0f)
@@ -385,6 +394,633 @@
         }
     }
 
+    @Test
+    fun nestedPreScroll_negativeDelta_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels up
+        val dragUpOffset = Offset(0f, -100f)
+
+        rule.runOnIdle {
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // Pull refresh is not showing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state by a bit
+        state.onPull(200f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(100f /* 200 / 2 for drag multiplier */)
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // Pull refresh is currently showing, so we should consume all the delta
+            assertThat(preConsumed).isEqualTo(dragUpOffset)
+            assertThat(state.position).isEqualTo(50f /* (200 - 100) / 2 for drag multiplier */)
+        }
+    }
+
+    @Test
+    fun nestedPreScroll_negativeDelta_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = { },
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels up
+        val dragUpOffset = Offset(0f, -100f)
+
+        rule.runOnIdle {
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // Pull refresh is refreshing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPreScroll_positiveDelta_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels down
+        val dragUpOffset = Offset(0f, 100f)
+
+        rule.runOnIdle {
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // We should ignore positive delta in prescroll, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state by a bit
+        state.onPull(200f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(100f /* 200 / 2 for drag multiplier */)
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // We should ignore positive delta in prescroll, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(100f /* 200 / 2 for drag multiplier */)
+        }
+    }
+
+    @Test
+    fun nestedPreScroll_positiveDelta_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = { },
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels down
+        val dragUpOffset = Offset(0f, 100f)
+
+        rule.runOnIdle {
+            val preConsumed = dispatcher.dispatchPreScroll(dragUpOffset, NestedScrollSource.Drag)
+            // Pull refresh is refreshing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPostScroll_negativeDelta_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels up
+        val dragUpOffset = Offset(0f, -100f)
+
+        rule.runOnIdle {
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // We should ignore negative delta in postscroll, so we should consume nothing
+            assertThat(postConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state by a bit
+        state.onPull(200f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(100f /* 200 / 2 for drag multiplier */)
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // We should ignore negative delta in postscroll, so we should consume nothing
+            assertThat(postConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(100f /* 200 / 2 for drag multiplier */)
+        }
+    }
+
+    @Test
+    fun nestedPostScroll_negativeDelta_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = { },
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels up
+        val dragUpOffset = Offset(0f, -100f)
+
+        rule.runOnIdle {
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // Pull refresh is refreshing, so we should consume nothing
+            assertThat(postConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPostScroll_positiveDelta_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels down
+        val dragUpOffset = Offset(0f, 100f)
+
+        rule.runOnIdle {
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // We should consume all the delta
+            assertThat(postConsumed).isEqualTo(dragUpOffset)
+            assertThat(state.position).isEqualTo(50f /* 100 / 2 for drag multiplier */)
+        }
+
+        // Pull the state by a bit
+        state.onPull(200f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(150f /* (100 + 200) / 2 for drag multiplier */)
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // We should consume all the delta again
+            assertThat(postConsumed).isEqualTo(dragUpOffset)
+            assertThat(state.position)
+                .isEqualTo(200f /* (100 + 200 + 100) / 2 for drag multiplier */)
+        }
+    }
+
+    @Test
+    fun nestedPostScroll_positiveDelta_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = { },
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // 100 pixels down
+        val dragUpOffset = Offset(0f, 100f)
+
+        rule.runOnIdle {
+            val postConsumed = dispatcher.dispatchPostScroll(
+                Offset.Zero,
+                dragUpOffset,
+                NestedScrollSource.Drag
+            )
+            // Pull refresh is refreshing, so we should consume nothing
+            assertThat(postConsumed).isEqualTo(Offset.Zero)
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPreFling_negativeVelocity_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+        var onRefreshCalled = false
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { onRefreshCalled = true },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // Fling upwards
+        val flingUp = Velocity(0f, -100f)
+
+        rule.runOnIdle {
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingUp) }
+            // Pull refresh is not showing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+            // Not past the threshold, so we shouldn't have called onRefresh
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state but not past the threshold
+        state.onPull(refreshThreshold / 2f)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo(refreshThreshold / 4f /* account for drag multiplier */)
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingUp) }
+            // Upwards fling, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+            // Not past the threshold, so we shouldn't have called onRefresh
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            // Indicator should be reset
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state past the threshold
+        state.onPull(refreshThreshold * 3f)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo(calculateIndicatorPosition(
+                    refreshThreshold * (3 / 2f) /* account for drag multiplier */,
+                    refreshThreshold
+                ))
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingUp) }
+            // Upwards fling, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+            // Past the threshold, so we should call onRefresh
+            assertThat(onRefreshCalled).isTrue()
+        }
+
+        rule.runOnIdle {
+            // Indicator should be reset since we never changed refreshing state
+            assertThat(state.position).isEqualTo(0f)
+        }
+    }
+
+    @Test
+    fun nestedPreFling_negativeVelocity_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = {},
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // Fling upwards
+        val flingUp = Velocity(0f, -100f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(refreshingOffset)
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingUp) }
+            // Currently refreshing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+        }
+
+        rule.runOnIdle {
+            // Shouldn't change position since we are refreshing
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPreFling_positiveVelocity_notRefreshing() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+        var onRefreshCalled = false
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { onRefreshCalled = true },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // Fling downwards
+        val flingDown = Velocity(0f, 100f)
+
+        rule.runOnIdle {
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingDown) }
+            // Pull refresh is not showing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+            // Not past the threshold, so we shouldn't have called onRefresh
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state but not past the threshold
+        state.onPull(refreshThreshold / 2f)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo(refreshThreshold / 4f /* account for drag multiplier */)
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingDown) }
+            // Downwards fling, and we are currently showing, so we should consume all
+            assertThat(preConsumed).isEqualTo(flingDown)
+            // Not past the threshold, so we shouldn't have called onRefresh
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            // Indicator should be reset
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state past the threshold
+        state.onPull(refreshThreshold * 3f)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo(calculateIndicatorPosition(
+                    refreshThreshold * (3 / 2f) /* account for drag multiplier */,
+                    refreshThreshold
+                ))
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingDown) }
+            // Downwards fling, and we are currently showing, so we should consume all
+            assertThat(preConsumed).isEqualTo(flingDown)
+            // Past the threshold, so we should call onRefresh
+            assertThat(onRefreshCalled).isTrue()
+        }
+
+        rule.runOnIdle {
+            // Indicator should be reset since we never changed refreshing state
+            assertThat(state.position).isEqualTo(0f)
+        }
+    }
+
+    @Test
+    fun nestedPreFling_positiveVelocity_refreshing() {
+        val refreshingOffset = 500f
+        lateinit var state: PullRefreshState
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = true,
+                onRefresh = {},
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // Fling downwards
+        val flingUp = Velocity(0f, 100f)
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(refreshingOffset)
+            val preConsumed = runBlocking { dispatcher.dispatchPreFling(flingUp) }
+            // Currently refreshing, so we should consume nothing
+            assertThat(preConsumed).isEqualTo(Velocity.Zero)
+        }
+
+        rule.runOnIdle {
+            // Shouldn't change position since we are refreshing
+            assertThat(state.position).isEqualTo(refreshingOffset)
+        }
+    }
+
+    @Test
+    fun nestedPostFling_noop() {
+        val refreshThreshold = 200f
+        lateinit var state: PullRefreshState
+        var onRefreshCalled = false
+
+        val dispatcher = NestedScrollDispatcher()
+        val connection = object : NestedScrollConnection {}
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                onRefresh = { onRefreshCalled = true },
+                refreshThreshold = with(LocalDensity.current) { refreshThreshold.toDp() }
+            )
+            Box(Modifier.size(200.dp).pullRefresh(state)) {
+                Box(Modifier.size(100.dp).nestedScroll(connection, dispatcher))
+            }
+        }
+
+        // Fling upwards
+        val flingUp = Velocity(0f, 100f)
+        // Fling downwards
+        val flingDown = Velocity(0f, 100f)
+
+        rule.runOnIdle {
+            val postConsumedUp = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingUp)
+            }
+            // Noop
+            assertThat(postConsumedUp).isEqualTo(Velocity.Zero)
+            val postConsumedDown = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingDown)
+            }
+            // Noop
+            assertThat(postConsumedDown).isEqualTo(Velocity.Zero)
+            // Noop
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            assertThat(state.position).isEqualTo(0f)
+        }
+
+        // Pull the state but not past the threshold
+        state.onPull(refreshThreshold / 2f)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo(refreshThreshold / 4f /* account for drag multiplier */)
+            val postConsumedUp = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingUp)
+            }
+            // Noop
+            assertThat(postConsumedUp).isEqualTo(Velocity.Zero)
+            val postConsumedDown = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingDown)
+            }
+            // Noop
+            assertThat(postConsumedDown).isEqualTo(Velocity.Zero)
+            // Noop
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            // Position should stay the same
+            assertThat(state.position)
+                .isEqualTo(refreshThreshold / 4f /* account for drag multiplier */)
+        }
+
+        // Pull the state past the threshold (we have already pulled half of this, so this is now
+        // 1.5 x refreshThreshold for the pull)
+        state.onPull(refreshThreshold)
+
+        rule.runOnIdle {
+            assertThat(state.position)
+                .isEqualTo((refreshThreshold * (3 / 2f)) / 2f /* account for drag multiplier */)
+            val postConsumedUp = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingUp)
+            }
+            // Noop
+            assertThat(postConsumedUp).isEqualTo(Velocity.Zero)
+            val postConsumedDown = runBlocking {
+                dispatcher.dispatchPostFling(Velocity.Zero, flingDown)
+            }
+            // Noop
+            assertThat(postConsumedDown).isEqualTo(Velocity.Zero)
+            // Noop
+            assertThat(onRefreshCalled).isFalse()
+        }
+
+        rule.runOnIdle {
+            // Position should be unchanged
+            assertThat(state.position)
+                .isEqualTo(refreshThreshold * (3 / 2f) / 2f /* account for drag multiplier */)
+        }
+    }
+
     /**
      * Taken from the private function of the same name in [PullRefreshState].
      */
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
index 8a94354..564d874 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
@@ -49,7 +49,7 @@
     properties["state"] = state
     properties["enabled"] = enabled
 }) {
-    Modifier.pullRefresh(state::onPull, { state.onRelease() }, enabled)
+    Modifier.pullRefresh(state::onPull, state::onRelease, enabled)
 }
 
 /**
@@ -64,16 +64,20 @@
  * @param onPull Callback for dispatching vertical scroll delta, takes float pullDelta as argument.
  * Positive delta (pulling down) is dispatched only if the child does not consume it (i.e. pulling
  * down despite being at the top of a scrollable component), whereas negative delta (swiping up) is
- * dispatched first (in case it is needed to push the indicator back up), and then whatever is not
- * consumed is passed on to the child.
+ * dispatched first (in case it is needed to push the indicator back up), and then the unconsumed
+ * delta is passed on to the child. The callback returns how much delta was consumed.
  * @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
+ * The callback returns how much velocity was consumed - in most cases this should only consume
+ * velocity if pull refresh has been dragged already and the velocity is positive (the fling is
+ * downwards), as an upwards fling should typically still scroll a scrollable component beneath the
+ * pullRefresh. This is invoked before any remaining velocity is passed to the child.
  * @param enabled If not enabled, all scroll delta and fling velocity will be ignored and neither
  * [onPull] nor [onRelease] will be invoked.
  */
 @ExperimentalMaterialApi
 fun Modifier.pullRefresh(
     onPull: (pullDelta: Float) -> Float,
-    onRelease: suspend (flingVelocity: Float) -> Unit,
+    onRelease: suspend (flingVelocity: Float) -> Float,
     enabled: Boolean = true
 ) = inspectable(inspectorInfo = debugInspectorInfo {
     name = "pullRefresh"
@@ -86,7 +90,7 @@
 
 private class PullRefreshNestedScrollConnection(
     private val onPull: (pullDelta: Float) -> Float,
-    private val onRelease: suspend (flingVelocity: Float) -> Unit,
+    private val onRelease: suspend (flingVelocity: Float) -> Float,
     private val enabled: Boolean
 ) : NestedScrollConnection {
 
@@ -110,7 +114,6 @@
     }
 
     override suspend fun onPreFling(available: Velocity): Velocity {
-        onRelease(available.y)
-        return Velocity.Zero
+        return Velocity(0f, onRelease(available.y))
     }
 }
\ No newline at end of file
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicator.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicator.kt
index 371e4cd..082cd36 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicator.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicator.kt
@@ -17,6 +17,8 @@
 package androidx.compose.material.pullrefresh
 
 import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.LinearEasing
+import androidx.compose.animation.core.animateFloatAsState
 import androidx.compose.animation.core.tween
 import androidx.compose.foundation.Canvas
 import androidx.compose.foundation.layout.Box
@@ -45,7 +47,6 @@
 import androidx.compose.ui.graphics.drawscope.DrawScope
 import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.drawscope.rotate
-import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.unit.dp
 import kotlin.math.abs
@@ -125,8 +126,18 @@
 ) {
     val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
 
-    Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
+    val targetAlpha by remember(state) {
+        derivedStateOf {
+            if (state.progress >= 1f) MaxAlpha else MinAlpha
+        }
+    }
+
+    val alphaState = animateFloatAsState(targetValue = targetAlpha, animationSpec = AlphaTween)
+
+    // Empty semantics for tests
+    Canvas(modifier.semantics {}) {
         val values = ArrowValues(state.progress)
+        val alpha = alphaState.value
 
         rotate(degrees = values.rotation) {
             val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
@@ -138,7 +149,7 @@
             )
             drawArc(
                 color = color,
-                alpha = values.alpha,
+                alpha = alpha,
                 startAngle = values.startAngle,
                 sweepAngle = values.endAngle - values.startAngle,
                 useCenter = false,
@@ -149,14 +160,13 @@
                     cap = StrokeCap.Square
                 )
             )
-            drawArrow(path, arcBounds, color, values)
+            drawArrow(path, arcBounds, color, alpha, values)
         }
     }
 }
 
 @Immutable
 private class ArrowValues(
-    val alpha: Float,
     val rotation: Float,
     val startAngle: Float,
     val endAngle: Float,
@@ -174,17 +184,22 @@
     val tensionPercent = linearTension - linearTension.pow(2) / 4
 
     // Calculations based on SwipeRefreshLayout specification.
-    val alpha = progress.coerceIn(0f, 1f)
     val endTrim = adjustedPercent * MaxProgressArc
     val rotation = (-0.25f + 0.4f * adjustedPercent + tensionPercent) * 0.5f
     val startAngle = rotation * 360
     val endAngle = (rotation + endTrim) * 360
     val scale = min(1f, adjustedPercent)
 
-    return ArrowValues(alpha, rotation, startAngle, endAngle, scale)
+    return ArrowValues(rotation, startAngle, endAngle, scale)
 }
 
-private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, values: ArrowValues) {
+private fun DrawScope.drawArrow(
+    arrow: Path,
+    bounds: Rect,
+    color: Color,
+    alpha: Float,
+    values: ArrowValues
+) {
     arrow.reset()
     arrow.moveTo(0f, 0f) // Move to left corner
     arrow.lineTo(x = ArrowWidth.toPx() * values.scale, y = 0f) // Line to right corner
@@ -205,7 +220,7 @@
     )
     arrow.close()
     rotate(degrees = values.endAngle) {
-        drawPath(path = arrow, color = color, alpha = values.alpha)
+        drawPath(path = arrow, color = color, alpha = alpha)
     }
 }
 
@@ -219,3 +234,8 @@
 private val ArrowWidth = 10.dp
 private val ArrowHeight = 5.dp
 private val Elevation = 6.dp
+
+// Values taken from SwipeRefreshLayout
+private const val MinAlpha = 0.3f
+private const val MaxAlpha = 1f
+private val AlphaTween = tween<Float>(300, easing = LinearEasing)
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt
index 8249ba1..96983f1 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt
@@ -136,14 +136,25 @@
         return dragConsumed
     }
 
-    internal fun onRelease() {
-        if (!_refreshing) {
-            if (adjustedDistancePulled > threshold) {
-                onRefreshState.value()
-            }
-            animateIndicatorTo(0f)
+    internal fun onRelease(velocity: Float): Float {
+        if (refreshing) return 0f // Already refreshing, do nothing
+
+        if (adjustedDistancePulled > threshold) {
+            onRefreshState.value()
+        }
+        animateIndicatorTo(0f)
+        val consumed = when {
+            // We are flinging without having dragged the pull refresh (for example a fling inside
+            // a list) - don't consume
+            distancePulled == 0f -> 0f
+            // If the velocity is negative, the fling is upwards, and we don't want to prevent the
+            // the list from scrolling
+            velocity < 0f -> 0f
+            // We are showing the indicator, and the fling is downwards - consume everything
+            else -> velocity
         }
         distancePulled = 0f
+        return consumed
     }
 
     internal fun setRefreshing(refreshing: Boolean) {
diff --git a/compose/material3/material3/api/current.txt b/compose/material3/material3/api/current.txt
index b56b591..5508abe 100644
--- a/compose/material3/material3/api/current.txt
+++ b/compose/material3/material3/api/current.txt
@@ -241,6 +241,9 @@
   public final class DatePickerKt {
   }
 
+  public final class DateRangePickerKt {
+  }
+
   public final class DividerDefaults {
     method @androidx.compose.runtime.Composable public long getColor();
     method public float getThickness();
@@ -397,7 +400,24 @@
     method public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
   }
 
+  @androidx.compose.runtime.Immutable public final class ListItemColors {
+  }
+
+  public final class ListItemDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
+    method @androidx.compose.runtime.Composable public long getContainerColor();
+    method @androidx.compose.runtime.Composable public long getContentColor();
+    method public float getElevation();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long containerColor;
+    property @androidx.compose.runtime.Composable public final long contentColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
+    field public static final androidx.compose.material3.ListItemDefaults INSTANCE;
+  }
+
   public final class ListItemKt {
+    method @androidx.compose.runtime.Composable public static void ListItem(kotlin.jvm.functions.Function0<kotlin.Unit> headlineText, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional androidx.compose.material3.ListItemColors colors, optional float tonalElevation, optional float shadowElevation);
   }
 
   public final class MaterialTheme {
@@ -427,6 +447,9 @@
   public final class MenuKt {
   }
 
+  public final class ModalBottomSheetKt {
+  }
+
   public final class NavigationBarDefaults {
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method public float getElevation();
@@ -600,6 +623,9 @@
   public final class ShapesKt {
   }
 
+  public final class SheetDefaultsKt {
+  }
+
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
@@ -776,6 +802,28 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  public final class TimeFormat_androidKt {
+  }
+
+  public final class TimePickerKt {
+  }
+
+  @androidx.compose.runtime.Stable public final class TimePickerState {
+    ctor public TimePickerState(int initialHour, int initialMinute, boolean is24Hour);
+    method public int getHour();
+    method public int getMinute();
+    method public boolean is24hour();
+    method public suspend Object? settle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final int hour;
+    property public final boolean is24hour;
+    property public final int minute;
+    field public static final androidx.compose.material3.TimePickerState.Companion Companion;
+  }
+
+  public static final class TimePickerState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
+  }
+
   public final class TonalPaletteKt {
   }
 
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index ff21164..ad3d5cf 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -81,6 +81,21 @@
     field public static final androidx.compose.material3.BottomAppBarDefaults INSTANCE;
   }
 
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class BottomSheetDefaults {
+    method @androidx.compose.runtime.Composable public void DragHandle(optional androidx.compose.ui.Modifier modifier, optional float width, optional float height, optional androidx.compose.ui.graphics.Shape shape, optional long color);
+    method @androidx.compose.runtime.Composable public long getContainerColor();
+    method public float getElevation();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getExpandedShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getMinimizedShape();
+    method @androidx.compose.runtime.Composable public long getScrimColor();
+    property @androidx.compose.runtime.Composable public final long ContainerColor;
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape ExpandedShape;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape MinimizedShape;
+    property @androidx.compose.runtime.Composable public final long ScrimColor;
+    field public static final androidx.compose.material3.BottomSheetDefaults INSTANCE;
+  }
+
   @androidx.compose.runtime.Immutable public final class ButtonColors {
   }
 
@@ -281,14 +296,7 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.graphics.Color> LocalContentColor;
   }
 
-  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class DateInputDefaults {
-    method @androidx.compose.runtime.Composable public void DateInputHeadline(androidx.compose.material3.DatePickerState state, androidx.compose.material3.DatePickerFormatter dateFormatter);
-    method @androidx.compose.runtime.Composable public void DateInputTitle();
-    field public static final androidx.compose.material3.DateInputDefaults INSTANCE;
-  }
-
   public final class DateInputKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DateInput(androidx.compose.material3.DatePickerState dateInputState, 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 androidx.compose.material3.DatePickerColors colors);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class DatePickerColors {
@@ -296,7 +304,7 @@
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class DatePickerDefaults {
     method @androidx.compose.runtime.Composable public void DatePickerHeadline(androidx.compose.material3.DatePickerState state, androidx.compose.material3.DatePickerFormatter dateFormatter);
-    method @androidx.compose.runtime.Composable public void DatePickerTitle();
+    method @androidx.compose.runtime.Composable public void DatePickerTitle(androidx.compose.material3.DatePickerState state);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.DatePickerColors colors(optional long containerColor, optional long titleContentColor, optional long headlineContentColor, optional long weekdayContentColor, optional long subheadContentColor, optional long yearContentColor, optional long currentYearContentColor, optional long selectedYearContentColor, optional long selectedYearContainerColor, optional long dayContentColor, optional long disabledDayContentColor, optional long selectedDayContentColor, optional long disabledSelectedDayContentColor, optional long selectedDayContainerColor, optional long disabledSelectedDayContainerColor, optional long todayContentColor, optional long todayDateBorderColor);
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
     method public float getTonalElevation();
@@ -319,16 +327,17 @@
   }
 
   public final class DatePickerKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void DatePicker(androidx.compose.material3.DatePickerState datePickerState, 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 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);
+    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);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class DatePickerState {
-    ctor public DatePickerState(Long? initialSelectedDateMillis, Long? initialDisplayedMonthMillis, kotlin.ranges.IntRange yearRange);
+    ctor public DatePickerState(Long? initialSelectedDateMillis, Long? initialDisplayedMonthMillis, kotlin.ranges.IntRange yearRange, int initialDisplayMode);
+    method public int getDisplayMode();
     method public Long? getSelectedDateMillis();
-    method public kotlin.ranges.IntRange getYearRange();
+    method public void setDisplayMode(int);
+    property public final int displayMode;
     property public final Long? selectedDateMillis;
-    property public final kotlin.ranges.IntRange yearRange;
     field public static final androidx.compose.material3.DatePickerState.Companion Companion;
   }
 
@@ -336,6 +345,9 @@
     method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.DatePickerState,?> Saver();
   }
 
+  public final class DateRangePickerKt {
+  }
+
   @androidx.compose.material3.ExperimentalMaterial3Api public enum DismissDirection {
     method public static androidx.compose.material3.DismissDirection valueOf(String name) throws java.lang.IllegalArgumentException;
     method public static androidx.compose.material3.DismissDirection[] values();
@@ -373,6 +385,17 @@
     enum_constant public static final androidx.compose.material3.DismissValue DismissedToStart;
   }
 
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @kotlin.jvm.JvmInline public final value class DisplayMode {
+    field public static final androidx.compose.material3.DisplayMode.Companion Companion;
+  }
+
+  public static final class DisplayMode.Companion {
+    method public int getInput();
+    method public int getPicker();
+    property public final int Input;
+    property public final int Picker;
+  }
+
   public final class DividerDefaults {
     method @androidx.compose.runtime.Composable public long getColor();
     method public float getThickness();
@@ -593,10 +616,10 @@
     property @Deprecated @androidx.compose.material3.ExperimentalMaterial3Api public static final androidx.compose.runtime.ProvidableCompositionLocal<java.lang.Boolean> LocalMinimumTouchTargetEnforcement;
   }
 
-  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class ListItemColors {
+  @androidx.compose.runtime.Immutable public final class ListItemColors {
   }
 
-  @androidx.compose.material3.ExperimentalMaterial3Api public final class ListItemDefaults {
+  public final class ListItemDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method @androidx.compose.runtime.Composable public long getContentColor();
@@ -610,7 +633,7 @@
   }
 
   public final class ListItemKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ListItem(kotlin.jvm.functions.Function0<kotlin.Unit> headlineText, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional androidx.compose.material3.ListItemColors colors, optional float tonalElevation, optional float shadowElevation);
+    method @androidx.compose.runtime.Composable public static void ListItem(kotlin.jvm.functions.Function0<kotlin.Unit> headlineText, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional androidx.compose.material3.ListItemColors colors, optional float tonalElevation, optional float shadowElevation);
   }
 
   public final class MaterialTheme {
@@ -640,6 +663,10 @@
   public final class MenuKt {
   }
 
+  public final class ModalBottomSheetKt {
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void ModalBottomSheet(kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.SheetState sheetState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, optional float tonalElevation, optional long scrimColor, optional kotlin.jvm.functions.Function0<kotlin.Unit>? dragHandle, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
+  }
+
   public final class NavigationBarDefaults {
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method public float getElevation();
@@ -716,6 +743,14 @@
     method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void OutlinedTextField(androidx.compose.ui.text.input.TextFieldValue value, kotlin.jvm.functions.Function1<? super androidx.compose.ui.text.input.TextFieldValue,kotlin.Unit> onValueChange, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional boolean readOnly, optional androidx.compose.ui.text.TextStyle textStyle, optional kotlin.jvm.functions.Function0<kotlin.Unit>? label, optional kotlin.jvm.functions.Function0<kotlin.Unit>? placeholder, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingIcon, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional boolean isError, optional androidx.compose.ui.text.input.VisualTransformation visualTransformation, optional androidx.compose.foundation.text.KeyboardOptions keyboardOptions, optional androidx.compose.foundation.text.KeyboardActions keyboardActions, optional boolean singleLine, optional int maxLines, optional int minLines, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.TextFieldColors colors);
   }
 
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class PlainTooltipState {
+    ctor public PlainTooltipState();
+    method public suspend Object? dismiss(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isVisible();
+    method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean isVisible;
+  }
+
   public final class ProgressIndicatorDefaults {
     method @androidx.compose.runtime.Composable public long getCircularColor();
     method public int getCircularDeterminateStrokeCap();
@@ -761,6 +796,26 @@
     method @androidx.compose.runtime.Composable public static void RadioButton(boolean selected, kotlin.jvm.functions.Function0<kotlin.Unit>? onClick, optional androidx.compose.ui.Modifier modifier, optional boolean enabled, optional androidx.compose.material3.RadioButtonColors colors, optional androidx.compose.foundation.interaction.MutableInteractionSource interactionSource);
   }
 
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable @androidx.compose.runtime.Stable public final class RichTooltipColors {
+    ctor public RichTooltipColors(long containerColor, long contentColor, long titleContentColor, long actionContentColor);
+    method public long getActionContentColor();
+    method public long getContainerColor();
+    method public long getContentColor();
+    method public long getTitleContentColor();
+    property public final long actionContentColor;
+    property public final long containerColor;
+    property public final long contentColor;
+    property public final long titleContentColor;
+  }
+
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class RichTooltipState {
+    ctor public RichTooltipState();
+    method public suspend Object? dismiss(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isVisible();
+    method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public boolean isVisible;
+  }
+
   @androidx.compose.material3.ExperimentalMaterial3Api public final class ScaffoldDefaults {
     method @androidx.compose.runtime.Composable public androidx.compose.foundation.layout.WindowInsets getContentWindowInsets();
     property @androidx.compose.runtime.Composable public final androidx.compose.foundation.layout.WindowInsets contentWindowInsets;
@@ -844,6 +899,37 @@
   public final class ShapesKt {
   }
 
+  public final class SheetDefaultsKt {
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.SheetState rememberSheetState(optional boolean skipHalfExpanded, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+  }
+
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class SheetState {
+    ctor public SheetState(boolean skipCollapsed, optional androidx.compose.material3.SheetValue initialValue, optional kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+    method public suspend Object? collapse(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public androidx.compose.material3.SheetValue getCurrentValue();
+    method public androidx.compose.material3.SheetValue getTargetValue();
+    method public suspend Object? hide(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public boolean isVisible();
+    method public float requireOffset();
+    method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final androidx.compose.material3.SheetValue currentValue;
+    property public final boolean isVisible;
+    property public final androidx.compose.material3.SheetValue targetValue;
+    field public static final androidx.compose.material3.SheetState.Companion Companion;
+  }
+
+  public static final class SheetState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.SheetState,androidx.compose.material3.SheetValue> Saver(boolean skipHalfExpanded, kotlin.jvm.functions.Function1<? super androidx.compose.material3.SheetValue,java.lang.Boolean> confirmValueChange);
+  }
+
+  @androidx.compose.material3.ExperimentalMaterial3Api public enum SheetValue {
+    method public static androidx.compose.material3.SheetValue valueOf(String name) throws java.lang.IllegalArgumentException;
+    method public static androidx.compose.material3.SheetValue[] values();
+    enum_constant public static final androidx.compose.material3.SheetValue Collapsed;
+    enum_constant public static final androidx.compose.material3.SheetValue Expanded;
+    enum_constant public static final androidx.compose.material3.SheetValue Hidden;
+  }
+
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
@@ -1078,6 +1164,38 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  public final class TimeFormat_androidKt {
+  }
+
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Immutable public final class TimePickerColors {
+  }
+
+  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TimePickerDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.TimePickerColors colors(optional long clockDialColor, optional long clockDialSelectedContentColor, optional long clockDialUnselectedContentColor, optional long selectorColor, optional long containerColor, optional long periodSelectorBorderColor, optional long periodSelectorSelectedContainerColor, optional long periodSelectorUnselectedContainerColor, optional long periodSelectorSelectedContentColor, optional long periodSelectorUnselectedContentColor, optional long timeSelectorSelectedContainerColor, optional long timeSelectorUnselectedContainerColor, optional long timeSelectorSelectedContentColor, optional long timeSelectorUnselectedContentColor);
+    field public static final androidx.compose.material3.TimePickerDefaults INSTANCE;
+  }
+
+  public final class TimePickerKt {
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void TimePicker(androidx.compose.material3.TimePickerState state, optional androidx.compose.material3.TimePickerColors colors);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static androidx.compose.material3.TimePickerState rememberTimePickerState(optional int initialHour, optional int initialMinute, optional boolean is24Hour);
+  }
+
+  @androidx.compose.runtime.Stable public final class TimePickerState {
+    ctor public TimePickerState(int initialHour, int initialMinute, boolean is24Hour);
+    method public int getHour();
+    method public int getMinute();
+    method public boolean is24hour();
+    method public suspend Object? settle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final int hour;
+    property public final boolean is24hour;
+    property public final int minute;
+    field public static final androidx.compose.material3.TimePickerState.Companion Companion;
+  }
+
+  public static final class TimePickerState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
+  }
+
   public final class TonalPaletteKt {
   }
 
@@ -1089,22 +1207,18 @@
     method @androidx.compose.runtime.Composable public long getPlainTooltipContainerColor();
     method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getPlainTooltipContainerShape();
     method @androidx.compose.runtime.Composable public long getPlainTooltipContentColor();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getRichTooltipContainerShape();
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.RichTooltipColors richTooltipColors(optional long containerColor, optional long contentColor, optional long titleContentColor, optional long actionContentColor);
     property @androidx.compose.runtime.Composable public final long plainTooltipContainerColor;
     property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape plainTooltipContainerShape;
     property @androidx.compose.runtime.Composable public final long plainTooltipContentColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape richTooltipContainerShape;
     field public static final androidx.compose.material3.TooltipDefaults INSTANCE;
   }
 
   public final class TooltipKt {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.TooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
-  }
-
-  @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TooltipState {
-    ctor public TooltipState();
-    method public suspend Object? dismiss(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    method public boolean isVisible();
-    method public suspend Object? show(kotlin.coroutines.Continuation<? super kotlin.Unit>);
-    property public final boolean isVisible;
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void PlainTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> tooltip, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.PlainTooltipState tooltipState, optional androidx.compose.ui.graphics.Shape shape, optional long containerColor, optional long contentColor, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public static void RichTooltipBox(kotlin.jvm.functions.Function0<kotlin.Unit> text, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.material3.RichTooltipState tooltipState, optional kotlin.jvm.functions.Function0<kotlin.Unit>? title, optional kotlin.jvm.functions.Function0<kotlin.Unit>? action, optional androidx.compose.ui.graphics.Shape shape, optional androidx.compose.material3.RichTooltipColors colors, kotlin.jvm.functions.Function1<? super androidx.compose.material3.TooltipBoxScope,kotlin.Unit> content);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Stable public final class TopAppBarColors {
diff --git a/compose/material3/material3/api/restricted_current.txt b/compose/material3/material3/api/restricted_current.txt
index b56b591..5508abe 100644
--- a/compose/material3/material3/api/restricted_current.txt
+++ b/compose/material3/material3/api/restricted_current.txt
@@ -241,6 +241,9 @@
   public final class DatePickerKt {
   }
 
+  public final class DateRangePickerKt {
+  }
+
   public final class DividerDefaults {
     method @androidx.compose.runtime.Composable public long getColor();
     method public float getThickness();
@@ -397,7 +400,24 @@
     method public static androidx.compose.ui.Modifier minimumInteractiveComponentSize(androidx.compose.ui.Modifier);
   }
 
+  @androidx.compose.runtime.Immutable public final class ListItemColors {
+  }
+
+  public final class ListItemDefaults {
+    method @androidx.compose.runtime.Composable public androidx.compose.material3.ListItemColors colors(optional long containerColor, optional long headlineColor, optional long leadingIconColor, optional long overlineColor, optional long supportingColor, optional long trailingIconColor, optional long disabledHeadlineColor, optional long disabledLeadingIconColor, optional long disabledTrailingIconColor);
+    method @androidx.compose.runtime.Composable public long getContainerColor();
+    method @androidx.compose.runtime.Composable public long getContentColor();
+    method public float getElevation();
+    method @androidx.compose.runtime.Composable public androidx.compose.ui.graphics.Shape getShape();
+    property public final float Elevation;
+    property @androidx.compose.runtime.Composable public final long containerColor;
+    property @androidx.compose.runtime.Composable public final long contentColor;
+    property @androidx.compose.runtime.Composable public final androidx.compose.ui.graphics.Shape shape;
+    field public static final androidx.compose.material3.ListItemDefaults INSTANCE;
+  }
+
   public final class ListItemKt {
+    method @androidx.compose.runtime.Composable public static void ListItem(kotlin.jvm.functions.Function0<kotlin.Unit> headlineText, optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit>? overlineText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? supportingText, optional kotlin.jvm.functions.Function0<kotlin.Unit>? leadingContent, optional kotlin.jvm.functions.Function0<kotlin.Unit>? trailingContent, optional androidx.compose.material3.ListItemColors colors, optional float tonalElevation, optional float shadowElevation);
   }
 
   public final class MaterialTheme {
@@ -427,6 +447,9 @@
   public final class MenuKt {
   }
 
+  public final class ModalBottomSheetKt {
+  }
+
   public final class NavigationBarDefaults {
     method @androidx.compose.runtime.Composable public long getContainerColor();
     method public float getElevation();
@@ -600,6 +623,9 @@
   public final class ShapesKt {
   }
 
+  public final class SheetDefaultsKt {
+  }
+
   @androidx.compose.runtime.Immutable public final class SliderColors {
   }
 
@@ -776,6 +802,28 @@
     property public static final androidx.compose.runtime.ProvidableCompositionLocal<androidx.compose.ui.text.TextStyle> LocalTextStyle;
   }
 
+  public final class TimeFormat_androidKt {
+  }
+
+  public final class TimePickerKt {
+  }
+
+  @androidx.compose.runtime.Stable public final class TimePickerState {
+    ctor public TimePickerState(int initialHour, int initialMinute, boolean is24Hour);
+    method public int getHour();
+    method public int getMinute();
+    method public boolean is24hour();
+    method public suspend Object? settle(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    property public final int hour;
+    property public final boolean is24hour;
+    property public final int minute;
+    field public static final androidx.compose.material3.TimePickerState.Companion Companion;
+  }
+
+  public static final class TimePickerState.Companion {
+    method public androidx.compose.runtime.saveable.Saver<androidx.compose.material3.TimePickerState,?> Saver();
+  }
+
   public final class TonalPaletteKt {
   }
 
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
index 2255feb..100d571 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Components.kt
@@ -69,6 +69,18 @@
     examples = BottomAppBarsExamples
 )
 
+private val BottomSheets = Component(
+    id = nextId(),
+    name = "Bottom Sheet",
+    description = "Bottom sheets are surfaces containing supplementary content, anchored to the " +
+        "bottom of the screen.",
+    // No bottom sheet icon
+    guidelinesUrl = "$ComponentGuidelinesUrl/bottom-sheets",
+    docsUrl = "$DocsUrl#bottomsheet",
+    sourceUrl = "$Material3SourceUrl/ModalBottomSheet.kt",
+    examples = BottomSheetExamples
+)
+
 private val Buttons = Component(
     id = nextId(),
     name = "Buttons",
@@ -335,6 +347,17 @@
     examples = TooltipsExamples
 )
 
+private val TimePickers = Component(
+    id = nextId(),
+    name = "Time Picker",
+    description = "Time picker allows the user to choose time of day.",
+    // No time picker icon
+    guidelinesUrl = "$ComponentGuidelinesUrl/time-picker",
+    docsUrl = "$DocsUrl#time-pickers",
+    sourceUrl = "$Material3SourceUrl/TimePicker.kt",
+    examples = TimePickerExamples
+)
+
 private val TopAppBar = Component(
     id = nextId(),
     name = "Top app bar",
@@ -350,6 +373,7 @@
 val Components = listOf(
     Badge,
     BottomAppBars,
+    BottomSheets,
     Buttons,
     Card,
     Checkboxes,
@@ -373,5 +397,6 @@
     Tabs,
     TextFields,
     Tooltips,
+    TimePickers,
     TopAppBar
 )
diff --git a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
index 94fceef..f264ce1 100644
--- a/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
+++ b/compose/material3/material3/integration-tests/material3-catalog/src/main/java/androidx/compose/material3/catalog/library/model/Examples.kt
@@ -28,6 +28,7 @@
 import androidx.compose.material3.samples.AnimatedExtendedFloatingActionButtonSample
 import androidx.compose.material3.samples.AssistChipSample
 import androidx.compose.material3.samples.BottomAppBarWithFAB
+import androidx.compose.material3.samples.ModalBottomSheetSample
 import androidx.compose.material3.samples.ButtonSample
 import androidx.compose.material3.samples.ButtonWithIconSample
 import androidx.compose.material3.samples.CardSample
@@ -38,7 +39,6 @@
 import androidx.compose.material3.samples.ClickableCardSample
 import androidx.compose.material3.samples.ClickableElevatedCardSample
 import androidx.compose.material3.samples.ClickableOutlinedCardSample
-import androidx.compose.material3.samples.DateInputDialogSample
 import androidx.compose.material3.samples.DateInputSample
 import androidx.compose.material3.samples.DatePickerDialogSample
 import androidx.compose.material3.samples.DatePickerSample
@@ -101,6 +101,8 @@
 import androidx.compose.material3.samples.RadioGroupSample
 import androidx.compose.material3.samples.RangeSliderSample
 import androidx.compose.material3.samples.RangeSliderWithCustomComponents
+import androidx.compose.material3.samples.RichTooltipSample
+import androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
 import androidx.compose.material3.samples.ScaffoldWithCoroutinesSnackbar
 import androidx.compose.material3.samples.ScaffoldWithCustomSnackbar
 import androidx.compose.material3.samples.ScaffoldWithIndefiniteSnackbar
@@ -133,6 +135,7 @@
 import androidx.compose.material3.samples.TextFieldWithSupportingText
 import androidx.compose.material3.samples.TextTabs
 import androidx.compose.material3.samples.ThreeLineListItem
+import androidx.compose.material3.samples.TimePickerSample
 import androidx.compose.material3.samples.TriStateCheckboxSample
 import androidx.compose.material3.samples.TwoLineListItem
 import androidx.compose.runtime.Composable
@@ -157,6 +160,17 @@
         ) { NavigationBarItemWithBadge() }
     )
 
+private const val BottomSheetExampleDescription = "Bottom Sheet examples"
+private const val BottomSheetExampleSourceUrl = "$SampleSourceUrl/BottomSheetSamples.kt"
+val BottomSheetExamples =
+    listOf(
+        Example(
+            name = ::ModalBottomSheetSample.name,
+            description = BottomSheetExampleDescription,
+            sourceUrl = BottomSheetExampleSourceUrl
+        ) { ModalBottomSheetSample() }
+    )
+
 private const val ButtonsExampleDescription = "Button examples"
 private const val ButtonsExampleSourceUrl = "$SampleSourceUrl/ButtonSamples.kt"
 val ButtonsExamples =
@@ -372,13 +386,6 @@
     ) {
         DateInputSample()
     },
-    Example(
-        name = ::DateInputDialogSample.name,
-        description = DatePickerExampleDescription,
-        sourceUrl = DatePickerExampleSourceUrl
-    ) {
-        DateInputDialogSample()
-    },
 )
 
 private const val DialogExampleDescription = "Dialog examples"
@@ -904,6 +911,18 @@
     }
 )
 
+private const val TimePickerDescription = "Time Picker examples"
+private const val TimePickerSourceUrl = "$SampleSourceUrl/TimePicker.kt"
+val TimePickerExamples = listOf(
+    Example(
+        name = ::TimePickerSample.name,
+        description = TimePickerDescription,
+        sourceUrl = TimePickerSourceUrl
+    ) {
+        TimePickerSample()
+    },
+)
+
 private const val TextFieldsExampleDescription = "Text fields examples"
 private const val TextFieldsExampleSourceUrl = "$SampleSourceUrl/TextFieldSamples.kt"
 val TextFieldsExamples = listOf(
@@ -1013,5 +1032,19 @@
         sourceUrl = TooltipsExampleSourceUrl
     ) {
         PlainTooltipWithManualInvocationSample()
+    },
+    Example(
+        name = ::RichTooltipSample.name,
+        description = TooltipsExampleDescription,
+        sourceUrl = TooltipsExampleSourceUrl
+    ) {
+        RichTooltipSample()
+    },
+    Example(
+        name = ::RichTooltipWithManualInvocationSample.name,
+        description = TooltipsExampleDescription,
+        sourceUrl = TooltipsExampleSourceUrl
+    ) {
+        RichTooltipWithManualInvocationSample()
     }
 )
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.kt
new file mode 100644
index 0000000..ba4e86b
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/BottomSheetSamples.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.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.selection.toggleable
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material3.Button
+import androidx.compose.material3.Checkbox
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.Icon
+import androidx.compose.material3.ListItem
+import androidx.compose.material3.ModalBottomSheet
+import androidx.compose.material3.Text
+import androidx.compose.material3.rememberSheetState
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.launch
+
+@Preview
+@Sampled
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun ModalBottomSheetSample() {
+    var openBottomSheet by rememberSaveable { mutableStateOf(false) }
+    var skipHalfExpanded by remember { mutableStateOf(false) }
+    val scope = rememberCoroutineScope()
+    val bottomSheetState = rememberSheetState(skipHalfExpanded = skipHalfExpanded)
+
+    // App content
+    Column(
+        modifier = Modifier.fillMaxSize(),
+        horizontalAlignment = Alignment.CenterHorizontally,
+        verticalArrangement = Arrangement.Center
+    ) {
+        Row(
+            Modifier.toggleable(
+                value = skipHalfExpanded,
+                role = Role.Checkbox,
+                onValueChange = { checked -> skipHalfExpanded = checked }
+            )
+        ) {
+            Checkbox(checked = skipHalfExpanded, onCheckedChange = null)
+            Spacer(Modifier.width(16.dp))
+            Text("Skip Half Expanded State")
+        }
+        Button(onClick = { openBottomSheet = !openBottomSheet }) {
+            Text(text = "Show Bottom Sheet")
+        }
+    }
+
+    // Sheet content
+    if (openBottomSheet) {
+        ModalBottomSheet(
+            onDismissRequest = { openBottomSheet = false },
+            sheetState = bottomSheetState,
+        ) {
+            Row(Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.Center) {
+                Button(
+                    // Note: If you provide logic outside of onDismissRequest to remove the sheet,
+                    // you must additionally handle intended state cleanup, if any.
+                    onClick = {
+                        scope.launch { bottomSheetState.hide() }.invokeOnCompletion {
+                            if (!bottomSheetState.isVisible) {
+                                openBottomSheet = false
+                            }
+                        }
+                    }
+                ) {
+                    Text("Hide Bottom Sheet")
+                }
+            }
+            LazyColumn {
+                items(50) {
+                    ListItem(
+                        headlineText = { Text("Item $it") },
+                        leadingContent = {
+                            Icon(
+                                Icons.Default.Favorite,
+                                contentDescription = "Localized description"
+                            )
+                        }
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
index b1f9b9c..b3b24eb 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/DatePickerSamples.kt
@@ -20,9 +20,9 @@
 import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.DateInput
 import androidx.compose.material3.DatePicker
 import androidx.compose.material3.DatePickerDialog
+import androidx.compose.material3.DisplayMode
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.SnackbarHost
 import androidx.compose.material3.SnackbarHostState
@@ -52,7 +52,7 @@
     Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
         // Pre-select a date for January 4, 2020
         val datePickerState = rememberDatePickerState(initialSelectedDateMillis = 1578096000000)
-        DatePicker(datePickerState = datePickerState, modifier = Modifier.padding(16.dp))
+        DatePicker(state = datePickerState, modifier = Modifier.padding(16.dp))
 
         Text("Selected date timestamp: ${datePickerState.selectedDateMillis ?: "no selection"}")
     }
@@ -104,7 +104,7 @@
                 }
             }
         ) {
-            DatePicker(datePickerState = datePickerState)
+            DatePicker(state = datePickerState)
         }
     }
 }
@@ -118,7 +118,7 @@
     val datePickerState = rememberDatePickerState()
     Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
         DatePicker(
-            datePickerState = datePickerState,
+            state = datePickerState,
             // Blocks Sunday and Saturday from being selected.
             dateValidator = { utcDateInMills ->
                 if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
@@ -143,61 +143,9 @@
 @Composable
 fun DateInputSample() {
     Column(verticalArrangement = Arrangement.spacedBy(8.dp)) {
-        val dateInputState = rememberDatePickerState()
-        DateInput(dateInputState = dateInputState, modifier = Modifier.padding(16.dp))
+        val state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
+        DatePicker(state = state, modifier = Modifier.padding(16.dp))
 
-        Text("Entered date timestamp: ${dateInputState.selectedDateMillis ?: "no input"}")
-    }
-}
-
-@OptIn(ExperimentalMaterial3Api::class)
-@Preview
-@Sampled
-@Composable
-fun DateInputDialogSample() {
-    // Decoupled snackbar host state from scaffold state for demo purposes.
-    val snackState = remember { SnackbarHostState() }
-    val snackScope = rememberCoroutineScope()
-    SnackbarHost(hostState = snackState, Modifier)
-    val openDialog = remember { mutableStateOf(true) }
-    // TODO demo how to read the selected date from the state.
-    if (openDialog.value) {
-        // Pre-select a date for January 4, 2020
-        val dateInputState = rememberDatePickerState(initialSelectedDateMillis = 1578096000000)
-        val confirmEnabled = derivedStateOf { dateInputState.selectedDateMillis != null }
-        DatePickerDialog(
-            onDismissRequest = {
-                // Dismiss the dialog when the user clicks outside the dialog or on the back
-                // button. If you want to disable that functionality, simply use an empty
-                // onDismissRequest.
-                openDialog.value = false
-            },
-            confirmButton = {
-                TextButton(
-                    onClick = {
-                        openDialog.value = false
-                        snackScope.launch {
-                            snackState.showSnackbar(
-                                "Entered date timestamp: ${dateInputState.selectedDateMillis}"
-                            )
-                        }
-                    },
-                    enabled = confirmEnabled.value
-                ) {
-                    Text("OK")
-                }
-            },
-            dismissButton = {
-                TextButton(
-                    onClick = {
-                        openDialog.value = false
-                    }
-                ) {
-                    Text("Cancel")
-                }
-            }
-        ) {
-            DateInput(dateInputState = dateInputState)
-        }
+        Text("Entered date timestamp: ${state.selectedDateMillis ?: "no input"}")
     }
 }
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
new file mode 100644
index 0000000..21a56c0
--- /dev/null
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TimePickerSamples.kt
@@ -0,0 +1,128 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.width
+import androidx.compose.material.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.SnackbarHost
+import androidx.compose.material3.SnackbarHostState
+import androidx.compose.material3.Surface
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextButton
+import androidx.compose.material3.TimePicker
+import androidx.compose.material3.TimePickerState
+import androidx.compose.material3.rememberTimePickerState
+import androidx.compose.runtime.Composable
+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.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Dialog
+import androidx.compose.ui.window.DialogProperties
+import java.text.SimpleDateFormat
+import java.util.Calendar
+import java.util.Locale
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun TimePickerSample() {
+    var showTimePicker by remember { mutableStateOf(false) }
+    val state = rememberTimePickerState()
+    val formatter = remember { SimpleDateFormat("hh:mm a", Locale.getDefault()) }
+    val snackState = remember { SnackbarHostState() }
+    val snackScope = rememberCoroutineScope()
+
+    Box(propagateMinConstraints = false) {
+        Button(
+            modifier = Modifier.align(Alignment.Center),
+            onClick = { showTimePicker = true }
+        ) { Text("Set Time") }
+        SnackbarHost(hostState = snackState)
+    }
+
+    if (showTimePicker) {
+        TimePickerDialog(state, onCancel = { showTimePicker = false }) {
+            val cal = Calendar.getInstance()
+            cal.set(Calendar.HOUR_OF_DAY, state.hour)
+            cal.set(Calendar.MINUTE, state.minute)
+            cal.isLenient = false
+            snackScope.launch {
+                snackState.showSnackbar("Entered time: ${formatter.format(cal.time)}")
+            }
+            showTimePicker = false
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun TimePickerDialog(
+    state: TimePickerState,
+    onCancel: () -> Unit = {},
+    onConfirm: () -> Unit = {}
+) {
+    Dialog(
+        onDismissRequest = onCancel,
+        properties = DialogProperties(usePlatformDefaultWidth = false),
+    ) {
+        Surface(
+            shape = MaterialTheme.shapes.extraLarge,
+            tonalElevation = 6.dp,
+            modifier = Modifier
+                .width(328.dp)
+                .background(
+                    shape = MaterialTheme.shapes.extraLarge,
+                    color = MaterialTheme.colorScheme.surface
+                ),
+        ) {
+            Column(
+                modifier = Modifier.padding(24.dp),
+                horizontalAlignment = Alignment.CenterHorizontally
+            ) {
+                Text(
+                    modifier = Modifier
+                        .fillMaxWidth()
+                        .padding(bottom = 20.dp),
+                    text = "Select Time",
+                    style = MaterialTheme.typography.labelMedium
+                )
+                TimePicker(state)
+                Row(modifier = Modifier.fillMaxWidth()) {
+                    Spacer(modifier = Modifier.weight(1f))
+                    TextButton(onClick = onCancel) { Text("Cancel") }
+                    TextButton(onClick = onConfirm) { Text("OK") }
+                }
+            }
+        }
+    }
+}
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
index 0e0f598..2eaaeaa 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/TooltipSamples.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -17,19 +17,23 @@
 package androidx.compose.material3.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.Spacer
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.AddCircle
 import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.material.icons.filled.Info
 import androidx.compose.material3.ExperimentalMaterial3Api
 import androidx.compose.material3.Icon
 import androidx.compose.material3.IconButton
 import androidx.compose.material3.OutlinedButton
 import androidx.compose.material3.PlainTooltipBox
+import androidx.compose.material3.PlainTooltipState
+import androidx.compose.material3.RichTooltipBox
+import androidx.compose.material3.RichTooltipState
 import androidx.compose.material3.Text
-import androidx.compose.material3.TooltipState
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
@@ -44,10 +48,8 @@
 @Sampled
 @Composable
 fun PlainTooltipSample() {
-    val tooltipState = remember { TooltipState() }
     PlainTooltipBox(
-        tooltip = { Text("Add to favorites") },
-        tooltipState = tooltipState
+        tooltip = { Text("Add to favorites") }
     ) {
         IconButton(
             onClick = { /* Icon button's click event */ },
@@ -66,7 +68,7 @@
 @Sampled
 @Composable
 fun PlainTooltipWithManualInvocationSample() {
-    val tooltipState = remember { TooltipState() }
+    val tooltipState = remember { PlainTooltipState() }
     val scope = rememberCoroutineScope()
     Column(
         horizontalAlignment = Alignment.CenterHorizontally
@@ -88,3 +90,76 @@
         }
     }
 }
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun RichTooltipSample() {
+    val tooltipState = remember { RichTooltipState() }
+    val scope = rememberCoroutineScope()
+    RichTooltipBox(
+        title = { Text(richTooltipSubheadText) },
+        action = {
+            Text(
+                text = richTooltipActionText,
+                modifier = Modifier.clickable { scope.launch { tooltipState.dismiss() } }
+            )
+        },
+        text = { Text(richTooltipText) },
+        tooltipState = tooltipState
+    ) {
+        IconButton(
+            onClick = { /* Icon button's click event */ },
+            modifier = Modifier.tooltipAnchor()
+        ) {
+            Icon(
+                imageVector = Icons.Filled.Info,
+                contentDescription = "Localized Description"
+            )
+        }
+    }
+}
+@OptIn(ExperimentalMaterial3Api::class)
+@Sampled
+@Composable
+fun RichTooltipWithManualInvocationSample() {
+    val tooltipState = remember { RichTooltipState() }
+    val scope = rememberCoroutineScope()
+    Column(
+        horizontalAlignment = Alignment.CenterHorizontally
+    ) {
+        RichTooltipBox(
+            title = { Text(richTooltipSubheadText) },
+            action = {
+                Text(
+                    text = richTooltipActionText,
+                    modifier = Modifier.clickable {
+                        scope.launch {
+                            tooltipState.dismiss()
+                        }
+                    }
+                )
+            },
+            text = { Text(richTooltipText) },
+            tooltipState = tooltipState
+        ) {
+            Icon(
+                imageVector = Icons.Filled.Info,
+                contentDescription = "Localized Description"
+            )
+        }
+        Spacer(Modifier.requiredHeight(30.dp))
+        OutlinedButton(
+            onClick = { scope.launch { tooltipState.show() } }
+        ) {
+            Text("Display tooltip")
+        }
+    }
+}
+
+const val richTooltipSubheadText = "Permissions"
+const val richTooltipText =
+    "Configure permissions for selected service accounts. " +
+        "You can add and remove service account members and assign roles to them. " +
+        "Visit go/permissions for details"
+const val richTooltipActionText = "Request Access"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputScreenshotTest.kt
index 02fdffb..18c5b0b8 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputScreenshotTest.kt
@@ -57,8 +57,9 @@
     fun dateInput_initialState() {
         rule.setMaterialContent(scheme.colorScheme) {
             Box(wrap.testTag(wrapperTestTag)) {
-                DateInput(
-                    dateInputState = rememberDatePickerState()
+                DatePicker(
+                    state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input),
+                    showModeToggle = false
                 )
             }
         }
@@ -66,14 +67,28 @@
     }
 
     @Test
+    fun dateInput_withModeToggle() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                DatePicker(
+                    state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
+                )
+            }
+        }
+        assertAgainstGolden("dateInput_withModeToggle_${scheme.name}")
+    }
+
+    @Test
     fun dateInput_withEnteredDate() {
         rule.setMaterialContent(scheme.colorScheme) {
             Box(wrap.testTag(wrapperTestTag)) {
                 val dayMillis = dayInUtcMilliseconds(year = 2021, month = 3, dayOfMonth = 6)
-                DateInput(
-                    dateInputState = rememberDatePickerState(
-                        initialSelectedDateMillis = dayMillis
-                    )
+                DatePicker(
+                    state = rememberDatePickerState(
+                        initialSelectedDateMillis = dayMillis,
+                        initialDisplayMode = DisplayMode.Input
+                    ),
+                    showModeToggle = false
                 )
             }
         }
@@ -81,15 +96,17 @@
     }
 
     @Test
-    fun dateInput_invalidDateInput() {
+    fun dateInput_invalidDatePicker() {
         rule.setMaterialContent(scheme.colorScheme) {
             Box(wrap.testTag(wrapperTestTag)) {
                 val monthInUtcMillis = dayInUtcMilliseconds(year = 2000, month = 6, dayOfMonth = 1)
-                DateInput(
-                    dateInputState = rememberDatePickerState(
-                        initialDisplayedMonthMillis = monthInUtcMillis
+                DatePicker(
+                    state = rememberDatePickerState(
+                        initialDisplayedMonthMillis = monthInUtcMillis,
+                        initialDisplayMode = DisplayMode.Input
                     ),
-                    dateValidator = { false }
+                    dateValidator = { false },
+                    showModeToggle = false
                 )
             }
         }
@@ -105,10 +122,12 @@
                 confirmButton = { TextButton(onClick = {}) { Text("OK") } },
                 dismissButton = { TextButton(onClick = {}) { Text("Cancel") } }
             ) {
-                DateInput(
-                    dateInputState = rememberDatePickerState(
-                        initialSelectedDateMillis = selectedDayMillis
-                    )
+                DatePicker(
+                    state = rememberDatePickerState(
+                        initialSelectedDateMillis = selectedDayMillis,
+                        initialDisplayMode = DisplayMode.Input
+                    ),
+                    showModeToggle = false
                 )
             }
         }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputTest.kt
index d79e898..af1833c 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateInputTest.kt
@@ -21,7 +21,9 @@
 import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithContentDescription
 import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.test.performClick
 import androidx.compose.ui.test.performTextInput
@@ -47,15 +49,16 @@
     fun dateInput() {
         lateinit var defaultHeadline: String
         lateinit var dateInputLabel: String
-        lateinit var dateInputState: DatePickerState
+        lateinit var state: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
             defaultHeadline = getString(string = Strings.DateInputHeadline)
             dateInputLabel = getString(string = Strings.DateInputLabel)
             val monthInUtcMillis = dayInUtcMilliseconds(year = 2019, month = 1, dayOfMonth = 1)
-            dateInputState = rememberDatePickerState(
-                initialDisplayedMonthMillis = monthInUtcMillis
+            state = rememberDatePickerState(
+                initialDisplayedMonthMillis = monthInUtcMillis,
+                initialDisplayMode = DisplayMode.Input
             )
-            DateInput(dateInputState = dateInputState)
+            DatePicker(state = state)
         }
 
         rule.onNodeWithText(defaultHeadline).assertExists()
@@ -64,7 +67,7 @@
         rule.onNodeWithText(dateInputLabel).performClick().performTextInput("01272019")
 
         rule.runOnIdle {
-            assertThat(dateInputState.selectedDateMillis).isEqualTo(
+            assertThat(state.selectedDateMillis).isEqualTo(
                 dayInUtcMilliseconds(
                     year = 2019,
                     month = 1,
@@ -79,13 +82,14 @@
 
     @Test
     fun dateInputWithInitialDate() {
-        lateinit var dateInputState: DatePickerState
+        lateinit var state: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
             val initialDateMillis = dayInUtcMilliseconds(year = 2010, month = 5, dayOfMonth = 11)
-            dateInputState = rememberDatePickerState(
+            state = rememberDatePickerState(
                 initialSelectedDateMillis = initialDateMillis,
+                initialDisplayMode = DisplayMode.Input
             )
-            DateInput(dateInputState = dateInputState)
+            DatePicker(state = state)
         }
 
         rule.onNodeWithText("05/11/2010").assertExists()
@@ -96,12 +100,12 @@
     fun inputDateNotAllowed() {
         lateinit var dateInputLabel: String
         lateinit var errorMessage: String
-        lateinit var dateInputState: DatePickerState
+        lateinit var state: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
             dateInputLabel = getString(string = Strings.DateInputLabel)
             errorMessage = getString(string = Strings.DateInputInvalidNotAllowed)
-            dateInputState = rememberDatePickerState()
-            DateInput(dateInputState = dateInputState,
+            state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
+            DatePicker(state = state,
                 // All dates are invalid for the sake of this test.
                 dateValidator = { false }
             )
@@ -110,7 +114,7 @@
         rule.onNodeWithText(dateInputLabel).performClick().performTextInput("02272020")
 
         rule.runOnIdle {
-            assertThat(dateInputState.selectedDateMillis).isNull()
+            assertThat(state.selectedDateMillis).isNull()
         }
         rule.onNodeWithText("02/27/2020")
             .assert(keyIsDefined(SemanticsProperties.Error))
@@ -126,21 +130,22 @@
     fun inputDateOutOfRange() {
         lateinit var dateInputLabel: String
         lateinit var errorMessage: String
-        lateinit var dateInputState: DatePickerState
+        lateinit var state: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
             dateInputLabel = getString(string = Strings.DateInputLabel)
             errorMessage = getString(string = Strings.DateInputInvalidYearRange)
-            dateInputState = rememberDatePickerState(
+            state = rememberDatePickerState(
                 // Limit the years selection to 2018-2023
-                yearRange = IntRange(2018, 2023)
+                yearRange = IntRange(2018, 2023),
+                initialDisplayMode = DisplayMode.Input
             )
-            DateInput(dateInputState = dateInputState)
+            DatePicker(state = state)
         }
 
         rule.onNodeWithText(dateInputLabel).performClick().performTextInput("02272030")
 
         rule.runOnIdle {
-            assertThat(dateInputState.selectedDateMillis).isNull()
+            assertThat(state.selectedDateMillis).isNull()
         }
         rule.onNodeWithText("02/27/2030")
             .assert(keyIsDefined(SemanticsProperties.Error))
@@ -148,8 +153,8 @@
                 expectValue(
                     SemanticsProperties.Error,
                     errorMessage.format(
-                        dateInputState.yearRange.first,
-                        dateInputState.yearRange.last
+                        state.stateData.yearRange.first,
+                        state.stateData.yearRange.last
                     )
                 )
             )
@@ -159,19 +164,19 @@
     fun inputDateInvalidForPattern() {
         lateinit var dateInputLabel: String
         lateinit var errorMessage: String
-        lateinit var dateInputState: DatePickerState
+        lateinit var state: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
             dateInputLabel = getString(string = Strings.DateInputLabel)
             errorMessage =
                 getString(string = Strings.DateInputInvalidForPattern).format("MM/DD/YYYY")
-            dateInputState = rememberDatePickerState()
-            DateInput(dateInputState = dateInputState)
+            state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input)
+            DatePicker(state = state)
         }
 
         rule.onNodeWithText(dateInputLabel).performClick().performTextInput("99272030")
 
         rule.runOnIdle {
-            assertThat(dateInputState.selectedDateMillis).isNull()
+            assertThat(state.selectedDateMillis).isNull()
         }
         rule.onNodeWithText("99/27/2030")
             .assert(keyIsDefined(SemanticsProperties.Error))
@@ -179,15 +184,37 @@
     }
 
     @Test
+    fun switchToDatePicker() {
+        lateinit var switchToPickerDescription: String
+        lateinit var dateInputLabel: String
+        rule.setMaterialContent(lightColorScheme()) {
+            switchToPickerDescription = getString(string = Strings.DatePickerSwitchToCalendarMode)
+            dateInputLabel = getString(string = Strings.DateInputLabel)
+            DatePicker(state = rememberDatePickerState(initialDisplayMode = DisplayMode.Input))
+        }
+
+        // Click to switch to DatePicker.
+        rule.onNodeWithContentDescription(label = switchToPickerDescription).performClick()
+
+        rule.waitForIdle()
+        rule.onNodeWithContentDescription(label = "next", substring = true, ignoreCase = true)
+            .assertIsDisplayed()
+        rule.onNodeWithContentDescription(label = "previous", substring = true, ignoreCase = true)
+            .assertIsDisplayed()
+        rule.onNodeWithText(dateInputLabel).assertDoesNotExist()
+    }
+
+    @Test
     fun defaultSemantics() {
         val selectedDateInUtcMillis = dayInUtcMilliseconds(year = 2010, month = 5, dayOfMonth = 11)
         lateinit var expectedHeadlineStringFormat: String
         rule.setMaterialContent(lightColorScheme()) {
             // e.g. "Entered date: %1$s"
             expectedHeadlineStringFormat = getString(Strings.DateInputHeadlineDescription)
-            DateInput(
-                dateInputState = rememberDatePickerState(
+            DatePicker(
+                state = rememberDatePickerState(
                     initialSelectedDateMillis = selectedDateInUtcMillis,
+                    initialDisplayMode = DisplayMode.Input
                 )
             )
         }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
index 7a55b57..f82ec5c 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerScreenshotTest.kt
@@ -61,13 +61,29 @@
             Box(wrap.testTag(wrapperTestTag)) {
                 val monthInUtcMillis = dayInUtcMilliseconds(year = 2021, month = 1, dayOfMonth = 1)
                 DatePicker(
-                    datePickerState = rememberDatePickerState(
+                    state = rememberDatePickerState(
+                        initialDisplayedMonthMillis = monthInUtcMillis
+                    ),
+                    showModeToggle = false
+                )
+            }
+        }
+        assertAgainstGolden("datePicker_initialMonth_${scheme.name}")
+    }
+
+    @Test
+    fun datePicker_withModeToggle() {
+        rule.setMaterialContent(scheme.colorScheme) {
+            Box(wrap.testTag(wrapperTestTag)) {
+                val monthInUtcMillis = dayInUtcMilliseconds(year = 2021, month = 1, dayOfMonth = 1)
+                DatePicker(
+                    state = rememberDatePickerState(
                         initialDisplayedMonthMillis = monthInUtcMillis
                     )
                 )
             }
         }
-        assertAgainstGolden("datePicker_initialMonth_${scheme.name}")
+        assertAgainstGolden("datePicker_withModeToggle_${scheme.name}")
     }
 
     @Test
@@ -77,10 +93,11 @@
                 val monthInUtcMillis = dayInUtcMilliseconds(year = 2021, month = 3, dayOfMonth = 1)
                 val selectedDayMillis = dayInUtcMilliseconds(year = 2021, month = 3, dayOfMonth = 6)
                 DatePicker(
-                    datePickerState = rememberDatePickerState(
+                    state = rememberDatePickerState(
                         initialDisplayedMonthMillis = monthInUtcMillis,
                         initialSelectedDateMillis = selectedDayMillis
-                    )
+                    ),
+                    showModeToggle = false
                 )
             }
         }
@@ -93,10 +110,11 @@
             Box(wrap.testTag(wrapperTestTag)) {
                 val monthInUtcMillis = dayInUtcMilliseconds(year = 2000, month = 6, dayOfMonth = 1)
                 DatePicker(
-                    datePickerState = rememberDatePickerState(
+                    state = rememberDatePickerState(
                         initialDisplayedMonthMillis = monthInUtcMillis
                     ),
-                    dateValidator = { false }
+                    dateValidator = { false },
+                    showModeToggle = false
                 )
             }
         }
@@ -109,9 +127,10 @@
             Box(wrap.testTag(wrapperTestTag)) {
                 val monthInUtcMillis = dayInUtcMilliseconds(year = 2000, month = 5, dayOfMonth = 1)
                 DatePicker(
-                    datePickerState = rememberDatePickerState(
+                    state = rememberDatePickerState(
                         initialDisplayedMonthMillis = monthInUtcMillis
-                    )
+                    ),
+                    showModeToggle = false
                 )
             }
         }
@@ -131,10 +150,11 @@
                 dismissButton = { TextButton(onClick = {}) { Text("Cancel") } }
             ) {
                 DatePicker(
-                    datePickerState = rememberDatePickerState(
+                    state = rememberDatePickerState(
                         initialDisplayedMonthMillis = monthInUtcMillis,
                         initialSelectedDateMillis = selectedDayMillis
-                    )
+                    ),
+                    showModeToggle = false
                 )
             }
         }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
index a22a0342..27760ff 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DatePickerTest.kt
@@ -21,6 +21,7 @@
 import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue
 import androidx.compose.ui.test.assert
 import androidx.compose.ui.test.assertContentDescriptionEquals
+import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertIsEnabled
 import androidx.compose.ui.test.assertIsNotEnabled
 import androidx.compose.ui.test.assertIsNotSelected
@@ -60,7 +61,7 @@
                 initialSelectedDateMillis = initialDateMillis,
                 initialDisplayedMonthMillis = monthInUtcMillis
             )
-            DatePicker(datePickerState = datePickerState)
+            DatePicker(state = datePickerState)
         }
 
         // Select the 11th day of the displayed month is selected.
@@ -78,7 +79,7 @@
             datePickerState = rememberDatePickerState(
                 initialDisplayedMonthMillis = monthInUtcMillis
             )
-            DatePicker(datePickerState = datePickerState)
+            DatePicker(state = datePickerState)
         }
 
         rule.onNodeWithText(defaultHeadline).assertExists()
@@ -112,7 +113,7 @@
             datePickerState = rememberDatePickerState(
                 initialDisplayedMonthMillis = monthInUtcMillis
             )
-            DatePicker(datePickerState = datePickerState,
+            DatePicker(state = datePickerState,
                 // All dates are invalid for the sake of this test.
                 dateValidator = { false }
             )
@@ -138,7 +139,7 @@
             datePickerState = rememberDatePickerState(
                 initialDisplayedMonthMillis = monthInUtcMillis
             )
-            DatePicker(datePickerState = datePickerState)
+            DatePicker(state = datePickerState)
         }
 
         rule.onNodeWithText("January 2019").performClick()
@@ -168,7 +169,7 @@
         rule.setMaterialContent(lightColorScheme()) {
             val monthInUtcMillis = dayInUtcMilliseconds(year = 2019, month = 1, dayOfMonth = 1)
             DatePicker(
-                datePickerState = rememberDatePickerState(
+                state = rememberDatePickerState(
                     initialDisplayedMonthMillis = monthInUtcMillis,
                     // Limit the years selection to 2018-2023
                     yearRange = IntRange(2018, 2023)
@@ -189,7 +190,7 @@
         rule.setMaterialContent(lightColorScheme()) {
             val monthInUtcMillis = dayInUtcMilliseconds(year = 2018, month = 1, dayOfMonth = 1)
             DatePicker(
-                datePickerState = rememberDatePickerState(
+                state = rememberDatePickerState(
                     initialDisplayedMonthMillis = monthInUtcMillis
                 )
             )
@@ -220,7 +221,7 @@
         rule.setMaterialContent(lightColorScheme()) {
             val monthInUtcMillis = dayInUtcMilliseconds(year = 2018, month = 1, dayOfMonth = 1)
             DatePicker(
-                datePickerState = rememberDatePickerState(
+                state = rememberDatePickerState(
                     initialDisplayedMonthMillis = monthInUtcMillis,
                     // Limit the years to just 2018
                     yearRange = IntRange(2018, 2018)
@@ -248,6 +249,27 @@
     }
 
     @Test
+    fun switchToDateInput() {
+        lateinit var switchToInputDescription: String
+        lateinit var dateInputLabel: String
+        rule.setMaterialContent(lightColorScheme()) {
+            switchToInputDescription = getString(string = Strings.DatePickerSwitchToInputMode)
+            dateInputLabel = getString(string = Strings.DateInputLabel)
+            DatePicker(state = rememberDatePickerState())
+        }
+
+        // Click to switch to DateInput.
+        rule.onNodeWithContentDescription(label = switchToInputDescription).performClick()
+
+        rule.waitForIdle()
+        rule.onNodeWithText(dateInputLabel).assertIsDisplayed()
+        rule.onNodeWithContentDescription(label = "next", substring = true, ignoreCase = true)
+            .assertDoesNotExist()
+        rule.onNodeWithContentDescription(label = "previous", substring = true, ignoreCase = true)
+            .assertDoesNotExist()
+    }
+
+    @Test
     fun state_initWithSelectedDate() {
         lateinit var datePickerState: DatePickerState
         rule.setMaterialContent(lightColorScheme()) {
@@ -256,8 +278,8 @@
         }
         with(datePickerState) {
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
-            assertThat(displayedMonth).isEqualTo(
-                calendarModel.getMonth(year = 2022, month = 4)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
             )
         }
     }
@@ -274,8 +296,8 @@
             // Assert that the actual selectedDateMillis was rounded down to the start of day
             // timestamp
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
-            assertThat(displayedMonth).isEqualTo(
-                calendarModel.getMonth(year = 2022, month = 4)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
             )
         }
     }
@@ -294,8 +316,8 @@
         with(datePickerState) {
             assertThat(selectedDateMillis).isEqualTo(1649721600000L)
             // Assert that the displayed month is the current month as of today.
-            assertThat(displayedMonth).isEqualTo(
-                calendarModel.getMonth(calendarModel.today.utcTimeMillis)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(stateData.calendarModel.today.utcTimeMillis)
             )
         }
     }
@@ -314,8 +336,8 @@
         with(datePickerState) {
             assertThat(selectedDateMillis).isNull()
             // Assert that the displayed month is the current month as of today.
-            assertThat(displayedMonth).isEqualTo(
-                calendarModel.getMonth(calendarModel.today.utcTimeMillis)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(stateData.calendarModel.today.utcTimeMillis)
             )
         }
     }
@@ -328,34 +350,35 @@
             datePickerState = rememberDatePickerState()
         }
 
-        val date = datePickerState!!.calendarModel.getCanonicalDate(1649721600000L) // 04/12/2022
-        val displayedMonth = datePickerState!!.calendarModel.getMonth(date)
-        rule.runOnIdle {
-            datePickerState!!.selectedDate = date
-            datePickerState!!.displayedMonth = displayedMonth
-        }
+        with(datePickerState!!) {
+            val date =
+                stateData.calendarModel.getCanonicalDate(1649721600000L) // 04/12/2022
+            val displayedMonth = stateData.calendarModel.getMonth(date)
+            rule.runOnIdle {
+                stateData.selectedStartDate = date
+                stateData.displayedMonth = displayedMonth
+            }
 
-        datePickerState = null
+            datePickerState = null
 
-        restorationTester.emulateSavedInstanceStateRestore()
+            restorationTester.emulateSavedInstanceStateRestore()
 
-        rule.runOnIdle {
-            assertThat(datePickerState!!.selectedDate).isEqualTo(date)
-            assertThat(datePickerState!!.displayedMonth).isEqualTo(displayedMonth)
-            assertThat(datePickerState!!.selectedDateMillis).isEqualTo(1649721600000L)
+            rule.runOnIdle {
+                assertThat(stateData.selectedStartDate).isEqualTo(date)
+                assertThat(stateData.displayedMonth).isEqualTo(displayedMonth)
+                assertThat(datePickerState!!.selectedDateMillis).isEqualTo(1649721600000L)
+            }
         }
     }
 
     @Test(expected = IllegalArgumentException::class)
-    fun initialDateOutObBounds() {
-        lateinit var datePickerState: DatePickerState
+    fun initialDateOutOfBounds() {
         rule.setMaterialContent(lightColorScheme()) {
             val initialDateMillis = dayInUtcMilliseconds(year = 2051, month = 5, dayOfMonth = 11)
-            datePickerState = rememberDatePickerState(
+            rememberDatePickerState(
                 initialSelectedDateMillis = initialDateMillis,
                 yearRange = IntRange(2000, 2050)
             )
-            DatePicker(datePickerState = datePickerState)
         }
     }
 
@@ -368,7 +391,7 @@
                 initialDisplayedMonthMillis = monthInUtcMillis,
                 yearRange = IntRange(2000, 2050)
             )
-            DatePicker(datePickerState = datePickerState)
+            DatePicker(state = datePickerState)
         }
     }
 
@@ -381,7 +404,7 @@
             // e.g. "Current selection: %1$s"
             expectedHeadlineStringFormat = getString(Strings.DatePickerHeadlineDescription)
             DatePicker(
-                datePickerState = rememberDatePickerState(
+                state = rememberDatePickerState(
                     initialSelectedDateMillis = selectedDateInUtcMillis,
                     initialDisplayedMonthMillis = monthInUtcMillis
                 )
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt
new file mode 100644
index 0000000..61f6b74
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/DateRangePickerTest.kt
@@ -0,0 +1,222 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.test.junit4.StateRestorationTester
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import java.util.Calendar
+import java.util.TimeZone
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3Api::class)
+class DateRangePickerTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun state_initWithSelectedDates() {
+        lateinit var dateRangePickerState: DateRangePickerState
+        rule.setMaterialContent(lightColorScheme()) {
+            // 04/12/2022
+            dateRangePickerState = rememberDateRangePickerState(
+                // 04/12/2022
+                initialSelectedStartDateMillis = 1649721600000L,
+                // 04/13/2022
+                initialSelectedEndDateMillis = 1649721600000L + MillisecondsIn24Hours
+            )
+        }
+        with(dateRangePickerState) {
+            assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
+            assertThat(selectedEndDateMillis).isEqualTo(1649721600000L + MillisecondsIn24Hours)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
+            )
+        }
+    }
+
+    @Test
+    fun state_initWithSelectedDates_roundingToUtcMidnight() {
+        lateinit var dateRangePickerState: DateRangePickerState
+        rule.setMaterialContent(lightColorScheme()) {
+            dateRangePickerState =
+                rememberDateRangePickerState(
+                    // 04/12/2022
+                    initialSelectedStartDateMillis = 1649721600000L + 10000L,
+                    // 04/13/2022
+                    initialSelectedEndDateMillis = 1649721600000L + MillisecondsIn24Hours + 10000L
+                )
+        }
+        with(dateRangePickerState) {
+            // Assert that the actual selectedDateMillis was rounded down to the start of day
+            // timestamp
+            assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
+            assertThat(selectedEndDateMillis).isEqualTo(1649721600000L + MillisecondsIn24Hours)
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
+            )
+        }
+    }
+
+    @Test
+    fun state_initWithEndDateOnly() {
+        lateinit var dateRangePickerState: DateRangePickerState
+        rule.setMaterialContent(lightColorScheme()) {
+            // 04/12/2022
+            dateRangePickerState = rememberDateRangePickerState(
+                // 04/12/2022
+                initialSelectedEndDateMillis = 1649721600000L
+            )
+        }
+        with(dateRangePickerState) {
+            // Expecting null for both start and end dates when providing just an initial end date.
+            assertThat(selectedStartDateMillis).isNull()
+            assertThat(selectedEndDateMillis).isNull()
+        }
+    }
+
+    @Test
+    fun state_initWithEndDateBeforeStartDate() {
+        lateinit var dateRangePickerState: DateRangePickerState
+        rule.setMaterialContent(lightColorScheme()) {
+            // 04/12/2022
+            dateRangePickerState = rememberDateRangePickerState(
+                // 04/12/2022
+                initialSelectedStartDateMillis = 1649721600000L,
+                // 04/11/2022
+                initialSelectedEndDateMillis = 1649721600000L - MillisecondsIn24Hours
+            )
+        }
+        with(dateRangePickerState) {
+            assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
+            // Expecting the end date to be null, as it was initialized with date that is earlier
+            // than the start date.
+            assertThat(selectedEndDateMillis).isNull()
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
+            )
+        }
+    }
+
+    @Test
+    fun state_initWithEqualStartAndEndDates() {
+        lateinit var dateRangePickerState: DateRangePickerState
+        rule.setMaterialContent(lightColorScheme()) {
+            // 04/12/2022
+            dateRangePickerState = rememberDateRangePickerState(
+                // 04/12/2022 + a few added milliseconds to ensure that the state is checking the
+                // canonical date.
+                initialSelectedStartDateMillis = 1649721600000L + 1000,
+                // 04/12/2022
+                initialSelectedEndDateMillis = 1649721600000L
+            )
+        }
+        with(dateRangePickerState) {
+            assertThat(selectedStartDateMillis).isEqualTo(1649721600000L)
+            // Expecting the end date to be null, as it was initialized with the same canonical date
+            // as the start date.
+            assertThat(selectedEndDateMillis).isNull()
+            assertThat(stateData.displayedMonth).isEqualTo(
+                stateData.calendarModel.getMonth(year = 2022, month = 4)
+            )
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialStartDateOutOfBounds() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val initialStartDateMillis =
+                dayInUtcMilliseconds(year = 1999, month = 5, dayOfMonth = 11)
+            val initialEndDateMillis = dayInUtcMilliseconds(year = 2020, month = 5, dayOfMonth = 12)
+            rememberDateRangePickerState(
+                initialSelectedStartDateMillis = initialStartDateMillis,
+                initialSelectedEndDateMillis = initialEndDateMillis,
+                yearRange = IntRange(2000, 2050)
+            )
+        }
+    }
+
+    @Test(expected = IllegalArgumentException::class)
+    fun initialEndDateOutOfBounds() {
+        rule.setMaterialContent(lightColorScheme()) {
+            val initialStartDateMillis =
+                dayInUtcMilliseconds(year = 2020, month = 1, dayOfMonth = 10)
+            val initialEndDateMillis = dayInUtcMilliseconds(year = 2051, month = 5, dayOfMonth = 12)
+            rememberDateRangePickerState(
+                initialSelectedStartDateMillis = initialStartDateMillis,
+                initialSelectedEndDateMillis = initialEndDateMillis,
+                yearRange = IntRange(2000, 2050)
+            )
+        }
+    }
+
+    @Test
+    fun state_restoresDatePickerState() {
+        val restorationTester = StateRestorationTester(rule)
+        var dateRangePickerState: DateRangePickerState? = null
+        restorationTester.setContent {
+            dateRangePickerState = rememberDateRangePickerState()
+        }
+
+        with(dateRangePickerState!!) {
+            // 04/12/2022
+            val startDate =
+                stateData.calendarModel.getCanonicalDate(1649721600000L)
+            // 04/13/2022
+            val endDate =
+                stateData.calendarModel.getCanonicalDate(1649721600000L + MillisecondsIn24Hours)
+            val displayedMonth = stateData.calendarModel.getMonth(startDate)
+            rule.runOnIdle {
+                stateData.selectedStartDate = startDate
+                stateData.selectedEndDate = endDate
+                stateData.displayedMonth = displayedMonth
+            }
+
+            dateRangePickerState = null
+
+            restorationTester.emulateSavedInstanceStateRestore()
+
+            rule.runOnIdle {
+                assertThat(stateData.selectedStartDate).isEqualTo(startDate)
+                assertThat(stateData.selectedEndDate).isEqualTo(endDate)
+                assertThat(stateData.displayedMonth).isEqualTo(displayedMonth)
+                assertThat(dateRangePickerState!!.selectedStartDateMillis)
+                    .isEqualTo(1649721600000L)
+                assertThat(dateRangePickerState!!.selectedEndDateMillis)
+                    .isEqualTo(1649721600000L + MillisecondsIn24Hours)
+            }
+        }
+    }
+
+    // Returns the given date's day as milliseconds from epoch. The returned value is for the day's
+    // start on midnight.
+    private fun dayInUtcMilliseconds(year: Int, month: Int, dayOfMonth: Int): Long {
+        val firstDayCalendar = Calendar.getInstance(TimeZone.getTimeZone("UTC"))
+        firstDayCalendar.clear()
+        firstDayCalendar[Calendar.YEAR] = year
+        firstDayCalendar[Calendar.MONTH] = month - 1
+        firstDayCalendar[Calendar.DAY_OF_MONTH] = dayOfMonth
+        return firstDayCalendar.timeInMillis
+    }
+}
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
new file mode 100644
index 0000000..ed5213e
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ModalBottomSheetTest.kt
@@ -0,0 +1,848 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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 android.content.ComponentCallbacks2
+import android.content.pm.ActivityInfo
+import android.content.res.Configuration
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.LocalOnBackPressedDispatcherOwner
+import androidx.compose.foundation.ScrollState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+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.requiredHeight
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.rememberScrollState
+import androidx.compose.foundation.verticalScroll
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalContext
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.semantics.SemanticsActions
+import androidx.compose.ui.test.SemanticsMatcher
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
+import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.isPopup
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onFirst
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onParent
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.compose.ui.test.swipeUp
+import androidx.compose.ui.unit.coerceAtMost
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.height
+import androidx.compose.ui.unit.width
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry
+import androidx.test.uiautomator.UiDevice
+import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import junit.framework.TestCase.fail
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+import kotlinx.coroutines.runBlocking
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterial3Api::class)
+class ModalBottomSheetTest {
+
+    @get:Rule
+    val rule = createAndroidComposeRule<ComponentActivity>()
+
+    private val sheetHeight = 256.dp
+    private val sheetTag = "sheetContentTag"
+    private val BackTestTag = "Back"
+
+    @Test
+    fun modalBottomSheet_isDismissedOnTapOutside() {
+        var showBottomSheet by mutableStateOf(true)
+
+        rule.setContent {
+            if (showBottomSheet) {
+                ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .height(sheetHeight)
+                            .testTag(sheetTag)
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(sheetTag).assertIsDisplayed()
+
+        val outsideY = with(rule.density) {
+            rule.onAllNodes(isPopup()).onFirst().getUnclippedBoundsInRoot().height.roundToPx() / 4
+        }
+
+        UiDevice.getInstance(InstrumentationRegistry.getInstrumentation()).click(0, outsideY)
+        rule.waitForIdle()
+
+        // Bottom sheet should not exist
+        rule.onNodeWithTag(sheetTag).assertDoesNotExist()
+    }
+
+    @Test
+    fun modalBottomSheet_fillsScreenWidth() {
+        var boxWidth = 0
+        var screenWidth by mutableStateOf(0)
+
+        rule.setContent {
+            val context = LocalContext.current
+            val density = LocalDensity.current
+            val resScreenWidth = context.resources.configuration.screenWidthDp
+            with(density) { screenWidth = resScreenWidth.dp.roundToPx() }
+
+            ModalBottomSheet(onDismissRequest = {}) {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .height(sheetHeight)
+                        .onSizeChanged { boxWidth = it.width }
+                )
+            }
+        }
+        assertThat(boxWidth).isEqualTo(screenWidth)
+    }
+
+    @Test
+    fun modalBottomSheet_wideScreen_sheetRespectsMaxWidthAndIsCentered() {
+        rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE
+        val latch = CountDownLatch(1)
+
+        rule.activity.application.registerComponentCallbacks(object : ComponentCallbacks2 {
+            override fun onConfigurationChanged(p0: Configuration) {
+                latch.countDown()
+            }
+
+            override fun onLowMemory() {
+                // NO-OP
+            }
+
+            override fun onTrimMemory(p0: Int) {
+                // NO-OP
+            }
+        })
+
+        try {
+            latch.await(1500, TimeUnit.MILLISECONDS)
+            rule.setContent {
+                ModalBottomSheet(onDismissRequest = {}) {
+                    Box(
+                        Modifier
+                            .testTag(sheetTag)
+                            .fillMaxHeight(0.4f)
+                    )
+                }
+            }
+
+            val simulatedRootWidth = rule.onNode(isPopup()).getUnclippedBoundsInRoot().width
+            val maxSheetWidth = 640.dp
+            val expectedSheetWidth = maxSheetWidth.coerceAtMost(simulatedRootWidth)
+            // Our sheet should be max 640 dp but fill the width if the container is less wide
+            val expectedSheetLeft = if (simulatedRootWidth <= expectedSheetWidth) {
+                0.dp
+            } else {
+                (simulatedRootWidth - expectedSheetWidth) / 2
+            }
+
+            rule.onNodeWithTag(sheetTag)
+                .onParent()
+                .assertLeftPositionInRootIsEqualTo(
+                    expectedLeft = expectedSheetLeft
+                )
+                .assertWidthIsEqualTo(expectedSheetWidth)
+        } catch (e: InterruptedException) {
+            fail("Unable to verify sheet width in landscape orientation")
+        } finally {
+            rule.activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_defaultStateForSmallContentIsFullExpanded() {
+        lateinit var sheetState: SheetState
+
+        rule.setContent {
+            sheetState = rememberSheetState()
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState, dragHandle = null) {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .testTag(sheetTag)
+                        .height(sheetHeight)
+                )
+            }
+        }
+
+        val height = rule.onNode(isPopup()).getUnclippedBoundsInRoot().height
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+        rule.onNodeWithTag(sheetTag).assertTopPositionInRootIsEqualTo(height - sheetHeight)
+    }
+
+    @Test
+    fun modalBottomSheet_defaultStateForLargeContentIsHalfExpanded() {
+        lateinit var sheetState: SheetState
+        var screenHeightPx by mutableStateOf(0f)
+
+        rule.setContent {
+            sheetState = rememberSheetState()
+            val context = LocalContext.current
+            val density = LocalDensity.current
+            val resScreenHeight = context.resources.configuration.screenHeightDp
+            with(density) {
+                screenHeightPx = resScreenHeight.dp.roundToPx().toFloat()
+            }
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag))
+            }
+        }
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        assertThat(sheetState.requireOffset())
+            .isWithin(1f)
+            .of(screenHeightPx / 2f)
+    }
+
+    @Test
+    fun modalBottomSheet_isDismissedOnBackPress() {
+        var showBottomSheet by mutableStateOf(true)
+        rule.setContent {
+            val dispatcher = LocalOnBackPressedDispatcherOwner.current!!.onBackPressedDispatcher
+            if (showBottomSheet) {
+                ModalBottomSheet(onDismissRequest = { showBottomSheet = false }) {
+                    Box(
+                        Modifier
+                            .size(sheetHeight)
+                            .testTag(sheetTag)) {
+                        Button(
+                            onClick = { dispatcher.onBackPressed() },
+                            modifier = Modifier.testTag(BackTestTag),
+                            content = { Text("Content") },
+                        )
+                    }
+                }
+            }
+        }
+
+        // Popup should be visible
+        rule.onNodeWithTag(sheetTag).assertIsDisplayed()
+
+        rule.onNodeWithTag(BackTestTag).performClick()
+        rule.onNodeWithTag(BackTestTag).assertDoesNotExist()
+
+        // Popup should not exist
+        rule.onNodeWithTag(sheetTag).assertDoesNotExist()
+    }
+
+    @Test
+    fun modalBottomSheet_shortSheet_sizeChanges_snapsToNewTarget() {
+        lateinit var state: SheetState
+        var size by mutableStateOf(56.dp)
+        var screenHeight by mutableStateOf(0.dp)
+        val expectedExpandedAnchor by derivedStateOf {
+            with(rule.density) {
+                (screenHeight - size).toPx()
+            }
+        }
+
+        rule.setContent {
+            val context = LocalContext.current
+            screenHeight = context.resources.configuration.screenHeightDp.dp
+            state = rememberSheetState()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = state,
+                dragHandle = null
+            ) {
+                Box(
+                    Modifier
+                        .height(size)
+                        .fillMaxWidth()
+                )
+            }
+        }
+        assertThat(state.requireOffset()).isWithin(0.5f).of(expectedExpandedAnchor)
+
+        size = 100.dp
+        rule.waitForIdle()
+        assertThat(state.requireOffset()).isWithin(0.5f).of(expectedExpandedAnchor)
+
+        size = 30.dp
+        rule.waitForIdle()
+        assertThat(state.requireOffset()).isWithin(0.5f).of(expectedExpandedAnchor)
+    }
+
+    @Test
+    fun modalBottomSheet_emptySheet_expandDoesNotAnimate() {
+        lateinit var state: SheetState
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            state = rememberSheetState()
+            scope = rememberCoroutineScope()
+
+            ModalBottomSheet(onDismissRequest = {}, sheetState = state, dragHandle = null) {}
+        }
+        assertThat(state.swipeableState.currentValue).isEqualTo(SheetValue.Hidden)
+        val hiddenOffset = state.requireOffset()
+        scope.launch { state.show() }
+        rule.waitForIdle()
+
+        assertThat(state.swipeableState.currentValue).isEqualTo(SheetValue.Expanded)
+        val expandedOffset = state.requireOffset()
+
+        assertThat(hiddenOffset).isEqualTo(expandedOffset)
+    }
+
+    @Test
+    fun modalBottomSheet_anchorsChange_retainsCurrentValue() {
+        lateinit var state: SheetState
+        var amountOfItems by mutableStateOf(0)
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            state = rememberSheetState()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = state,
+                dragHandle = null,
+            ) {
+                scope = rememberCoroutineScope()
+                LazyColumn {
+                    items(amountOfItems) {
+                        ListItem(headlineText = { Text("$it") })
+                    }
+                }
+            }
+        }
+
+        assertThat(state.currentValue).isEqualTo(SheetValue.Hidden)
+
+        amountOfItems = 50
+        rule.waitForIdle()
+        scope.launch {
+            state.show()
+        }
+        // The anchors should now be {Hidden, HalfExpanded, Expanded}
+
+        rule.waitForIdle()
+        assertThat(state.currentValue).isEqualTo(SheetValue.Collapsed)
+
+        amountOfItems = 100 // The anchors should now be {Hidden, HalfExpanded, Expanded}
+
+        rule.waitForIdle()
+        assertThat(state.currentValue).isEqualTo(SheetValue.Collapsed) // We should
+        // retain the current value if possible
+        assertThat(state.swipeableState.anchors).containsKey(SheetValue.Hidden)
+        assertThat(state.swipeableState.anchors).containsKey(SheetValue.Collapsed)
+        assertThat(state.swipeableState.anchors).containsKey(SheetValue.Expanded)
+
+        amountOfItems = 0 // When the sheet height is 0, we should only have a hidden anchor
+        rule.waitForIdle()
+        assertThat(state.currentValue).isEqualTo(SheetValue.Hidden)
+        assertThat(state.swipeableState.anchors).containsKey(SheetValue.Hidden)
+        assertThat(state.swipeableState.anchors)
+            .doesNotContainKey(SheetValue.Collapsed)
+        assertThat(state.swipeableState.anchors).doesNotContainKey(SheetValue.Expanded)
+    }
+
+    @Test
+    fun modalBottomSheet_nestedScroll_consumesWithinBounds_scrollsOutsideBounds() {
+        lateinit var sheetState: SheetState
+        lateinit var scrollState: ScrollState
+        rule.setContent {
+            sheetState = rememberSheetState()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = sheetState,
+            ) {
+                scrollState = rememberScrollState()
+                Column(
+                    Modifier
+                        .verticalScroll(scrollState)
+                        .testTag(sheetTag)
+                ) {
+                    repeat(100) {
+                        Text(it.toString(), Modifier.requiredHeight(50.dp))
+                    }
+                }
+            }
+        }
+
+        rule.waitForIdle()
+
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput {
+                swipeUp(startY = bottom, endY = bottom / 2)
+            }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput {
+                swipeUp(startY = bottom, endY = top)
+            }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isGreaterThan(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput {
+                swipeDown(startY = top, endY = bottom)
+            }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput {
+                swipeDown(startY = top, endY = bottom / 2)
+            }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput {
+                swipeDown(startY = bottom / 2, endY = bottom)
+            }
+        rule.waitForIdle()
+        assertThat(scrollState.value).isEqualTo(0)
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+    }
+
+    @Test
+    fun modalBottomSheet_missingAnchors_findsClosest() {
+        val topTag = "ModalBottomSheetLayout"
+        var showShortContent by mutableStateOf(false)
+        val sheetState = SheetState(skipCollapsed = false)
+        lateinit var scope: CoroutineScope
+
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                modifier = Modifier.testTag(topTag),
+                sheetState = sheetState,
+            ) {
+                if (showShortContent) {
+                    Box(
+                        Modifier
+                            .fillMaxWidth()
+                            .height(100.dp)
+                    )
+                } else {
+                    Box(
+                        Modifier
+                            .fillMaxSize()
+                            .testTag(sheetTag)
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag(topTag).performTouchInput {
+            swipeDown()
+            swipeDown()
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+        }
+
+        showShortContent = true
+        scope.launch { sheetState.show() } // We can't use LaunchedEffect with Swipeable in tests
+        // yet, so we're invoking this outside of composition. See b/254115946.
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_expandBySwiping() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberSheetState()
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput { swipeUp() }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_respectsConfirmStateChange() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberSheetState(
+                confirmValueChange = { newState ->
+                    newState != SheetValue.Hidden
+                }
+            )
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput { swipeDown() }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        }
+
+        rule.onNodeWithTag(sheetTag).onParent()
+            .performSemanticsAction(SemanticsActions.Dismiss)
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_hideBySwiping_tallBottomSheet() {
+        lateinit var sheetState: SheetState
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            sheetState = rememberSheetState()
+            scope = rememberCoroutineScope()
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+        }
+
+        scope.launch { sheetState.expand() }
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput { swipeDown() }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_hideBySwiping_skipHalfExpanded() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberSheetState(skipHalfExpanded = true)
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxWidth()
+                        .height(sheetHeight)
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+        }
+
+        rule.onNodeWithTag(sheetTag)
+            .performTouchInput { swipeDown() }
+
+        rule.runOnIdle {
+            assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_hideManually_skipHalfExpanded(): Unit = runBlocking(
+        AutoTestFrameClock()
+    ) {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberSheetState(skipHalfExpanded = true)
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+        assertThat(sheetState.currentValue == SheetValue.Expanded)
+
+        sheetState.hide()
+
+        assertThat(sheetState.currentValue == SheetValue.Hidden)
+    }
+
+    @Test
+    fun modalBottomSheet_testDismissAction_tallBottomSheet_whenHalfExpanded() {
+        rule.setContent {
+            ModalBottomSheet(onDismissRequest = {}) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(sheetTag).onParent()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Collapse))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Expand))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+            .performSemanticsAction(SemanticsActions.Dismiss)
+    }
+
+    @Test
+    fun modalBottomSheet_testExpandAction_tallBottomSheet_whenHalfExpanded() {
+        lateinit var sheetState: SheetState
+        rule.setContent {
+            sheetState = rememberSheetState()
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+
+        rule.onNodeWithTag(sheetTag).onParent()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Collapse))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Expand))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+            .performSemanticsAction(SemanticsActions.Expand)
+
+        rule.runOnIdle {
+            assertThat(sheetState.requireOffset()).isEqualTo(0f)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_testDismissAction_tallBottomSheet_whenExpanded() {
+        lateinit var sheetState: SheetState
+        lateinit var scope: CoroutineScope
+
+        var screenHeightPx by mutableStateOf(0f)
+
+        rule.setContent {
+            sheetState = rememberSheetState()
+            scope = rememberCoroutineScope()
+            val context = LocalContext.current
+            val density = LocalDensity.current
+            val resScreenHeight = context.resources.configuration.screenHeightDp
+            with(density) {
+                screenHeightPx = resScreenHeight.dp.roundToPx().toFloat()
+            }
+
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+        scope.launch {
+            sheetState.expand()
+        }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(sheetTag).onParent()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Expand))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Collapse))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+            .performSemanticsAction(SemanticsActions.Dismiss)
+
+        rule.runOnIdle {
+            assertThat(sheetState.requireOffset()).isWithin(1f).of(screenHeightPx)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_testCollapseAction_tallBottomSheet_whenExpanded() {
+        lateinit var sheetState: SheetState
+        lateinit var scope: CoroutineScope
+
+        var screenHeightPx by mutableStateOf(0f)
+
+        rule.setContent {
+            sheetState = rememberSheetState()
+            scope = rememberCoroutineScope()
+            val context = LocalContext.current
+            val density = LocalDensity.current
+            val resScreenHeight = context.resources.configuration.screenHeightDp
+            with(density) {
+                screenHeightPx = resScreenHeight.dp.roundToPx().toFloat()
+            }
+
+            ModalBottomSheet(onDismissRequest = {}, sheetState = sheetState) {
+                Box(
+                    Modifier
+                        .fillMaxSize()
+                        .testTag(sheetTag)
+                )
+            }
+        }
+        scope.launch {
+            sheetState.expand()
+        }
+        rule.waitForIdle()
+
+        rule.onNodeWithTag(sheetTag).onParent()
+            .assert(SemanticsMatcher.keyNotDefined(SemanticsActions.Expand))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Collapse))
+            .assert(SemanticsMatcher.keyIsDefined(SemanticsActions.Dismiss))
+            .performSemanticsAction(SemanticsActions.Collapse)
+
+        rule.runOnIdle {
+            assertThat(sheetState.requireOffset()).isWithin(1f).of(screenHeightPx / 2)
+        }
+    }
+
+    @Test
+    fun modalBottomSheet_shortSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
+        val sheetState = SheetState(skipCollapsed = false)
+        var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = sheetState,
+                dragHandle = null,
+            ) {
+                if (hasSheetContent) {
+                    Box(Modifier.fillMaxHeight(0.4f))
+                }
+            }
+        }
+
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+        assertThat(sheetState.swipeableState.hasAnchorForValue(SheetValue.Collapsed))
+            .isFalse()
+        assertThat(sheetState.swipeableState.hasAnchorForValue(SheetValue.Expanded))
+            .isFalse()
+
+        scope.launch { sheetState.show() }
+        rule.waitForIdle()
+
+        assertThat(sheetState.isVisible).isTrue()
+        assertThat(sheetState.currentValue).isEqualTo(sheetState.targetValue)
+
+        hasSheetContent = true // Recompose with sheet content
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Expanded)
+    }
+
+    @Test
+    fun modalBottomSheet_tallSheet_anchorChangeHandler_previousTargetNotInAnchors_reconciles() {
+        val sheetState = SheetState(skipCollapsed = false)
+        var hasSheetContent by mutableStateOf(false) // Start out with empty sheet content
+        lateinit var scope: CoroutineScope
+        rule.setContent {
+            scope = rememberCoroutineScope()
+            ModalBottomSheet(
+                onDismissRequest = {},
+                sheetState = sheetState,
+                dragHandle = null,
+            ) {
+                if (hasSheetContent) {
+                    Box(Modifier.fillMaxHeight(0.6f))
+                }
+            }
+        }
+
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Hidden)
+        assertThat(sheetState.swipeableState.hasAnchorForValue(SheetValue.Collapsed))
+            .isFalse()
+        assertThat(sheetState.swipeableState.hasAnchorForValue(SheetValue.Expanded))
+            .isFalse()
+
+        scope.launch { sheetState.show() }
+        rule.waitForIdle()
+
+        assertThat(sheetState.isVisible).isTrue()
+        assertThat(sheetState.currentValue).isEqualTo(sheetState.targetValue)
+
+        hasSheetContent = true // Recompose with sheet content
+        rule.waitForIdle()
+        assertThat(sheetState.currentValue).isEqualTo(SheetValue.Collapsed)
+    }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..92a8a0d
--- /dev/null
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TimePickerTest.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.material3
+
+import android.provider.Settings.System.TIME_12_24
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.SemanticsProperties
+import androidx.compose.ui.semantics.SemanticsProperties.SelectableGroup
+import androidx.compose.ui.test.SemanticsMatcher.Companion.expectValue
+import androidx.compose.ui.test.SemanticsMatcher.Companion.keyIsDefined
+import androidx.compose.ui.test.assert
+import androidx.compose.ui.test.assertAll
+import androidx.compose.ui.test.assertCountEquals
+import androidx.compose.ui.test.assertHasClickAction
+import androidx.compose.ui.test.assertIsNotSelected
+import androidx.compose.ui.test.assertIsSelectable
+import androidx.compose.ui.test.assertIsSelected
+import androidx.compose.ui.test.filter
+import androidx.compose.ui.test.hasContentDescription
+import androidx.compose.ui.test.isFocusable
+import androidx.compose.ui.test.isSelectable
+import androidx.compose.ui.test.isSelected
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onAllNodesWithContentDescription
+import androidx.compose.ui.test.onAllNodesWithText
+import androidx.compose.ui.test.onChildren
+import androidx.compose.ui.test.onLast
+import androidx.compose.ui.test.onNodeWithContentDescription
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.onSiblings
+import androidx.compose.ui.test.performClick
+import androidx.compose.ui.test.performTouchInput
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalMaterial3Api::class)
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class TimePickerTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun timePicker_initialState() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        rule.onAllNodesWithText("23").assertCountEquals(1)
+
+        rule.onNodeWithText("02").assertIsSelected()
+
+        rule.onNodeWithText("AM").assertExists()
+
+        rule.onNodeWithText("PM").assertExists().assertIsSelected()
+    }
+
+    @Test
+    fun timePicker_switchToMinutes() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        rule.onNodeWithText("23").performClick()
+
+        rule.onNodeWithText("55").assertExists()
+    }
+
+    @Test
+    fun timePicker_selectHour() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        rule.onNodeWithText("6").performClick()
+
+        // shows 06 in display
+        rule.onNodeWithText("06").assertExists()
+
+        // switches to minutes
+        rule.onNodeWithText("23").assertIsSelected()
+
+        // state updated
+        assertThat(state.hour).isEqualTo(18)
+    }
+
+    @Test
+    fun timePicker_switchToAM() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        assertThat(state.hour).isEqualTo(14)
+
+        rule.onNodeWithText("AM").performClick()
+
+        assertThat(state.hour).isEqualTo(2)
+    }
+
+    @Test
+    fun timePicker_dragging() {
+        val state = TimePickerState(initialHour = 0, initialMinute = 23, is24Hour = false)
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        rule.onAllNodes(keyIsDefined(SelectableGroup), useUnmergedTree = true)
+            .onLast()
+            .performTouchInput {
+                down(topCenter)
+                // 3 O'Clock
+                moveTo(centerRight)
+                up()
+            }
+
+        rule.runOnIdle {
+            assertThat(state.hour).isEqualTo(3)
+        }
+    }
+
+    @Test
+    fun timePickerState_format_12h() {
+        lateinit var state: TimePickerState
+        getInstrumentation().uiAutomation.executeShellCommand(
+            "settings put system $TIME_12_24 12"
+        )
+        rule.setContent {
+            state = rememberTimePickerState()
+        }
+
+        assertThat(state.is24hour).isFalse()
+    }
+
+    @Test
+    fun timePickerState_format_24h() {
+        lateinit var state: TimePickerState
+        getInstrumentation().uiAutomation.executeShellCommand(
+            "settings put system $TIME_12_24 24"
+        )
+        rule.setContent {
+            state = rememberTimePickerState()
+        }
+
+        assertThat(state.is24hour).isTrue()
+    }
+
+    @Test
+    fun timePicker_toggle_semantics() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        lateinit var contentDescription: String
+        rule.setMaterialContent(lightColorScheme()) {
+            contentDescription = getString(Strings.TimePickerPeriodToggle)
+            TimePicker(state)
+        }
+
+        rule.onNodeWithContentDescription(contentDescription)
+            .onChildren()
+            .assertAll(isSelectable())
+    }
+
+    @Test
+    fun timePicker_display_semantics() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        lateinit var minuteDescription: String
+        lateinit var hourDescription: String
+        rule.setMaterialContent(lightColorScheme()) {
+            minuteDescription = getString(Strings.TimePickerMinuteSelection)
+            hourDescription = getString(Strings.TimePickerHourSelection)
+            TimePicker(state)
+        }
+
+        rule.onNodeWithContentDescription(minuteDescription)
+            .assertIsSelectable()
+            .assertIsNotSelected()
+            .assert(expectValue(SemanticsProperties.Role, Role.RadioButton))
+            .assertHasClickAction()
+
+        rule.onNodeWithContentDescription(hourDescription)
+            .assertIsSelectable()
+            .assertIsSelected()
+            .assert(expectValue(SemanticsProperties.Role, Role.RadioButton))
+            .assertHasClickAction()
+    }
+
+    @Test
+    fun timePicker_clockFace_hour_semantics() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        lateinit var hourDescription: String
+
+        rule.setMaterialContent(lightColorScheme()) {
+            hourDescription = getString(Strings.TimePickerHourSuffix, 2)
+            TimePicker(state)
+        }
+
+        rule.onAllNodesWithContentDescription(hourDescription)
+            .onLast()
+            .onSiblings()
+            .filter(isFocusable())
+            .assertCountEquals(11)
+            .assertAll(
+                hasContentDescription(
+                    value = "o'clock",
+                    substring = true,
+                    ignoreCase = true
+                )
+            )
+    }
+
+    @Test
+    fun timePicker_clockFace_selected_semantics() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = true)
+
+        rule.setMaterialContent(lightColorScheme()) {
+            TimePicker(state)
+        }
+
+        rule.onAllNodesWithText("14")
+            .assertAll(isSelected())
+    }
+
+    @Test
+    fun timePicker_clockFace_minutes_semantics() {
+        val state = TimePickerState(initialHour = 14, initialMinute = 23, is24Hour = false)
+        lateinit var minuteDescription: String
+
+        rule.setMaterialContent(lightColorScheme()) {
+            minuteDescription = getString(Strings.TimePickerMinuteSuffix, 55)
+            TimePicker(state)
+        }
+
+        // Switch to minutes
+        rule.onNodeWithText("23").performClick()
+
+        rule.waitForIdle()
+
+        rule.onNodeWithContentDescription(minuteDescription)
+            .assertExists()
+            .onSiblings()
+            .assertCountEquals(11)
+            .assertAll(
+                hasContentDescription(
+                    value = "minutes",
+                    substring = true,
+                    ignoreCase = true
+                )
+            )
+    }
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
index 49505df..eafc433 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipScreenshotTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.testutils.assertAgainstGolden
 import androidx.compose.ui.Modifier
@@ -45,24 +46,34 @@
     @get:Rule
     val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL3)
 
-    private val tooltipState = TooltipState()
-
     @Test
     fun plainTooltip_lightTheme() {
-        rule.setMaterialContent(lightColorScheme()) { TestTooltips() }
+        rule.setMaterialContent(lightColorScheme()) { TestPlainTooltips() }
         assertAgainstGolden("plainTooltip_lightTheme")
     }
 
     @Test
     fun plainTooltip_darkTheme() {
-        rule.setMaterialContent(darkColorScheme()) { TestTooltips() }
+        rule.setMaterialContent(darkColorScheme()) { TestPlainTooltips() }
         assertAgainstGolden("plainTooltip_darkTheme")
     }
 
-    @Composable
-    private fun TestTooltips() {
-        val scope = rememberCoroutineScope()
+    @Test
+    fun richTooltip_lightTheme() {
+        rule.setMaterialContent(lightColorScheme()) { TestRichTooltips() }
+        assertAgainstGolden("richTooltip_lightTheme")
+    }
 
+    @Test
+    fun richTooltip_darkTheme() {
+        rule.setMaterialContent(darkColorScheme()) { TestRichTooltips() }
+        assertAgainstGolden("richTooltip_darkTheme")
+    }
+
+    @Composable
+    private fun TestPlainTooltips() {
+        val scope = rememberCoroutineScope()
+        val tooltipState = remember { PlainTooltipState() }
         PlainTooltipBox(
             tooltip = { Text("Tooltip Text") },
             modifier = Modifier.testTag(TooltipTestTag),
@@ -72,6 +83,26 @@
         scope.launch { tooltipState.show() }
     }
 
+    @Composable
+    private fun TestRichTooltips() {
+        val scope = rememberCoroutineScope()
+        val tooltipState = remember { RichTooltipState() }
+        RichTooltipBox(
+            title = { Text("Title") },
+            text = {
+                Text(
+                    "Area for supportive text, providing a descriptive " +
+                        "message for the composable that the tooltip is anchored to."
+                )
+            },
+            action = { Text("Action Text") },
+            tooltipState = tooltipState,
+            modifier = Modifier.testTag(TooltipTestTag)
+        ) {}
+
+        scope.launch { tooltipState.show() }
+    }
+
     private fun assertAgainstGolden(goldenName: String) {
         rule.onNodeWithTag(TooltipTestTag)
             .captureToImage()
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
index ae58d4b..666507d 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/TooltipTest.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -22,6 +22,7 @@
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.Favorite
 import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
@@ -29,6 +30,8 @@
 import androidx.compose.ui.test.assertLeftPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.assertWidthIsEqualTo
+import androidx.compose.ui.test.click
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.longClick
 import androidx.compose.ui.test.onNodeWithTag
@@ -36,6 +39,7 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.launch
 import org.junit.Ignore
 import org.junit.Rule
@@ -50,11 +54,9 @@
     @get:Rule
     val rule = createComposeRule()
 
-    private val tooltipState = TooltipState()
-
     @Test
     fun plainTooltip_noContent_size() {
-        rule.setMaterialContent(lightColorScheme()) { TestTooltip() }
+        rule.setMaterialContent(lightColorScheme()) { TestPlainTooltip() }
 
         rule.onNodeWithTag(ContainerTestTag)
             .assertHeightIsEqualTo(TooltipMinHeight)
@@ -62,12 +64,34 @@
     }
 
     @Test
+    fun richTooltip_noContent_size() {
+        rule.setMaterialContent(lightColorScheme()) { TestRichTooltip() }
+        rule.onNodeWithTag(ContainerTestTag)
+            .assertHeightIsEqualTo(TooltipMinHeight)
+            .assertWidthIsEqualTo(TooltipMinWidth)
+    }
+
+    @Test
     fun plainTooltip_customSize_size() {
         val customWidth = 100.dp
         val customHeight = 100.dp
 
         rule.setMaterialContent(lightColorScheme()) {
-            TestTooltip(modifier = Modifier.size(customWidth, customHeight))
+            TestPlainTooltip(modifier = Modifier.size(customWidth, customHeight))
+        }
+
+        rule.onNodeWithTag(ContainerTestTag)
+            .assertHeightIsEqualTo(customHeight)
+            .assertWidthIsEqualTo(customWidth)
+    }
+
+    @Test
+    fun richTooltip_customSize_size() {
+        val customWidth = 100.dp
+        val customHeight = 100.dp
+
+        rule.setMaterialContent(lightColorScheme()) {
+            TestRichTooltip(modifier = Modifier.size(customWidth, customHeight))
         }
 
         rule.onNodeWithTag(ContainerTestTag)
@@ -79,7 +103,7 @@
     @Test
     fun plainTooltip_content_padding() {
         rule.setMaterialContent(lightColorScheme()) {
-            TestTooltip(
+            TestPlainTooltip(
                 tooltipContent = {
                     Text(
                         text = "Test",
@@ -94,33 +118,149 @@
             .assertTopPositionInRootIsEqualTo(4.dp)
     }
 
-    @Ignore // b/264887805
+    @Test
+    fun richTooltip_content_padding() {
+        rule.setMaterialContent(lightColorScheme()) {
+            TestRichTooltip(
+                title = { Text(text = "Subhead", modifier = Modifier.testTag(SubheadTestTag)) },
+                text = { Text(text = "Text", modifier = Modifier.testTag(TextTestTag)) },
+                action = { Text(text = "Action", modifier = Modifier.testTag(ActionTestTag)) },
+            )
+        }
+
+        val subhead = rule.onNodeWithTag(SubheadTestTag)
+        val text = rule.onNodeWithTag(TextTestTag)
+
+        val subheadBaseline = subhead.getFirstBaselinePosition()
+        val textBaseLine = text.getFirstBaselinePosition()
+
+        val subheadBound = subhead.getUnclippedBoundsInRoot()
+        val textBound = text.getUnclippedBoundsInRoot()
+
+        rule.onNodeWithTag(SubheadTestTag)
+            .assertLeftPositionInRootIsEqualTo(RichTooltipHorizontalPadding)
+            .assertTopPositionInRootIsEqualTo(28.dp - subheadBaseline)
+
+        rule.onNodeWithTag(TextTestTag)
+            .assertLeftPositionInRootIsEqualTo(RichTooltipHorizontalPadding)
+            .assertTopPositionInRootIsEqualTo(subheadBound.bottom + 24.dp - textBaseLine)
+
+        rule.onNodeWithTag(ActionTestTag)
+            .assertLeftPositionInRootIsEqualTo(RichTooltipHorizontalPadding)
+            .assertTopPositionInRootIsEqualTo(textBound.bottom + 16.dp)
+    }
+
     @Test
     fun plainTooltip_behavior() {
+        val tooltipState = PlainTooltipState()
         rule.setMaterialContent(lightColorScheme()) {
             PlainTooltipBox(
                 tooltip = { Text(text = "Test", modifier = Modifier.testTag(TextTestTag)) },
                 tooltipState = tooltipState,
                 modifier = Modifier.testTag(ContainerTestTag)
-            ) { Anchor() }
+            ) { Anchor(tooltipState) }
         }
 
         // Tooltip should initially be not visible
-        assert(!tooltipState.isVisible)
+        assertThat(tooltipState.isVisible).isFalse()
 
-        // Long press the icon and check that the tooltip is now showing
+        // Test will manually advance the time to check the timeout
+        rule.mainClock.autoAdvance = false
+
+        // Long press the icon
         rule.onNodeWithTag(AnchorTestTag)
             .performTouchInput { longClick() }
 
-        assert(tooltipState.isVisible)
+        // Check that the tooltip is now showing
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isTrue()
 
         // Tooltip should dismiss itself after 1.5s
-        rule.waitUntil(TooltipDuration + 100L) { !tooltipState.isVisible }
+        rule.mainClock.advanceTimeBy(milliseconds = TooltipDuration)
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isFalse()
+    }
+
+    @Test
+    fun richTooltip_behavior_noAction() {
+        val tooltipState = RichTooltipState()
+        rule.setMaterialContent(lightColorScheme()) {
+            RichTooltipBox(
+                title = { Text(text = "Subhead", modifier = Modifier.testTag(SubheadTestTag)) },
+                text = { Text(text = "Text", modifier = Modifier.testTag(TextTestTag)) },
+                tooltipState = tooltipState,
+                modifier = Modifier.testTag(ContainerTestTag)
+            ) { Anchor(tooltipState) }
+        }
+
+        // Tooltip should initially be not visible
+        assertThat(tooltipState.isVisible).isFalse()
+
+        // Test will manually advance the time to check the timeout
+        rule.mainClock.autoAdvance = false
+
+        // Long press the icon
+        rule.onNodeWithTag(AnchorTestTag)
+            .performTouchInput { longClick() }
+
+        // Check that the tooltip is now showing
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isTrue()
+
+        // Tooltip should dismiss itself after 1.5s
+        rule.mainClock.advanceTimeBy(milliseconds = TooltipDuration)
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isFalse()
+    }
+
+    @Test
+    fun richTooltip_behavior_persistent() {
+        val tooltipState = RichTooltipState()
+        rule.setMaterialContent(lightColorScheme()) {
+            val scope = rememberCoroutineScope()
+            RichTooltipBox(
+                title = { Text(text = "Subhead", modifier = Modifier.testTag(SubheadTestTag)) },
+                text = { Text(text = "Text", modifier = Modifier.testTag(TextTestTag)) },
+                action = {
+                    TextButton(
+                        onClick = { scope.launch { tooltipState.dismiss() } },
+                        modifier = Modifier.testTag(ActionTestTag)
+                    ) { Text(text = "Action") }
+                },
+                tooltipState = tooltipState,
+                modifier = Modifier.testTag(ContainerTestTag)
+            ) { Anchor(tooltipState) }
+        }
+
+        // Tooltip should initially be not visible
+        assertThat(tooltipState.isVisible).isFalse()
+
+        // Test will manually advance the time to check the timeout
+        rule.mainClock.autoAdvance = false
+
+        // Long press the icon
+        rule.onNodeWithTag(AnchorTestTag)
+            .performTouchInput { longClick() }
+
+        // Check that the tooltip is now showing
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isTrue()
+
+        // Tooltip should still be visible after the normal TooltipDuration, since we have an action.
+        rule.mainClock.advanceTimeBy(milliseconds = TooltipDuration)
+        rule.waitForIdle()
+        assertThat(tooltipState.isVisible).isTrue()
+
+        // Click the action and check that it closed the tooltip
+        rule.onNodeWithTag(ActionTestTag)
+            .performTouchInput { click() }
+        assertThat(tooltipState.isVisible).isFalse()
     }
 
     @Composable
-    fun TestTooltip(
+    private fun TestPlainTooltip(
         modifier: Modifier = Modifier,
+        tooltipState: PlainTooltipState = remember { PlainTooltipState() },
         tooltipContent: @Composable () -> Unit = {}
     ) {
         val scope = rememberCoroutineScope()
@@ -134,9 +274,32 @@
         scope.launch { tooltipState.show() }
     }
 
+    @Composable
+    private fun TestRichTooltip(
+        modifier: Modifier = Modifier,
+        tooltipState: RichTooltipState = remember { RichTooltipState() },
+        text: @Composable () -> Unit = {},
+        action: (@Composable () -> Unit)? = null,
+        title: (@Composable () -> Unit)? = null
+    ) {
+        val scope = rememberCoroutineScope()
+
+        RichTooltipBox(
+            text = text,
+            action = action,
+            title = title,
+            modifier = modifier.testTag(ContainerTestTag),
+            tooltipState = tooltipState
+        ) {}
+
+        scope.launch { tooltipState.show() }
+    }
+
     @OptIn(ExperimentalFoundationApi::class)
     @Composable
-    fun Anchor() {
+    private fun Anchor(
+        tooltipState: TooltipState
+    ) {
         val scope = rememberCoroutineScope()
 
         Icon(
@@ -156,6 +319,8 @@
     }
 }
 
-private const val AnchorTestTag = "Anchor"
 private const val ContainerTestTag = "Container"
-private const val TextTestTag = "Text"
\ No newline at end of file
+private const val TextTestTag = "Text"
+private const val SubheadTestTag = "Subhead"
+private const val ActionTestTag = "Action"
+private const val AnchorTestTag = "Anchor'"
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
index 43ed254..1c3aae9 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/CalendarModel.android.kt
@@ -25,10 +25,10 @@
 import java.util.Locale
 
 /**
- * Creates a [CalendarModel] to be used by the date picker.
+ * Returns a [CalendarModel] to be used by the date picker.
  */
 @ExperimentalMaterial3Api
-internal actual fun createCalendarModel(): CalendarModel {
+internal actual fun CalendarModel(): CalendarModel {
     return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) {
         CalendarModelImpl()
     } else {
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
index 7ad1761..f77adef 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/DatePickerDialog.android.kt
@@ -25,6 +25,7 @@
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.wrapContentHeight
+import androidx.compose.material3.tokens.DatePickerModalTokens
 import androidx.compose.material3.tokens.DialogTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
@@ -43,9 +44,6 @@
  * A sample for displaying a [DatePicker] in a dialog:
  * @sample androidx.compose.material3.samples.DatePickerDialogSample
  *
- * A sample for displaying a [DateInput] in a dialog:
- * @sample androidx.compose.material3.samples.DateInputDialogSample
- *
  * @param onDismissRequest called when the user tries to dismiss the Dialog by clicking outside
  * or pressing the back button. This is not called when the dismiss button is clicked.
  * @param confirmButton button which is meant to confirm a proposed action, thus resolving what
@@ -81,11 +79,9 @@
         properties = properties
     ) {
         Surface(
-            // TODO: Use DatePickerModalTokens values for width and height after b/247694457 is
-            //  resolved.
             modifier = Modifier
-                .requiredWidth(ContainerWidth)
-                .heightIn(max = ContainerHeight),
+                .requiredWidth(DatePickerModalTokens.ContainerWidth)
+                .heightIn(max = DatePickerModalTokens.ContainerHeight),
             shape = shape,
             color = colors.containerColor,
             tonalElevation = tonalElevation,
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
index 44537c3..0ca4a17 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/Strings.android.kt
@@ -82,12 +82,17 @@
         Strings.DatePickerNavigateToYearDescription -> resources.getString(
             androidx.compose.material3.R.string.date_picker_navigate_to_year_description
         )
+
         Strings.DatePickerHeadlineDescription -> resources.getString(
             androidx.compose.material3.R.string.date_picker_headline_description
         )
+
         Strings.DatePickerNoSelectionDescription -> resources.getString(
             androidx.compose.material3.R.string.date_picker_no_selection_description
         )
+        Strings.DatePickerTodayDescription -> resources.getString(
+            androidx.compose.material3.R.string.date_picker_today_description
+        )
         Strings.DateInputTitle -> resources.getString(
             androidx.compose.material3.R.string.date_input_title
         )
@@ -100,7 +105,7 @@
         Strings.DateInputHeadlineDescription -> resources.getString(
             androidx.compose.material3.R.string.date_input_headline_description
         )
-        Strings.DateInputNoInputHeadlineDescription -> resources.getString(
+        Strings.DateInputNoInputDescription -> resources.getString(
             androidx.compose.material3.R.string.date_input_no_input_description
         )
         Strings.DateInputInvalidNotAllowed -> resources.getString(
@@ -112,9 +117,31 @@
         Strings.DateInputInvalidYearRange -> resources.getString(
             androidx.compose.material3.R.string.date_input_invalid_year_range
         )
+        Strings.DatePickerSwitchToCalendarMode -> resources.getString(
+            androidx.compose.material3.R.string.date_picker_switch_to_calendar_mode
+        )
+        Strings.DatePickerSwitchToInputMode -> resources.getString(
+            androidx.compose.material3.R.string.date_picker_switch_to_input_mode
+        )
         Strings.TooltipLongPressLabel -> resources.getString(
             androidx.compose.material3.R.string.tooltip_long_press_label
         )
+        Strings.TimePickerAM -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_am)
+        Strings.TimePickerPM -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_pm)
+        Strings.TimePickerPeriodToggle -> resources.getString(
+                androidx.compose.material3.R.string.time_picker_period_toggle_description)
+        Strings.TimePickerMinuteSelection -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_minute_selection)
+        Strings.TimePickerHourSelection -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_hour_selection)
+        Strings.TimePickerHourSuffix -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_hour_suffix)
+        Strings.TimePickerMinuteSuffix -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_minute_suffix)
+        Strings.TimePicker24HourSuffix -> resources.getString(
+            androidx.compose.material3.R.string.time_picker_hour_24h_suffix)
         else -> ""
     }
 }
@@ -124,5 +151,5 @@
     val raw = getString(string)
     val locale =
         ConfigurationCompat.getLocales(LocalConfiguration.current).get(0) ?: Locale.getDefault()
-    return String.format(raw, locale, *formatArgs)
-}
\ No newline at end of file
+    return String.format(locale, raw, *formatArgs)
+}
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TimeFormat.android.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TimeFormat.android.kt
new file mode 100644
index 0000000..ea45cd6
--- /dev/null
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/TimeFormat.android.kt
@@ -0,0 +1,26 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import android.text.format.DateFormat.is24HourFormat
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.ui.platform.LocalContext
+
+internal actual val is24HourFormat: Boolean
+    @Composable
+    @ReadOnlyComposable get() = is24HourFormat(LocalContext.current)
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 f4a8514..e46c3dd 100644
--- a/compose/material3/material3/src/androidMain/res/values-af/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-af/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Huidige keuse: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Geen"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Jaarkieser sigbaar"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 5094b8c..f8327988 100644
--- a/compose/material3/material3/src/androidMain/res/values-am/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-am/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ቀን ይምረጡ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"የተመረጠው ቀን"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ወደ ዓመት መምረጥ ቀይር"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ዓመት ለመምረጥ ያንሸራትቱ ወይም ወደ ቀንን መምረጥ መልሶ ለመቀየር መታ ያድርጉ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"ወደ የሚቀጥለው ወር ቀይር"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ወደ ቀዳሚው ወር ቀይር"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"ወደ ዓመት %1$s ያስሱ"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"የአሁን ምርጫ፦ %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ምንም"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ዓመት መራጭ ይታያል"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 d12bf54..f783a0f 100644
--- a/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ar/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"اختيار تاريخ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"التاريخ المحدَّد"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"التبديل لاختيار سنة"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"مرِّر سريعًا لتحديد عام، أو انقر للرجوع إلى تحديد يوم."</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"التغيير إلى الشهر التالي"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"التغيير إلى الشهر السابق"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"‏الانتقال إلى عام %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"‏التحديد الحالي: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"بدون تاريخ"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"أداة اختيار الأعوام مرئية"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 5fe35c4..3615dbf 100644
--- a/compose/material3/material3/src/androidMain/res/values-as/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-as/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"তাৰিখ বাছনি কৰক"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"বাছনি কৰা তাৰিখ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"বছৰ বাছনি কৰাৰ ছুইচ"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"এটা বছৰ বাছনি কৰিবলৈ ছোৱাইপ কৰক অথবা এটা দিন বাছনি কৰাৰ সুবিধাটোলৈ উভতি যাবলৈ টিপক"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"পৰৱৰ্তী মাহলৈ সলনি কৰক"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"পূৰ্বৱৰ্তী মাহলৈ সলনি কৰক"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"বৰ্ষ %1$sলৈ নেভিগে’ট কৰক"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"বৰ্তমানৰ বাছনি: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"একো নাই"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"বছৰ বাছনিকৰ্তা দৃশ্যমান"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 5fed3f0..0473c2c 100644
--- a/compose/material3/material3/src/androidMain/res/values-az/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-az/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Tarix seçin"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Seçilmiş tarix"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"İl seçiminə keçin"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"İl seçmək üçün sürüşdürün və ya gün seçiminə qayıtmaq üçün toxunun"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Növbəti aya dəyişin"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Əvvəlki aya dəyişin"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Bu ilə keçin: %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Cari seçim: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Heç bir"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"İl seçicisi görünür"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 1382020..c9ac82a 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
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Izaberite datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Izabrani datum"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Pređite na izbor godine"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Prevucite da biste izabrali godinu ili dodirnite da biste se vratili na izbor dana"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Pređite na sledeći mesec"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Pređite na prethodni mesec"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Idite na godinu: %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuelni izbor: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ništa"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Vidljiv birač godina"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 67fb0cd..ce5f78d 100644
--- a/compose/material3/material3/src/androidMain/res/values-be/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-be/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Выберыце дату"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Выбраная дата"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Перайсці да выбару года"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Правядзіце пальцам, каб выбраць год, або націсніце, каб вярнуцца да выбару даты"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Перайсці да наступнага месяца"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Перайсці да папярэдняга месяца"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Перайсці ў год %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Бягучы выбар: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Не выбрана"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Бачны інструмент выбару года"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 57cf932..08e44f8 100644
--- a/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bg/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Избиране на дата"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Избрана дата"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Превключване към избиране на година"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Прекарайте пръст, за да изберете година, или докоснете, за да се върнете към избора на ден"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Преминаване към следващия месец"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Преминаване към предишния месец"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Навигиране до %1$s година"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Текущ избор: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Без"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Инструментът за избор на година е видим"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 fafb3fe..6d4ef6e 100644
--- a/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bn/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"তারিখ বেছে নিন"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"বেছে নেওয়া তারিখ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"কোনও একটি বছর বেছে নিতে পাল্টে নিন"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"একটি বছর বেছে নিতে সোয়াইপ করুন অথবা কোনও একটি দিন বাছতে ফিরে গিয়ে সুইচে ট্যাপ করুন"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"আগামী মাসে পরিবর্তন করুন"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"আগের মাসে পরিবর্তন করুন"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"নেভিগেট করে %1$s বছরে যান"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"বর্তমানে বেছে নেওয়া হয়েছে: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"কোনওটিই নয়"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"বছর বেছে নেওয়ার তালিকা দেখা যাচ্ছে"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 f95ba3e..8c24d35 100644
--- a/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-bs/strings.xml
@@ -26,11 +26,20 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Odabir datuma"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Odabrani datum"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Prebaci na odabir godine"</string>
-    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Pomaknite se za odabir godine ili dodirnite za povratak na odabir dana"</string>
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Prevucite da odaberete godinu ili dodirnite da se vratite na odabir dana"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Promijeni na sljedeći mjesec"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Promijeni na prethodni mjesec"</string>
-    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Prelazak u godinu %1$s"</string>
-    <string name="date_picker_headline_description" msgid="4627306862713137085">"Trenutačni odabir: %1$s"</string>
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Odlazak na %1$s. godinu"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Trenutni odabir: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ništa"</string>
-    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Vidljiv je alat za odabir godine"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Birač godine je vidljiv"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Odaberite datum"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Datum unosa"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Datum"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Datum unosa: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Ništa"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Datum nije dopušten: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Datum se ne podudara s očekivanim uzorkom: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Datum je izvan očekivanog raspona godine %1$s – %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Prikaži opis"</string>
 </resources>
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 7e77c503..33269eb 100644
--- a/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ca/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Selecciona la data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data seleccionada"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Canvia a la selecció de l\'any"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Llisca per seleccionar un any o toca per tornar a seleccionar un dia"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Canvia al mes següent"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Canvia al mes anterior"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navega fins a l\'any %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Selecció actual: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Cap"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selector d\'any visible"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 fa85087..9f7ed3f 100644
--- a/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-cs/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuální výběr: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Žádné"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Je vidět výběr roku"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 48e0d97..7c05fee 100644
--- a/compose/material3/material3/src/androidMain/res/values-da/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-da/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Vælg dato"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valgt dato"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Skift til valg af år"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Stryg for at vælge et år, eller tryk for at skifte tilbage til datovælgeren"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Skift til næste måned"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Skift til forrige måned"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Naviger til år %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuelt valg: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ingen"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Årsvælgeren er synlig"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 400832b..ab55db2 100644
--- a/compose/material3/material3/src/androidMain/res/values-de/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-de/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Datum auswählen"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Ausgewähltes Datum"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Zur Jahresauswahl wechseln"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Wischen, um ein Jahr auszuwählen, oder tippen, um zur Tagesauswahl zurückzukehren"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Zum nächsten Monat wechseln"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Zum vorherigen Monat wechseln"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Zum Jahr %1$s wechseln"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuelle Auswahl: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Keine"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Jahresauswahl sichtbar"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 eba81b8..24ada47 100644
--- a/compose/material3/material3/src/androidMain/res/values-el/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-el/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Τρέχουσα επιλογή: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Καμία"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Το εργαλείο επιλογής έτους είναι ορατό"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 8313892..b67749f 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
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Current selection: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"None"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Year picker visible"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Select date"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Entered date"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Date"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Entered date: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"None"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Date not allowed: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Date does not match expected pattern: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Date out of expected year range %1$s – %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
 </resources>
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 e6e1c93..4f43a60 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
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Current selection: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"None"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Year picker visible"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Select date"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Entered date"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Date"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Entered date: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"None"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Date not allowed: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Date does not match expected pattern: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Date out of expected year range %1$s - %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
 </resources>
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 8313892..b67749f 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
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Current selection: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"None"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Year picker visible"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Select date"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Entered date"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Date"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Entered date: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"None"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Date not allowed: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Date does not match expected pattern: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Date out of expected year range %1$s – %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
 </resources>
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 8313892..b67749f 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
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Current selection: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"None"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Year picker visible"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Select date"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Entered date"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Date"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Entered date: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"None"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Date not allowed: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Date does not match expected pattern: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Date out of expected year range %1$s – %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Show tooltip"</string>
 </resources>
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 3d4a007..5d47ca6 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
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‎‎‎‎‎‎‏‏‎‏‏‏‎‏‏‏‏‏‏‏‎‎‎‏‎‎‏‏‎‎‏‏‏‎‏‎‏‎‏‏‏‏‏‎‎‏‏‎‎‏‏‏‏‎‏‏‏‏‎‏‎Current selection: %1$s‎‏‎‎‏‎"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‏‎‎‎‏‎‎‎‏‎‎‏‎‏‎‏‎‏‎‎‎‎‎‎‎‏‎‎‏‏‎‏‎‎‎‎‏‎‏‎‎‏‎‎‏‏‏‎‎‏‎‏‏‎None‎‏‎‎‏‎"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‎‎‎‏‏‏‏‏‎‎‎‎‎‏‏‏‏‏‎‎‎‏‏‎‎‏‎‏‎‎‎‏‏‎‏‎‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‎‏‎‎‎‎‎‎‎Year picker visible‎‏‎‎‏‎"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‏‏‏‎‎‎‏‏‏‎‎‎‏‎‎‏‏‏‏‎‏‎‏‎‎‎‎‎‎‎‏‏‎‏‎‏‎‏‎‏‎‏‏‏‏‎‎‏‏‎‎‎‎‏‎‎‎‎Select date‎‏‎‎‏‎"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‎‎‎‏‎‎‏‎‎‎‏‎‎‏‏‏‎‏‏‏‎‎‎‏‎‏‎‎‏‎‎‏‎‎‎‏‏‏‎‏‏‏‎‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‎Entered date‎‏‎‎‏‎"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‎‎‎‎‏‎‏‏‏‏‎‏‏‏‎‏‎‏‎‎‏‎‏‏‏‎‏‎‎‎‎‎‎‏‎‎‏‏‏‎‎‏‎‎‏‎‎‎‏‏‎‎‏‎‎‏‎‎Date‎‏‎‎‏‎"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‏‏‏‎‎‏‏‎‎‏‏‎‎‏‎‏‎‏‏‎‎‎‏‏‏‏‎‏‎‎‎‎‎‎‎‎‎‏‏‎‎‏‎‎‎‎‏‎‏‎‎Entered date: %1$s‎‏‎‎‏‎"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‏‏‏‎‏‏‎‏‎‏‏‏‏‏‎‏‏‏‏‏‎‎‎‎‏‎‎‎‎‎‏‏‏‏‏‏‎‏‎‏‎‎‎‏‎‎‎‏‎‏‎‎‎‎‎‎‏‎‎‎None‎‏‎‎‏‎"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‏‎‏‎‎‏‏‎‏‏‏‎‎‎‎‎‏‏‎‏‏‏‏‎‏‎‏‎‎‎‎‏‏‏‏‎‏‎‎‎‎‏‎‏‏‎‎‎‏‏‎‎‎‏‎‎‎‎‎‏‏‎Date not allowed: %1$s‎‏‎‎‏‎"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‎‎‏‎‎‏‎‏‎‎‏‏‎‎‏‏‎‏‏‎‏‎‏‎‎‎‎‎‏‏‎‏‎‏‏‏‏‎‏‏‏‎‏‎‎‏‏‎‎‎‎‏‎‎‎‎‏‏‎‎‎‏‎Date does not match expected pattern: %1$s‎‏‎‎‏‎"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‏‎‎‎‎‏‎‏‏‏‏‏‏‏‏‎‎‎‏‎‎‏‎‏‏‏‎‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‎‎‎‎‎‎‏‎‎‎‏‎‎‎‎‎Date out of expected year range %1$s - %2$s‎‏‎‎‏‎"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‎‎‏‎‏‏‏‏‎‏‏‎‎‏‏‎‏‏‏‏‏‎‎‏‏‏‏‏‏‏‎‎‎‏‎‏‎‏‎‏‎‎‏‎‏‎‎‎‎‏‏‎‏‏‏‎‏‏‏‎‏‎Show tooltip‎‏‎‎‏‎"</string>
 </resources>
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 79a162e..816a701 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
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Seleccionar fecha"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Fecha seleccionada"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Cambiar a seleccionar un año"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Desliza el dedo para elegir un año o presiona para volver a seleccionar un día"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Cambiar al mes siguiente"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Cambiar al mes anterior"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navegar al año %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Selección actual: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nada"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selector de año visible"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 992987e..f3e7902 100644
--- a/compose/material3/material3/src/androidMain/res/values-es/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-es/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Seleccionar fecha"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Fecha seleccionada"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Cambiar para seleccionar un año"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Desliza el dedo para seleccionar un año o toca para volver a seleccionar un día"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Cambiar al mes siguiente"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Cambiar al mes anterior"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Ir al año %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Selección: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ninguno"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selector de año visible"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 160fc4b..834e1de 100644
--- a/compose/material3/material3/src/androidMain/res/values-et/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-et/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Valige kuupäev"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valitud kuupäev"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Lülitu aasta valimisele"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Pühkige aasta valimiseks või puudutage, et minna tagasi päeva valimise juurde"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Vaheta järgmisele kuule"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Vaheta eelmisele kuule"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Liigu aasta %1$s juurde"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Praegune valik: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Pole"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Aasta valija on nähtav"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 e29dbb6..f879ef1 100644
--- a/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-eu/strings.xml
@@ -26,16 +26,20 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Hautatu data bat"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Hautatutako data"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Joan urte-hautatzailera"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Pasatu hatza urte bat hautatzeko. Bestela, sakatu hau eguna hautatzeko pantailara itzultzeko."</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Aldatu hurrengo hilabetera"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Aldatu aurreko hilabetera"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
-    <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
-    <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
-    <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
-    <skip />
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Joan %1$s urtera"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Oraingo hautapena: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Bat ere ez"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Urte-hautatzailea ikusgai dago"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Hautatu data bat"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Idatzitako data"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Data"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Idatzitako data: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Bat ere ez"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Ez da onartzen data: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Data ez dator bat espero den ereduarekin: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Espero den urte tartetik (%1$s-%2$s) kanpo dago data"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Erakutsi aholkua"</string>
 </resources>
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 118d6a0..e31b5a8 100644
--- a/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fa/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"‏انتخاب فعلی: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"هیچ‌کدام"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"انتخابگر سال نمایان است"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 b28202f..0a5817a 100644
--- a/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fi/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Valitse päivämäärä"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valittu päivämäärä"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Vaihda vuoden valintaan"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Valitse vuosi pyyhkäisemällä tai palaa päivän valintaan napauttamalla"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Vaihda seuraavaan kuukauteen"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Vaihda edelliseen kuukauteen"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Siirry vuoteen %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Nykyinen valinta: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"–"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Vuosivalitsin näkyvillä"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 d173840..0ec8d9b 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
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Sélectionnez une date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Date sélectionnée"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Passer à la sélection d\'une année"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Balayez l\'écran pour sélectionner une année, ou touchez pour revenir en arrière et sélectionner un jour"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Passer au mois suivant"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Passer au mois précédent"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Naviguez jusqu\'à l\'année %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Sélection actuelle : %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Aucune"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Sélecteur d\'année visible"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </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 e77a027..ab3346f 100644
--- a/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-fr/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Sélectionner une date"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Date sélectionnée"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Passer à la sélection d\'une année"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Balayez l\'écran pour sélectionner une année ou appuyez pour revenir à la sélection d\'un jour"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Passer au mois suivant"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Passer au mois précédent"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Accéder à l\'année %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Sélection actuelle : %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Aucune"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Sélecteur d\'année visible"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 d26efaa..111bc92 100644
--- a/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gl/strings.xml
@@ -38,4 +38,22 @@
     <skip />
     <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
     <skip />
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 2004438..dd3faef 100644
--- a/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-gu/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"હાલની પસંદગી: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"એકપણ નહીં"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"વર્ષ માટેનું પિકર દૃશ્યમાન છે"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 938a6ef..f699414 100644
--- a/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hi/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"तारीख चुनें"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"चुनी गई तारीख"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"साल चुनने के लिए स्विच करें"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"साल चुनने के लिए स्वाइप करें या दिन चुनने पर वापस स्विच करने लिए टैप करें"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"अगले महीने पर जाएं"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"पिछले महीने पर जाएं"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"साल %1$s पर जाएं"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"फ़िलहाल, यह चुना गया है: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"कोई नहीं"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"साल चुनने का विकल्प दिख रहा है"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 beb7a13..3b70225 100644
--- a/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hr/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Trenutačni odabir: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ništa"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Vidljiv je alat za odabir godine"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Odaberite datum"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Datum unosa"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Datum"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Datum unosa: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Ništa"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Datum nije dopušten: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Datum se ne podudara s očekivanim uzorkom: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Datum je izvan očekivanog raspona godine %1$s – %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Prikaži opis"</string>
 </resources>
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 89a5d5e..55ef980b 100644
--- a/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hu/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Dátum kiválasztása"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Kiválasztott dátum"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Váltson a kívánt év kiválasztásához"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Csúsztatással kiválaszthatja a kívánt évet, vagy koppintással visszaválthat a nap kiválasztásához."</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Váltás a következő hónapra"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Váltás az előző hónapra"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navigálás a következő évhez: %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Jelenleg kiválasztva: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nincs"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Látható az évválasztó"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 2423e17..8f81997 100644
--- a/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-hy/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Ընտրեք ամսաթիվը"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Ընտրված ամսաթիվ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Անցնել տարվա ընտրությանը"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Սահեցրեք՝ տարեթիվ ընտրելու համար, կամ հպեք՝ օրվա ընտրությանը վերադառնալու համար"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Անցնել հաջորդ ամսվան"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Անցնել նախորդ ամսվան"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Անցնել %1$s թվական"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Ընթացիկ ընտրությունը՝ %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ոչ մեկը"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Տարեթվի ցուցադրվող ընտրիչ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 be0a178..7f7eae1 100644
--- a/compose/material3/material3/src/androidMain/res/values-in/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-in/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Pilih tanggal"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tanggal yang dipilih"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Beralih ke memilih tahun"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Geser untuk memilih tahun, atau ketuk untuk beralih kembali ke pemilihan tanggal"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Ubah ke bulan berikutnya"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Ubah ke bulan sebelumnya"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Pilih tahun %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Pilihan saat ini: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Tidak ada"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Pemilih tahun terlihat"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 cf44f62..0e63211 100644
--- a/compose/material3/material3/src/androidMain/res/values-is/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-is/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Velja dagsetningu"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valin dagsetning"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Skipta yfir í val á ári"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Strjúktu til að velja ár eða ýttu til að skipta aftur yfir í að velja dag"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Breyta í næsta mánuð"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Breyta í fyrri mánuð"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Fletta til ársins %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Núverandi val: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ekkert"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Ársval birt"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 b9b990d..0ecf89f 100644
--- a/compose/material3/material3/src/androidMain/res/values-it/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-it/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Selezione attuale: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nessuna selezione"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selettore dell\'anno visibile"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Seleziona data"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Data inserita"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Data"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Data inserita: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Nessuna"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Data non consentita: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"La data non corrisponde al pattern previsto: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"La data non rientra nell\'intervallo di anni previsto (%1$s-%2$s)"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Mostra descrizione comando"</string>
 </resources>
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 28b8957..3224130 100644
--- a/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-iw/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"בחירת תאריך"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"התאריך הנבחר"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"החלפה לבחירה של שנה"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"יש להחליק כדי לבחור שנה, או להקיש כדי לחזור לבחירת היום"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"מעבר לחודש הבא"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"מעבר לחודש הקודם"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"‏ניווט לשנת %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"‏הבחירה הנוכחית: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ללא"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"בורר השנה גלוי"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 8a3660e..c28cc19 100644
--- a/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ja/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"現在の選択: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"なし"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"年の選択ツールの表示"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"日付を選択"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"入力された日付"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"日付"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"入力された日付: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"なし"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"許可されていない日付です: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"想定されるパターンと日付が一致しません: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"想定される年の範囲(%1$s~%2$s)から日付が外れています"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"ツールチップを表示します"</string>
 </resources>
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 ddb7477..dc3fa1f 100644
--- a/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ka/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"ამჟამინდელი არჩევანი: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"არცერთი"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"არჩეული წელი ხილულია"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 fde6ac6..3c86ac5 100644
--- a/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kk/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Күн таңдау"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Таңдалған күн"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Жыл таңдауға өту"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Жыл таңдау үшін сырғытыңыз. Күн таңдауға ауысу үшін түртіңіз."</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Келесі айға өзгерту"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Алдыңғы айға өзгерту"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Мына жылға өту: %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Қазіргі таңдау: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ешқандай"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Көрсетілген жыл таңдағышы"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 8570b94..af6061a 100644
--- a/compose/material3/material3/src/androidMain/res/values-km/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-km/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ជ្រើសរើស​កាលបរិច្ឆេទ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"កាលបរិច្ឆេទដែលបាន​ជ្រើសរើស"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ប្ដូរទៅ​ការជ្រើសរើសឆ្នាំ"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"អូសដើម្បីជ្រើសរើសឆ្នាំ ឬចុចដើម្បីប្ដូរត្រឡប់ទៅការជ្រើសរើសថ្ងៃវិញ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"ប្ដូរ​ទៅ​ខែបន្ទាប់"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ប្ដូរ​ទៅ​ខែមុន"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"រុករកទៅកាន់ឆ្នាំ %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"ការជ្រើសរើសបច្ចុប្បន្ន៖ %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"គ្មាន"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"អាចមើលឃើញផ្ទាំងជ្រើសរើសឆ្នាំ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 90f8006..a146b0f 100644
--- a/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-kn/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"ಪ್ರಸ್ತುತ ಆಯ್ಕೆ: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ಯಾವುದೂ ಅಲ್ಲ"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ವರ್ಷದ ಪಿಕರ್ ಗೋಚರಿಸುತ್ತದೆ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 e9d0cea..4058800 100644
--- a/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ko/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"날짜 선택"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"선택한 날짜"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"연도 선택으로 전환"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"스와이프하여 연도를 선택하거나 탭하여 날짜 선택으로 돌아가세요."</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"다음 달로 변경"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"이전 달로 변경"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s년으로 이동"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"현재 선택사항: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"없음"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"연도 선택 도구 표시"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 4e73680..2a2be27 100644
--- a/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ky/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Күндү тандоо"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Тандалган күн"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Жыл тандоого которулуу"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Жылды тандоо үчүн экранды сүрүңүз же күндү тандоого кайтуу үчүн таптап коюңуз"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Кийинки айга өзгөртүү"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Мурунку айга өзгөртүү"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s-жылга өтүү"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Учурда %1$s тандалды"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Жок"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Көрсөтүлгөн жыл тандагыч"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 7f74e4b..ad23c6d 100644
--- a/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lo/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ເລືອກວັນທີ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ວັນທີທີ່ເລືອກໄວ້"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ປ່ຽນໄປເລືອກປີ"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ປັດເພື່ອເລືອກປີ ຫຼື ແຕະເພື່ອສະຫຼັບກັບໄປຫາການເລືອກວັນ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"ປ່ຽນເປັນເດືອນຕໍ່ໄປ"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ປ່ຽນເປັນເດືອນຜ່ານມາ"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"ນຳທາງໄປຫາປີ %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"ການເລືອກປັດຈຸບັນ: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ບໍ່ມີ"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ສະແດງຕົວເລືອກປີ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 b146f78..1d88730 100644
--- a/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lt/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Dabartinis pasirinkimas: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nėra"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Rodomas metų parinkiklis"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Pasirinkite datą"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Įvesta data"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Data"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Įvesta data: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Nėra"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Data neleidžiama: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Data neatitinka numatyto šablono: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Data nepatenka į numatytų metų diapazoną: %1$s–%2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Rodyti patarimą"</string>
 </resources>
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 2a61852..34e073d 100644
--- a/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-lv/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Atlasīt datumu"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Atlasītais datums"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Pāriet uz gada atlasi"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Velciet, lai atlasītu gadu, vai pieskarieties, lai pārietu atpakaļ pie dienas atlases"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Mainīt uz nākamo mēnesi"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Mainīt uz iepriekšējo mēnesi"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Pāriet uz %1$s. gadu"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Pašreizējā atlase: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nav"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Redzams gada atlasītājs"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 2a853e8..5ade032 100644
--- a/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mk/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Тековен избор: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Нема"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Избирачот на година е видлив"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Изберете датум"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Внесен датум"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Датум"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Внесен датум: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Нема"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Датумот не е дозволен: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Не се совпаѓа со очекуваната шема: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Датумот не е во очекуваниот опсег на години %1$s - %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Прикажи совет за алатка"</string>
 </resources>
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 534a6c9..9923a3b 100644
--- a/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ml/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"നിലവിലെ തിരഞ്ഞെടുപ്പ്: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ഒന്നുമില്ല"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"വർഷ പിക്കർ ദൃശ്യമാണ്"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 46fbedf..1cc6860 100644
--- a/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mn/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Огноо сонгох"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Сонгосон огноо"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Жил сонгох руу сэлгэх"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Он сонгохын тулд шудрах эсвэл өдөр сонгох руу буцааж сэлгэхийн тулд товшино уу"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Дараагийн сар луу өөрчлөх"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Өмнөх сар луу өөрчлөх"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s он руу шилжих"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Одоогийн сонголт: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Байхгүй"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Он сонгогч харагдаж байна"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 449917a..575a698 100644
--- a/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-mr/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"तारीख निवडा"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"निवडलेली तारीख"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"वर्ष निवडणे वर स्विच करा"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"वर्ष निवडण्यासाठी स्‍वाइप करा, किंवा दिवस निवडण्यावर परत स्विच करण्यासाठी टॅप करा"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"पुढील महिन्यावर बदला"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"मागील महिन्यावर बदला"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s वर्षावर नेव्हिगेट करा"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"सद्य निवड: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"काहीही नाही"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"वर्ष पिकर दृश्यमान आहे"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 d477ca3..e588581 100644
--- a/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ms/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Pilihan semasa: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Tiada"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Pemilih tahun kelihatan"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 4bf4229..3204728 100644
--- a/compose/material3/material3/src/androidMain/res/values-my/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-my/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ရက်စွဲရွေးရန်"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ရွေးထားသည့် ရက်စွဲ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"နှစ်ရွေးခြင်းသို့ ပြောင်းရန်"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ခုနှစ်ရွေးချယ်ရန် ပွတ်ဆွဲပါ (သို့) ရက်ရွေးချယ်ခြင်းသို့ ပြန်ရန် တို့ပါ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"နောက်လသို့ ပြောင်းရန်"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ယခင်လသို့ ပြောင်းရန်"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s ခုနှစ်သို့ သွားရန်"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"လက်ရှိ ရွေးချယ်မှု- %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"မရှိ"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ခုနှစ်ရွေးချယ်ရေးစနစ်ကို မြင်ရသည်"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 31e04ee..eebe6bc 100644
--- a/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nb/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Velg dato"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valgt dato"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Bytt til å velge et år"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Sveip for å velge år, eller trykk for å bytte tilbake til valg av dag"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Endre til neste måned"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Endre til forrige måned"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Gå til år %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Valgt: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ingen"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Årsvelgeren er synlig"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 cf60f8b..4b950f9 100644
--- a/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ne/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"हालको छनौट: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"कुनै पनि होइन"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"साल पिकर देखिएको छ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 e27d1b0..2ff3515 100644
--- a/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-nl/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Huidige selectie: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Geen"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Jaarselectie zichtbaar"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 38cdf14..912bdbb 100644
--- a/compose/material3/material3/src/androidMain/res/values-or/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-or/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ତାରିଖ ଚୟନ କରନ୍ତୁ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ଚୟନିତ ତାରିଖ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ବର୍ଷ ଚୟନ କରିବାକୁ ସ୍ୱିଚ କରନ୍ତୁ"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ଏକ ବର୍ଷ ଚୟନ କରିବା ପାଇଁ ସ୍ୱାଇପ କରନ୍ତୁ କିମ୍ବା ଏକ ଦିନ ଚୟନ କରିବା ପାଇଁ ପୁଣି ସ୍ୱିଚ କରିବାକୁ ଟାପ କରନ୍ତୁ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"ପରବର୍ତ୍ତୀ ମାସକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ପୂର୍ବବର୍ତ୍ତୀ ମାସକୁ ପରିବର୍ତ୍ତନ କରନ୍ତୁ"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s ବର୍ଷକୁ ନାଭିଗେଟ କରନ୍ତୁ"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"ବର୍ତ୍ତମାନର ଚୟନ: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"କିଛି ନାହିଁ"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ବର୍ଷ ପିକର ଦେଖାଯାଉଛି"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 472ec3b..6d49ad8 100644
--- a/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pa/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"ਤਾਰੀਖ ਚੁਣੋ"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"ਚੁਣੀ ਗਈ ਤਾਰੀਖ"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ਸਾਲ ਚੁਣਨ ਲਈ ਸਵਿੱਚ ਕਰੋ"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ਕੋਈ ਸਾਲ ਚੁਣਨ ਲਈ ਸਵਾਈਪ ਕਰੋ ਜਾਂ ਕੋਈ ਦਿਨ ਚੁਣਨ ਲਈ ਵਾਪਸ ਜਾਣ ਵਾਸਤੇ ਟੈਪ ਕਰੋ"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"ਅਗਲੇ ਮਹੀਨੇ \'ਤੇ ਜਾਓ"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"ਪਿਛਲੇ ਮਹੀਨੇ \'ਤੇ ਜਾਓ"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"ਸਾਲ %1$s \'ਤੇ ਜਾਓ"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"ਮੌਜੂਦਾ ਚੋਣ: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ਕੋਈ ਨਹੀਂ"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ਸਾਲ ਚੋਣਕਾਰ ਦਿਖਣਯੋਗ ਹੈ"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 5ef83f1..2daa8a8 100644
--- a/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pl/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Wybierz datę"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Wybrana data"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Przełącz na wybór roku"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Przesuń, aby wybrać rok, lub wróć do poprzedniej sekcji i wybierz dzień"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Zmień na następny miesiąc"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Zmień na poprzedni miesiąc"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Przejdź do roku %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Obecnie wybrane: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Brak"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Widoczny selektor roku"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 2856968..6b6c723 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
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Selecionar data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selecionada"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Trocar para a seleção de ano"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Deslize para selecionar um ano ou toque para voltar à seleção de dia"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Mudar para o próximo mês"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Mudar para o mês anterior"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navegar para o ano de %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Seleção atual: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nenhuma"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Seletor de dia visível"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 f6ac645..86e9c17 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
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Seleção atual: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nenhuma"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selecionador de ano visível"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 2856968..6b6c723 100644
--- a/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-pt/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Selecionar data"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Data selecionada"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Trocar para a seleção de ano"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Deslize para selecionar um ano ou toque para voltar à seleção de dia"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Mudar para o próximo mês"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Mudar para o mês anterior"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navegar para o ano de %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Seleção atual: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Nenhuma"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Seletor de dia visível"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 1415a48..75def7c 100644
--- a/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ro/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Opțiunea selectată: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Fără"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Selectorul de an este vizibil"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 c019d05..b6296a1 100644
--- a/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ru/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Текущий выбор: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Не выбрано"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Отображаемый выбор года"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 f0b9773..e6acdba 100644
--- a/compose/material3/material3/src/androidMain/res/values-si/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-si/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"වත්මන් තේරීම: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"කිසිවක් නැත"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"වසර තෝරකය දෘශ්‍යමානයි"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 e100c8d..f1dc227 100644
--- a/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sk/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuálny výber: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Žiadne"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Výber roka je viditeľný"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 46b3eee..6a6b270 100644
--- a/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sl/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Trenutna izbira: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Brez"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Izbirnik leta je viden"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 f26d8ad..b7c198e 100644
--- a/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sq/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Zgjedhja aktuale: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Asnjë"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Zgjedhësi i vitit i dukshëm"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 c6a75d1..aff6c1a 100644
--- a/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sr/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Изаберите датум"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Изабрани датум"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Пређите на избор године"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Превуците да бисте изабрали годину или додирните да бисте се вратили на избор дана"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Пређите на следећи месец"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Пређите на претходни месец"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Идите на годину: %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Актуелни избор: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Ништа"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Видљив бирач година"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 f36393e..57638de 100644
--- a/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sv/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Välj datum"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Valt datum"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Byt till att välja år"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Svep för att välja ett år eller tryck för att återgå till att välja en dag"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Ändra till nästa månad"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Ändra till föregående månad"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Navigera till %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Aktuellt val: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Inget"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Årväljaren är synlig"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 b6a2d54..1495a76 100644
--- a/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-sw/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Chagua tarehe"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tarehe uliyochagua"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Nenda kwenye sehemu ya kuchagua mwaka"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Telezesha ili uchague mwaka au gusa ili urejee kwenye kuchagua siku"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Nenda kwenye mwezi unaofuata"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Rudi kwenye mwezi uliotangulia"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Nenda kwenye mwaka %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Iliyochaguliwa sasa: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Hamna"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Kiteua mwaka kinaonekana"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 beb62e1..c0c83ef 100644
--- a/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ta/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"தேதியைத் தேர்ந்தெடுக்கவும்"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"தேர்ந்தெடுக்கப்பட்ட தேதி"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"ஆண்டைத் தேர்ந்தெடுக்கும் விருப்பத்திற்கு மாற்று"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"ஆண்டைத் தேர்வுசெய்ய ஸ்வைப் செய்யுங்கள் அல்லது தேதியைத் தேர்வுசெய்யும் பக்கத்திற்கு மீண்டும் செல்ல தட்டுங்கள்"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"அடுத்த மாதத்திற்கு மாற்று"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"முந்தைய மாதத்திற்கு மாற்று"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$sக்குச் செல்லும்"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"தற்போது %1$s தேர்வுசெய்யப்பட்டுள்ளது"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ஏதுமில்லை"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"ஆண்டைத் தேர்வுசெய்யும் பக்கம் காட்டப்படுகிறது"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 3b94184..5dd0adb 100644
--- a/compose/material3/material3/src/androidMain/res/values-te/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-te/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"ప్రస్తుత ఎంపిక: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ఏదీ లేదు"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"సంవత్సరం పికర్ కనిపిస్తుంది"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 798a2fb3..3033d65 100644
--- a/compose/material3/material3/src/androidMain/res/values-th/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-th/strings.xml
@@ -33,4 +33,13 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"การเลือกปัจจุบัน: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"ไม่มี"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"แสดงตัวเลือกปี"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"เลือกวันที่"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"วันที่ป้อน"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"วันที่"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"วันที่ป้อน: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"ไม่มี"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"ไม่อนุญาตให้ใช้วันที่นี้: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"วันที่ไม่ตรงกับรูปแบบที่คาดไว้: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"วันที่อยู่นอกเหนือจากช่วงปีที่คาดไว้ %1$s - %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"แสดงเคล็ดลับเครื่องมือ"</string>
 </resources>
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 dec41d4..dfdc9b2 100644
--- a/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tl/strings.xml
@@ -26,16 +26,20 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Pumili ng petsa"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Piniling petsa"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Lumipat sa pagpili ng taon"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Mag-swipe para pumili ng taon, o mag-tap para bumalik sa pagpili ng araw"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Lumipat sa susunod na buwan"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Lumipat sa nakaraang buwan"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
-    <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
-    <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
-    <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
-    <skip />
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Mag-navigate papunta sa taong %1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Kasalukuyang napili: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Wala"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Nakikita ang picker ng taon"</string>
+    <string name="date_input_title" msgid="3010396677286327048">"Pumili ng petsa"</string>
+    <string name="date_input_headline" msgid="3499643850558715142">"Inilagay na petsa"</string>
+    <string name="date_input_label" msgid="5194825853981987218">"Petsa"</string>
+    <string name="date_input_headline_description" msgid="8562356184193964298">"Inilagay na petsa: %1$s"</string>
+    <string name="date_input_no_input_description" msgid="5722931102250207748">"Wala"</string>
+    <string name="date_input_invalid_not_allowed" msgid="6114792992433444995">"Hindi pinapayagan ang petsa: %1$s"</string>
+    <string name="date_input_invalid_for_pattern" msgid="5281836720766682161">"Hindi tumutugma ang petsa sa inaasahang pattern: %1$s"</string>
+    <string name="date_input_invalid_year_range" msgid="8434112129235255568">"Wala ang petsa sa inaasahang hanay ng taon na %1$s - %2$s"</string>
+    <string name="tooltip_long_press_label" msgid="2732804537909054941">"Ipakita ang tooltip"</string>
 </resources>
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 8f8654b..b8de10e 100644
--- a/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-tr/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Tarih seç"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Seçilen tarih"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Yıl seçimine geç"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Kaydırarak bir yıl seçin veya gün seçme bölümüne geri dönmek için dokunun"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Sonraki aya değiştir"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Önceki aya değiştir"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s yılına gidin"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Geçerli seçim: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Yok"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Yıl seçici görünür durumda"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 117e86a..cb30959 100644
--- a/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uk/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Вибрати дату"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Вибрана дата"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Перейти до вибору року"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Проведіть пальцем по екрану, щоб вибрати рік, або торкніться, щоб повернутися до вибору дня"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Перейти до наступного місяця"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Перейти до попереднього місяця"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"Перейти до %1$s року"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Поточний вибір: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Немає"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Показувати засіб вибору року"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 c79a153..dd566cd 100644
--- a/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-ur/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"‏موجودہ انتخاب: ‎%1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"کوئی نہیں"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"سال کا منتخب کنندہ مرئی ہے"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 5eaeaad..c93a3ac 100644
--- a/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-uz/strings.xml
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"Sanani tanlang"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"Tanlangan sana"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"Tanlangan yilga oʻtish"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"Yilni tanlash uchun suring yoki kunni tanlashga qaytish uchun tegining"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"Keyingi oyga oʻzgartirish"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"Avvalgi oyga oʻzgartirish"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"%1$s-yilga oʻtish"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"Joriy tanlov: %1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Hech biri"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Yil tanlagich ochiq"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 7249833..0108f77 100644
--- a/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-vi/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Lựa chọn hiện tại: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Không có"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Bộ chọn năm hiển thị"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 eebf583..a6362df 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
@@ -26,16 +26,29 @@
     <string name="date_picker_title" msgid="9208721003668059792">"选择日期"</string>
     <string name="date_picker_headline" msgid="2846784065735639969">"选定的日期"</string>
     <string name="date_picker_switch_to_year_selection" msgid="3412370019845183965">"切换以选择年份"</string>
-    <!-- no translation found for date_picker_switch_to_day_selection (145089358343568971) -->
-    <skip />
+    <string name="date_picker_switch_to_day_selection" msgid="145089358343568971">"滑动可选择年份,点按可切换回选择日期"</string>
     <string name="date_picker_switch_to_next_month" msgid="8313783187901412102">"转到下个月"</string>
     <string name="date_picker_switch_to_previous_month" msgid="7596294429748914881">"转到上个月"</string>
-    <!-- no translation found for date_picker_navigate_to_year_description (5152441868029453612) -->
+    <string name="date_picker_navigate_to_year_description" msgid="5152441868029453612">"切换到年份:%1$s"</string>
+    <string name="date_picker_headline_description" msgid="4627306862713137085">"当前的选择:%1$s"</string>
+    <string name="date_picker_no_selection_description" msgid="5724377114289981899">"无"</string>
+    <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"年份选择器可见"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
     <skip />
-    <!-- no translation found for date_picker_headline_description (4627306862713137085) -->
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
     <skip />
-    <!-- no translation found for date_picker_no_selection_description (5724377114289981899) -->
+    <!-- no translation found for date_input_label (5194825853981987218) -->
     <skip />
-    <!-- no translation found for date_picker_year_picker_pane_title (8140324713311804736) -->
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
     <skip />
 </resources>
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 afb2590..c41edfe 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
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"目前選項:%1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"無"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"顯示年分挑選器"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 375ebe2..6ea7600 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
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"目前選項:%1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"無"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"顯示年份挑選器"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
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 74dbf47..093f8fb 100644
--- a/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values-zu/strings.xml
@@ -33,4 +33,22 @@
     <string name="date_picker_headline_description" msgid="4627306862713137085">"Ukukhetha kwamanje: %1$s"</string>
     <string name="date_picker_no_selection_description" msgid="5724377114289981899">"Lutho"</string>
     <string name="date_picker_year_picker_pane_title" msgid="8140324713311804736">"Isikhethi sonyaka siyabonakala"</string>
+    <!-- no translation found for date_input_title (3010396677286327048) -->
+    <skip />
+    <!-- no translation found for date_input_headline (3499643850558715142) -->
+    <skip />
+    <!-- no translation found for date_input_label (5194825853981987218) -->
+    <skip />
+    <!-- no translation found for date_input_headline_description (8562356184193964298) -->
+    <skip />
+    <!-- no translation found for date_input_no_input_description (5722931102250207748) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_not_allowed (6114792992433444995) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_for_pattern (5281836720766682161) -->
+    <skip />
+    <!-- no translation found for date_input_invalid_year_range (8434112129235255568) -->
+    <skip />
+    <!-- no translation found for tooltip_long_press_label (2732804537909054941) -->
+    <skip />
 </resources>
diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml
index dd708e6..cdbd37b 100644
--- a/compose/material3/material3/src/androidMain/res/values/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values/strings.xml
@@ -40,6 +40,7 @@
     <string name="date_picker_navigate_to_year_description">Navigate to year %1$s</string>
     <string name="date_picker_headline_description">Current selection: %1$s</string>
     <string name="date_picker_no_selection_description">None</string>
+    <string name="date_picker_today_description">Today</string>
     <string name="date_picker_year_picker_pane_title">Year picker visible</string>
     <string name="date_input_title">Select date</string>
     <string name="date_input_headline">Entered date</string>
@@ -51,6 +52,24 @@
     <string name="date_input_invalid_year_range">
         Date out of expected year range %1$s - %2$s
     </string>
+    <string name="date_picker_switch_to_calendar_mode">Switch to calendar input mode</string>
+    <string name="date_picker_switch_to_input_mode">Switch to text input mode</string>
     <!-- Spoken description of a tooltip -->
     <string name="tooltip_long_press_label">Show tooltip</string>
+    <!-- Suffix for time in 12-hour standard, after noon. [CHAR_LIMIT=2]" -->
+    <string name="time_picker_pm">PM</string>
+    <!-- Suffix for time in 12-hour standard, before noon. [CHAR_LIMIT=2]" -->
+    <string name="time_picker_am">AM</string>
+    <!-- Description for the toggle to choose between AM and PM [CHAR_LIMIT=NONE] -->
+    <string name="time_picker_period_toggle_description">Select AM or PM</string>
+    <!-- Description for button to switch to select the hour [CHAR_LIMIT=NONE] -->
+    <string name="time_picker_hour_selection">Select hour</string>
+    <!-- Description for button to switch to select the minute [CHAR_LIMIT=NONE] -->
+    <string name="time_picker_minute_selection">Select minutes</string>
+    <!-- Spoken suffix for an hour in the clock [CHAR_LIMIT=10] -->
+    <string name="time_picker_hour_suffix">%1$d o\'clock</string>
+    <!-- Spoken suffix for an hour in the 24-hour clock [CHAR_LIMIT=10] -->
+    <string name="time_picker_hour_24h_suffix">%1$d hours</string>
+    <!-- Spoken suffix for an amount of minutes in the clock [CHAR_LIMIT=16] -->
+    <string name="time_picker_minute_suffix">%1$d minutes</string>
 </resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
index 7bae757..d513a40 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/CalendarModel.kt
@@ -24,7 +24,7 @@
  * Creates a [CalendarModel] to be used by the date picker.
  */
 @ExperimentalMaterial3Api
-internal expect fun createCalendarModel(): CalendarModel
+internal expect fun CalendarModel(): CalendarModel
 
 /**
  * Formats a UTC timestamp into a string with a given date format skeleton.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
index 0b0a7e1..129ac14 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateInput.kt
@@ -16,23 +16,18 @@
 
 package androidx.compose.material3
 
-import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.padding
 import androidx.compose.foundation.text.KeyboardOptions
 import androidx.compose.runtime.Composable
-import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
-import androidx.compose.ui.semantics.LiveRegionMode
-import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.error
-import androidx.compose.ui.semantics.liveRegion
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextRange
@@ -44,132 +39,42 @@
 import androidx.compose.ui.text.input.VisualTransformation
 import androidx.compose.ui.unit.dp
 
-// TODO: External preview image.
-// TODO: Introduce a rememberDateInputState once we allow switching between modes.
-/**
- * <a href="https://m3.material.io/components/date-pickers/overview" class="external" target="_blank">Material Design date input</a>.
- *
- * Date pickers let people input a date, and preferably should be embedded into Dialogs.
- * See [DatePickerDialog].
- *
- * A simple DateInput looks like:
- * @sample androidx.compose.material3.samples.DateInputSample
- *
- * @param dateInputState state of the date input. See [rememberDatePickerState].
- * @param modifier the [Modifier] to be applied to this date input
- * @param dateFormatter a [DatePickerFormatter] that provides formatting skeletons for dates display
- * @param dateValidator a lambda that takes a date timestamp and return true if the date is a valid
- * one for input. Invalid dates will be indicate with an error at the UI.
- * @param title the title to be displayed in the date input
- * @param headline the headline to be displayed in the date input
- * @param colors [DatePickerColors] that will be used to resolve the colors used for this date input
- * in different states. See [DatePickerDefaults.colors].
- */
-@ExperimentalMaterial3Api
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-fun DateInput(
-    dateInputState: DatePickerState,
-    modifier: Modifier = Modifier,
-    dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() },
-    dateValidator: (Long) -> Boolean = { true },
-    title: (@Composable () -> Unit)? = { DateInputDefaults.DateInputTitle() },
-    headline: @Composable () -> Unit = {
-        DateInputDefaults.DateInputHeadline(
-            dateInputState,
-            dateFormatter
-        )
-    },
-    colors: DatePickerColors = DatePickerDefaults.colors()
+internal fun DateInputContent(
+    stateData: StateData,
+    dateFormatter: DatePickerFormatter,
+    dateValidator: (Long) -> Boolean,
 ) {
-    Column(modifier = modifier.padding(DatePickerHorizontalPadding)) {
-        // Reusing the same header that is used by the DatePicker.
-        DatePickerHeader(
-            modifier = Modifier,
-            title = title,
-            titleContentColor = colors.titleContentColor,
-            headlineContentColor = colors.headlineContentColor
-        ) {
-            headline()
-        }
-        Divider()
-        DateInputTextField(
-            modifier = Modifier
-                .fillMaxWidth()
-                .padding(InputTextFieldPadding),
-            dateInputState = dateInputState,
-            dateFormatter = dateFormatter,
-            dateValidator = dateValidator
-        )
-    }
-}
-
-/**
- * Contains default values used by the date input.
- */
-@ExperimentalMaterial3Api
-@Stable
-object DateInputDefaults {
-
-    /** A default date input title composable. */
-    @Composable
-    fun DateInputTitle() = Text(getString(string = Strings.DateInputTitle))
-
-    /**
-     * A default date input headline composable lambda that displays a default headline text when
-     * there is no date selection, and an actual date string when there is.
-     *
-     * @param state a [DatePickerState] that will help determine the title's headline
-     * @param dateFormatter a [DatePickerFormatter]
-     */
-    @Composable
-    fun DateInputHeadline(state: DatePickerState, dateFormatter: DatePickerFormatter) {
-        val defaultLocale = defaultLocale()
-        val formattedDate = dateFormatter.formatDate(
-            date = state.selectedDate,
-            calendarModel = state.calendarModel,
-            locale = defaultLocale
-        )
-        val verboseDateDescription = dateFormatter.formatDate(
-            date = state.selectedDate,
-            calendarModel = state.calendarModel,
-            locale = defaultLocale,
-            forContentDescription = true
-        ) ?: getString(Strings.DateInputNoInputHeadlineDescription)
-
-        val headlineText = formattedDate ?: getString(string = Strings.DateInputHeadline)
-        val headlineDescription =
-            getString(Strings.DateInputHeadlineDescription).format(verboseDateDescription)
-
-        Text(
-            text = headlineText,
-            modifier = Modifier.semantics {
-                liveRegion = LiveRegionMode.Polite
-                contentDescription = headlineDescription
-            },
-            maxLines = 1
-        )
-    }
+    DateInputTextField(
+        modifier = Modifier
+            .fillMaxWidth()
+            .padding(InputTextFieldPadding),
+        stateData = stateData,
+        dateFormatter = dateFormatter,
+        dateValidator = dateValidator
+    )
 }
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun DateInputTextField(
     modifier: Modifier,
-    dateInputState: DatePickerState,
+    stateData: StateData,
     dateFormatter: DatePickerFormatter,
     dateValidator: (Long) -> Boolean
 ) {
     // Obtain the DateInputFormat for the default Locale.
     val defaultLocale = defaultLocale()
     val dateInputFormat = remember(defaultLocale) {
-        dateInputState.calendarModel.getDateInputFormat(defaultLocale)
+        stateData.calendarModel.getDateInputFormat(defaultLocale)
     }
     var errorText by rememberSaveable { mutableStateOf("") }
     var text by rememberSaveable(stateSaver = TextFieldValue.Saver) {
         mutableStateOf(
             TextFieldValue(
-                text = with(dateInputState) {
-                    selectedDate?.let {
+                text = with(stateData) {
+                    selectedStartDate?.let {
                         calendarModel.formatWithPattern(
                             it.utcTimeMillis,
                             dateInputFormat.patternWithoutDelimiters,
@@ -203,7 +108,7 @@
             errorText = ""
             return null
         }
-        val parsedDate = dateInputState.calendarModel.parse(
+        val parsedDate = stateData.calendarModel.parse(
             dateInputText,
             dateInputFormat.patternWithoutDelimiters
         )
@@ -212,10 +117,10 @@
             return null
         }
         // Check that the date is within the valid range of years.
-        if (!dateInputState.yearRange.contains(parsedDate.year)) {
+        if (!stateData.yearRange.contains(parsedDate.year)) {
             errorText = errorDateOutOfYearRange.format(
-                dateInputState.yearRange.first,
-                dateInputState.yearRange.last
+                stateData.yearRange.first,
+                stateData.yearRange.last
             )
             return null
         }
@@ -224,7 +129,7 @@
             errorText = errorInvalidNotAllowed.format(
                 dateFormatter.formatDate(
                     date = parsedDate,
-                    calendarModel = dateInputState.calendarModel,
+                    calendarModel = stateData.calendarModel,
                     locale = defaultLocale
                 )
             )
@@ -240,7 +145,7 @@
                 input.text.all { it.isDigit() }
             ) {
                 text = input
-                dateInputState.selectedDate = validate(input)
+                stateData.selectedStartDate = validate(input)
             }
         },
         modifier = modifier
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 fa3d9c1..ce5a473 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
@@ -16,6 +16,7 @@
 
 package androidx.compose.material3
 
+import androidx.compose.animation.Crossfade
 import androidx.compose.animation.animateColorAsState
 import androidx.compose.animation.core.DecayAnimationSpec
 import androidx.compose.animation.core.Spring
@@ -52,11 +53,12 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.material.icons.Icons
 import androidx.compose.material.icons.filled.ArrowDropDown
+import androidx.compose.material.icons.filled.DateRange
+import androidx.compose.material.icons.filled.Edit
 import androidx.compose.material.icons.filled.KeyboardArrowLeft
 import androidx.compose.material.icons.filled.KeyboardArrowRight
 import androidx.compose.material3.tokens.DatePickerModalTokens
 import androidx.compose.material3.tokens.MotionTokens
-import androidx.compose.material3.tokens.TypographyKeyTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
 import androidx.compose.runtime.Immutable
@@ -70,6 +72,7 @@
 import androidx.compose.runtime.rememberCoroutineScope
 import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.listSaver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
@@ -109,49 +112,69 @@
  * Date pickers let people select a date and preferably should be embedded into Dialogs.
  * See [DatePickerDialog].
  *
+ * By default, a date picker lets you pick a date via a calendar UI. However, it also allows
+ * switching into a date input mode for a manual entry of dates using the numbers on a keyboard.
+ *
  * A simple DatePicker looks like:
  * @sample androidx.compose.material3.samples.DatePickerSample
  *
+ * A DatePicker with an initial UI of a date input mode looks like:
+ * @sample androidx.compose.material3.samples.DateInputSample
+ *
  * A DatePicker with validation that blocks certain days from being selected looks like:
  * @sample androidx.compose.material3.samples.DatePickerWithDateValidatorSample
  *
- * @param datePickerState state of the date picker. See [rememberDatePickerState].
+ * @param state state of the date picker. See [rememberDatePickerState].
  * @param modifier the [Modifier] to be applied to this date picker
  * @param dateFormatter a [DatePickerFormatter] that provides formatting skeletons for dates display
  * @param dateValidator a lambda that takes a date timestamp and return true if the date is a valid
  * one for selection. Invalid dates will appear disabled in the UI.
  * @param title the title to be displayed in the date picker
  * @param headline the headline to be displayed in the date picker
+ * @param showModeToggle indicates if this DatePicker should show a mode toggle action that
+ * transforms it into a date input
  * @param colors [DatePickerColors] that will be used to resolve the colors used for this date
  * picker in different states. See [DatePickerDefaults.colors].
  */
 @ExperimentalMaterial3Api
 @Composable
 fun DatePicker(
-    datePickerState: DatePickerState,
+    state: DatePickerState,
     modifier: Modifier = Modifier,
     dateFormatter: DatePickerFormatter = remember { DatePickerFormatter() },
     dateValidator: (Long) -> Boolean = { true },
-    title: (@Composable () -> Unit)? = { DatePickerDefaults.DatePickerTitle() },
+    title: (@Composable () -> Unit)? = { DatePickerDefaults.DatePickerTitle(state) },
     headline: @Composable () -> Unit = {
         DatePickerDefaults.DatePickerHeadline(
-            datePickerState,
+            state,
             dateFormatter
         )
     },
+    showModeToggle: Boolean = true,
     colors: DatePickerColors = DatePickerDefaults.colors()
-) = DatePickerImpl(
-    modifier = modifier,
-    datePickerState = datePickerState,
-    dateFormatter = dateFormatter,
-    dateValidator = dateValidator,
-    title = title,
-    headline = headline,
-    colors = colors
-)
+) {
+    DateEntryContainer(
+        modifier = modifier,
+        title = title,
+        headline = headline,
+        modeToggleButton = if (showModeToggle) {
+            { DateEntryModeToggleButton(stateData = state.stateData) }
+        } else {
+            null
+        },
+        colors = colors
+    ) {
+        SwitchableDateEntryContent(
+            state = state,
+            dateFormatter = dateFormatter,
+            dateValidator = dateValidator,
+            colors = colors
+        )
+    }
+}
 
 /**
- * Creates a [DatePickerState] for a date picker that is remembered across compositions.
+ * Creates a [DatePickerState] for a [DatePicker] that is remembered across compositions.
  *
  * @param initialSelectedDateMillis timestamp in _UTC_ milliseconds from the epoch that represents
  * an initial selection of a date. Provide a `null` to indicate no selection.
@@ -161,20 +184,23 @@
  * selected date. Otherwise, in case `null` is provided, the displayed month would be the
  * current one.
  * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to
+ * @param initialDisplayMode an initial [DisplayMode] that this state will hold
  */
 @Composable
 @ExperimentalMaterial3Api
 fun rememberDatePickerState(
     @Suppress("AutoBoxing") initialSelectedDateMillis: Long? = null,
     @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long? = initialSelectedDateMillis,
-    yearRange: IntRange = DatePickerDefaults.YearRange
+    yearRange: IntRange = DatePickerDefaults.YearRange,
+    initialDisplayMode: DisplayMode = DisplayMode.Picker
 ): DatePickerState = rememberSaveable(
     saver = DatePickerState.Saver()
 ) {
     DatePickerState(
         initialSelectedDateMillis = initialSelectedDateMillis,
         initialDisplayedMonthMillis = initialDisplayedMonthMillis,
-        yearRange = yearRange
+        yearRange = yearRange,
+        initialDisplayMode = initialDisplayMode
     )
 }
 
@@ -183,27 +209,43 @@
  * [rememberDatePickerState].
  *
  * The state's [selectedDateMillis] will provide a timestamp that represents the _start_ of the day.
- *
- * @param initialSelectedDateMillis timestamp in _UTC_ milliseconds from the epoch that represents
- * an initial selection of a date. Provide a `null` to indicate no selection. Note that the state's
- * [selectedDateMillis] will provide a timestamp that represents the _start_ of the day, which may
- * be different than the provided initialSelectedDateMillis.
- * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that represents
- * an initial selection of a month to be displayed to the user. In case `null` is provided, the
- * displayed month would be the current one.
- * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to
- * @see rememberDatePickerState
- * @throws [IllegalArgumentException] if the initial selected date or displayed month represent a
- * year that is out of the year range.
  */
 @ExperimentalMaterial3Api
 @Stable
-class DatePickerState constructor(
-    @Suppress("AutoBoxing") initialSelectedDateMillis: Long?,
-    @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
-    val yearRange: IntRange
-) {
-    internal val calendarModel: CalendarModel = createCalendarModel()
+class DatePickerState private constructor(internal val stateData: StateData) {
+
+    /**
+     * Constructs a DatePickerState.
+     *
+     * @param initialSelectedDateMillis timestamp in _UTC_ milliseconds from the epoch that
+     * represents an initial selection of a date. Provide a `null` to indicate no selection. Note
+     * that the state's
+     * [selectedDateMillis] will provide a timestamp that represents the _start_ of the day, which
+     * may be different than the provided initialSelectedDateMillis.
+     * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that
+     * represents an initial selection of a month to be displayed to the user. In case `null` is
+     * provided, the displayed month would be the current one.
+     * @param yearRange an [IntRange] that holds the year range that the date picker will be limited
+     * to
+     * @param initialDisplayMode an initial [DisplayMode] that this state will hold
+     * @see rememberDatePickerState
+     * @throws [IllegalArgumentException] if the initial selected date or displayed month represent
+     * a year that is out of the year range.
+     */
+    constructor(
+        @Suppress("AutoBoxing") initialSelectedDateMillis: Long?,
+        @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
+        yearRange: IntRange,
+        initialDisplayMode: DisplayMode
+    ) : this(
+        StateData(
+            initialSelectedStartDateMillis = initialSelectedDateMillis,
+            initialSelectedEndDateMillis = null,
+            initialDisplayedMonthMillis = initialDisplayedMonthMillis,
+            yearRange = yearRange,
+            initialDisplayMode = initialDisplayMode,
+        )
+    )
 
     /**
      * A timestamp that represents the _start_ of the day of the selected date in _UTC_ milliseconds
@@ -213,87 +255,22 @@
      */
     @get:Suppress("AutoBoxing")
     val selectedDateMillis by derivedStateOf {
-        selectedDate?.utcTimeMillis
+        stateData.selectedStartDate?.utcTimeMillis
     }
 
     /**
-     * A mutable state of [CalendarDate] that represents the selected date.
+     * A mutable state of [DisplayMode] that represents the current display mode of the UI
+     * (i.e. picker or input).
      */
-    internal var selectedDate by mutableStateOf(
-        if (initialSelectedDateMillis != null) {
-            val date = calendarModel.getCanonicalDate(
-                initialSelectedDateMillis
-            )
-            require(yearRange.contains(date.year)) {
-                "The initial selected date's year (${date.year}) is out of the years range of " +
-                    "$yearRange."
-            }
-            date
-        } else {
-            null
-        }
-    )
-
-    /**
-     * A mutable state for the month that is displayed to the user. In case an initial month was not
-     * provided, the current month will be the one to be displayed.
-     */
-    internal var displayedMonth by mutableStateOf(
-        if (initialDisplayedMonthMillis != null) {
-            val month = calendarModel.getMonth(initialDisplayedMonthMillis)
-            require(yearRange.contains(month.year)) {
-                "The initial display month's year (${month.year}) is out of the years range of " +
-                    "$yearRange."
-            }
-            month
-        } else {
-            currentMonth
-        }
-    )
-
-    /**
-     * The current [CalendarMonth] that represents the present's day month.
-     */
-    internal val currentMonth: CalendarMonth
-        get() = calendarModel.getMonth(calendarModel.today)
-
-    /**
-     * The displayed month index within the total months at the defined years range.
-     *
-     * @see [displayedMonth]
-     * @see [yearRange]
-     */
-    internal val displayedMonthIndex: Int
-        get() = displayedMonth.indexIn(yearRange)
-
-    /**
-     * The total month count for the defined years range.
-     *
-     * @see [yearRange]
-     */
-    internal val totalMonthsInRange: Int
-        get() = (yearRange.last - yearRange.first + 1) * 12
+    var displayMode by stateData.displayMode
 
     companion object {
         /**
          * The default [Saver] implementation for [DatePickerState].
          */
         fun Saver(): Saver<DatePickerState, *> = Saver(
-            save = {
-                listOf(
-                    it.selectedDateMillis,
-                    it.displayedMonth.startUtcTimeMillis,
-                    it.yearRange.first,
-                    it.yearRange.last
-                )
-            },
-            restore = { value ->
-                DatePickerState(
-                    initialSelectedDateMillis = value[0] as Long?,
-                    initialDisplayedMonthMillis = value[1] as Long?,
-                    yearRange = IntRange(value[2] as Int, value[3] as Int)
-                )
-            }
+            save = { with(StateData.Saver()) { save(it.stateData) } },
+            restore = { value -> DatePickerState(with(StateData.Saver()) { restore(value)!! }) }
         )
     }
 }
@@ -381,9 +358,18 @@
             todayDateBorderColor = todayDateBorderColor
         )
 
-    /** A default date picker title composable. */
+    /**
+     * A default date picker title composable.
+     *
+     * @param state a [DatePickerState] that will help determine the title's content
+     */
     @Composable
-    fun DatePickerTitle() = Text(getString(string = Strings.DatePickerTitle))
+    fun DatePickerTitle(state: DatePickerState) {
+        when (state.displayMode) {
+            DisplayMode.Picker -> Text(getString(string = Strings.DatePickerTitle))
+            DisplayMode.Input -> Text(getString(string = Strings.DateInputTitle))
+        }
+    }
 
     /**
      * A default date picker headline composable lambda that displays a default headline text when
@@ -394,31 +380,7 @@
      */
     @Composable
     fun DatePickerHeadline(state: DatePickerState, dateFormatter: DatePickerFormatter) {
-        val defaultLocale = defaultLocale()
-        val formattedDate = dateFormatter.formatDate(
-            date = state.selectedDate,
-            calendarModel = state.calendarModel,
-            locale = defaultLocale
-        )
-        val verboseDateDescription = dateFormatter.formatDate(
-            date = state.selectedDate,
-            calendarModel = state.calendarModel,
-            locale = defaultLocale,
-            forContentDescription = true
-        ) ?: getString(Strings.DatePickerNoSelectionDescription)
-
-        val headlineText = formattedDate ?: getString(string = Strings.DatePickerHeadline)
-        val headlineDescription =
-            getString(Strings.DatePickerHeadlineDescription).format(verboseDateDescription)
-
-        Text(
-            text = headlineText,
-            modifier = Modifier.semantics {
-                liveRegion = LiveRegionMode.Polite
-                contentDescription = headlineDescription
-            },
-            maxLines = 1
-        )
+        DateEntryHeadline(stateData = state.stateData, dateFormatter = dateFormatter)
     }
 
     /**
@@ -706,23 +668,193 @@
     }
 }
 
+/**
+ * Represents the different modes that a date picker can be at.
+ */
+@Immutable
+@JvmInline
 @ExperimentalMaterial3Api
+value class DisplayMode internal constructor(internal val value: Int) {
+
+    companion object {
+        /** Date picker mode */
+        val Picker = DisplayMode(0)
+
+        /** Date text input mode */
+        val Input = DisplayMode(1)
+    }
+
+    override fun toString() = when (this) {
+        Picker -> "Picker"
+        Input -> "Input"
+        else -> "Unknown"
+    }
+}
+
+/**
+ * Holds the state's data for the date picker.
+ *
+ * Note that the internal representation is capable of holding a start and end date. However, the
+ * the [DatePickerState] and the [DateRangePickerState] that use this class will only expose
+ * publicly the relevant functionality for their purpose.
+ *
+ * @param initialSelectedStartDateMillis timestamp in _UTC_ milliseconds from the epoch that
+ * represents an initial selection of a start date. Provide a `null` to indicate no selection.
+ * @param initialSelectedEndDateMillis timestamp in _UTC_ milliseconds from the epoch that
+ * represents an initial selection of an end date. Provide a `null` to indicate no selection. This
+ * value will be ignored in case it's smaller or equals to the initial start value.
+ * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that represents
+ * an initial selection of a month to be displayed to the user. In case `null` is provided, the
+ * displayed month would be the current one.
+ * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to
+ * @param initialDisplayMode an initial [DisplayMode] that this state will hold
+ * @see rememberDatePickerState
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Stable
+internal class StateData constructor(
+    initialSelectedStartDateMillis: Long?,
+    initialSelectedEndDateMillis: Long?,
+    initialDisplayedMonthMillis: Long?,
+    val yearRange: IntRange,
+    initialDisplayMode: DisplayMode,
+) {
+
+    val calendarModel: CalendarModel = CalendarModel()
+
+    /**
+     * A mutable state of [CalendarDate] that represents the start date for a selection.
+     */
+    internal var selectedStartDate by mutableStateOf(
+        if (initialSelectedStartDateMillis != null) {
+            val date = calendarModel.getCanonicalDate(
+                initialSelectedStartDateMillis
+            )
+            require(yearRange.contains(date.year)) {
+                "The initial selected start date's year (${date.year}) is out of the years range " +
+                    "of $yearRange."
+            }
+            date
+        } else {
+            null
+        }
+    )
+
+    /**
+     * A mutable state of [CalendarDate] that represents the end date for a selection.
+     *
+     * Single date selection states that use this [StateData] should always have this as `null`.
+     */
+    internal var selectedEndDate by mutableStateOf(
+        // Set to null in case the provided value is "undefined" or <= than the start date.
+        if (initialSelectedEndDateMillis != null &&
+            initialSelectedStartDateMillis != null &&
+            initialSelectedEndDateMillis > initialSelectedStartDateMillis
+        ) {
+            val date = calendarModel.getCanonicalDate(
+                initialSelectedEndDateMillis
+            )
+            require(yearRange.contains(date.year)) {
+                "The initial selected end date's year (${date.year}) is out of the years range " +
+                    "of $yearRange."
+            }
+            date
+        } else {
+            null
+        }
+    )
+
+    /**
+     * A mutable state for the month that is displayed to the user. In case an initial month was not
+     * provided, the current month will be the one to be displayed.
+     */
+    internal var displayedMonth by mutableStateOf(
+        if (initialDisplayedMonthMillis != null) {
+            val month = calendarModel.getMonth(initialDisplayedMonthMillis)
+            require(yearRange.contains(month.year)) {
+                "The initial display month's year (${month.year}) is out of the years range of " +
+                    "$yearRange."
+            }
+            month
+        } else {
+            currentMonth
+        }
+    )
+
+    /**
+     * The current [CalendarMonth] that represents the present's day month.
+     */
+    internal val currentMonth: CalendarMonth
+        get() = calendarModel.getMonth(calendarModel.today)
+
+    /**
+     * A mutable state of [DisplayMode] that represents the current display mode of the UI
+     * (i.e. picker or input).
+     */
+    internal var displayMode = mutableStateOf(initialDisplayMode)
+
+    /**
+     * The displayed month index within the total months at the defined years range.
+     *
+     * @see [displayedMonth]
+     * @see [yearRange]
+     */
+    internal val displayedMonthIndex: Int
+        get() = displayedMonth.indexIn(yearRange)
+
+    /**
+     * The total month count for the defined years range.
+     *
+     * @see [yearRange]
+     */
+    internal val totalMonthsInRange: Int
+        get() = (yearRange.last - yearRange.first + 1) * 12
+
+    companion object {
+        /**
+         * A [Saver] implementation for [StateData].
+         */
+        fun Saver(): Saver<StateData, Any> = listSaver(
+            save = {
+                listOf(
+                    it.selectedStartDate?.utcTimeMillis,
+                    it.selectedEndDate?.utcTimeMillis,
+                    it.displayedMonth.startUtcTimeMillis,
+                    it.yearRange.first,
+                    it.yearRange.last,
+                    it.displayMode.value.value
+                )
+            },
+            restore = { value ->
+                StateData(
+                    initialSelectedStartDateMillis = value[0] as Long?,
+                    initialSelectedEndDateMillis = value[1] as Long?,
+                    initialDisplayedMonthMillis = value[2] as Long?,
+                    yearRange = IntRange(value[3] as Int, value[4] as Int),
+                    initialDisplayMode = DisplayMode(value[5] as Int)
+                )
+            }
+        )
+    }
+}
+
+/**
+ * A base container for the date picker and the date input. This container composes the top common
+ * area of the UI, and accepts [content] for the actual calendar picker or text field input.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
 @Composable
-private fun DatePickerImpl(
+internal fun DateEntryContainer(
     modifier: Modifier,
-    datePickerState: DatePickerState,
-    dateFormatter: DatePickerFormatter,
-    dateValidator: (Long) -> Boolean,
     title: (@Composable () -> Unit)?,
     headline: @Composable () -> Unit,
-    colors: DatePickerColors
+    modeToggleButton: (@Composable () -> Unit)?,
+    colors: DatePickerColors,
+    content: @Composable () -> Unit
 ) {
-    val monthsListState =
-        rememberLazyListState(initialFirstVisibleItemIndex = datePickerState.displayedMonthIndex)
-    val coroutineScope = rememberCoroutineScope()
     Column(
         modifier = modifier
-            .sizeIn(minWidth = ContainerWidth)
+            .sizeIn(minWidth = DatePickerModalTokens.ContainerWidth)
             .padding(DatePickerHorizontalPadding)
     ) {
         DatePickerHeader(
@@ -732,24 +864,146 @@
             headlineContentColor = colors.headlineContentColor
         ) {
             headline()
+            modeToggleButton?.invoke()
         }
-
         Divider()
+        content()
+    }
+}
 
-        val onDateSelected = { dateInMillis: Long ->
-            datePickerState.selectedDate =
-                datePickerState.calendarModel.getCanonicalDate(dateInMillis)
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun DateEntryModeToggleButton(stateData: StateData) {
+    with(stateData) {
+        if (displayMode.value == DisplayMode.Picker) {
+            IconButton(onClick = {
+                displayMode.value = DisplayMode.Input
+            }) {
+                Icon(
+                    imageVector = Icons.Filled.Edit,
+                    contentDescription = getString(Strings.DatePickerSwitchToInputMode)
+                )
+            }
+        } else {
+            IconButton(
+                onClick = {
+                    // Update the displayed month, if needed, and change the mode to a
+                    // date-picker.
+                    selectedStartDate?.let { displayedMonth = calendarModel.getMonth(it) }
+                    displayMode.value = DisplayMode.Picker
+                }
+            ) {
+                Icon(
+                    imageVector = Icons.Filled.DateRange,
+                    contentDescription = getString(Strings.DatePickerSwitchToCalendarMode)
+                )
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun DateEntryHeadline(stateData: StateData, dateFormatter: DatePickerFormatter) {
+    with(stateData) {
+        val defaultLocale = defaultLocale()
+        val formattedDate = dateFormatter.formatDate(
+            date = selectedStartDate,
+            calendarModel = calendarModel,
+            locale = defaultLocale
+        )
+        val verboseDateDescription = dateFormatter.formatDate(
+            date = selectedStartDate,
+            calendarModel = calendarModel,
+            locale = defaultLocale,
+            forContentDescription = true
+        ) ?: when (displayMode.value) {
+            DisplayMode.Picker -> getString(Strings.DatePickerNoSelectionDescription)
+            DisplayMode.Input -> getString(Strings.DateInputNoInputDescription)
+            else -> ""
         }
 
-        var yearPickerVisible by rememberSaveable { mutableStateOf(false) }
-        val defaultLocale = defaultLocale()
+        val headlineText = formattedDate ?: when (displayMode.value) {
+            DisplayMode.Picker -> getString(Strings.DatePickerHeadline)
+            DisplayMode.Input -> getString(Strings.DateInputHeadline)
+            else -> ""
+        }
+
+        val headlineDescription = when (displayMode.value) {
+            DisplayMode.Picker -> getString(Strings.DatePickerHeadlineDescription)
+            DisplayMode.Input -> getString(Strings.DateInputHeadlineDescription)
+            else -> ""
+        }.format(verboseDateDescription)
+        Text(
+            text = headlineText,
+            modifier = Modifier.semantics {
+                liveRegion = LiveRegionMode.Polite
+                contentDescription = headlineDescription
+            },
+            maxLines = 1
+        )
+    }
+}
+
+/**
+ * Date entry content that displays a [DatePickerContent] or a [DateInputContent] according to the
+ * state's display mode.
+ */
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+internal fun SwitchableDateEntryContent(
+    state: DatePickerState,
+    dateFormatter: DatePickerFormatter,
+    dateValidator: (Long) -> Boolean,
+    colors: DatePickerColors
+) {
+    // TODO(b/266480386): Apply the motion spec for this once we have it. Consider replacing this
+    //  with AnimatedContent when it's out of experimental.
+    Crossfade(targetState = state.displayMode, animationSpec = spring()) { mode ->
+        when (mode) {
+            DisplayMode.Picker -> DatePickerContent(
+                stateData = state.stateData,
+                dateFormatter = dateFormatter,
+                dateValidator = dateValidator,
+                colors = colors
+            )
+
+            DisplayMode.Input -> DateInputContent(
+                stateData = state.stateData,
+                dateFormatter = dateFormatter,
+                dateValidator = dateValidator,
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun DatePickerContent(
+    stateData: StateData,
+    dateFormatter: DatePickerFormatter,
+    dateValidator: (Long) -> Boolean,
+    colors: DatePickerColors
+) {
+    val monthsListState =
+        rememberLazyListState(initialFirstVisibleItemIndex = stateData.displayedMonthIndex)
+    val coroutineScope = rememberCoroutineScope()
+
+    val onDateSelected = { dateInMillis: Long ->
+        stateData.selectedStartDate =
+            stateData.calendarModel.getCanonicalDate(dateInMillis)
+    }
+
+    var yearPickerVisible by rememberSaveable { mutableStateOf(false) }
+    val defaultLocale = defaultLocale()
+    Column {
         MonthsNavigation(
             nextAvailable = monthsListState.canScrollForward,
             previousAvailable = monthsListState.canScrollBackward,
             yearPickerVisible = yearPickerVisible,
             yearPickerText = dateFormatter.formatMonthYear(
-                month = datePickerState.displayedMonth,
-                calendarModel = datePickerState.calendarModel,
+                month = stateData.displayedMonth,
+                calendarModel = stateData.calendarModel,
                 locale = defaultLocale
             ) ?: "-",
             onNextClicked = {
@@ -771,10 +1025,10 @@
 
         Box {
             Column {
-                WeekDays(colors, datePickerState.calendarModel)
+                WeekDays(colors, stateData.calendarModel)
                 HorizontalMonthsList(
                     onDateSelected = onDateSelected,
-                    datePickerState = datePickerState,
+                    stateData = stateData,
                     lazyListState = monthsListState,
                     dateFormatter = dateFormatter,
                     dateValidator = dateValidator,
@@ -808,7 +1062,7 @@
                                 // Scroll to the selected year (maintaining the month of year).
                                 // A LaunchEffect at the MonthsList will take care of rest and will
                                 // update the state's displayedMonth to the month we scrolled to.
-                                with(datePickerState) {
+                                with(stateData) {
                                     monthsListState.scrollToItem(
                                         (year - yearRange.first) * 12 + displayedMonth.month - 1
                                     )
@@ -816,7 +1070,7 @@
                             }
                         },
                         colors = colors,
-                        datePickerState = datePickerState
+                        stateData = stateData
                     )
                     Divider()
                 }
@@ -842,8 +1096,10 @@
     ) {
         if (title != null) {
             CompositionLocalProvider(LocalContentColor provides titleContentColor) {
-                // TODO: Use the value from the tokens, once updated (b/251240936).
-                val textStyle = MaterialTheme.typography.fromToken(HeaderSupportingTextFont)
+                val textStyle =
+                    MaterialTheme.typography.fromToken(
+                        DatePickerModalTokens.HeaderSupportingTextFont
+                    )
                 ProvideTextStyle(textStyle) {
                     Box(contentAlignment = Alignment.BottomStart) {
                         title()
@@ -857,7 +1113,7 @@
             ProvideTextStyle(textStyle) {
                 Row(
                     modifier = Modifier.fillMaxWidth(),
-                    horizontalArrangement = Arrangement.Start,
+                    horizontalArrangement = Arrangement.SpaceBetween,
                     verticalAlignment = Alignment.CenterVertically,
                     content = content
                 )
@@ -873,16 +1129,16 @@
 @Composable
 private fun HorizontalMonthsList(
     onDateSelected: (dateInMillis: Long) -> Unit,
-    datePickerState: DatePickerState,
+    stateData: StateData,
     lazyListState: LazyListState,
     dateFormatter: DatePickerFormatter,
     dateValidator: (Long) -> Boolean,
     colors: DatePickerColors,
 ) {
-    val today = datePickerState.calendarModel.today
-    val firstMonth = remember(datePickerState.yearRange) {
-        datePickerState.calendarModel.getMonth(
-            year = datePickerState.yearRange.first,
+    val today = stateData.calendarModel.today
+    val firstMonth = remember(stateData.yearRange) {
+        stateData.calendarModel.getMonth(
+            year = stateData.yearRange.first,
             month = 1 // January
         )
     }
@@ -898,9 +1154,9 @@
         //  when promoted to stable
         flingBehavior = DatePickerDefaults.rememberSnapFlingBehavior(lazyListState)
     ) {
-        items(datePickerState.totalMonthsInRange) {
+        items(stateData.totalMonthsInRange) {
             val month =
-                datePickerState.calendarModel.plusMonths(
+                stateData.calendarModel.plusMonths(
                     from = firstMonth,
                     addedMonthsCount = it
                 )
@@ -911,7 +1167,7 @@
                     month = month,
                     onDateSelected = onDateSelected,
                     today = today,
-                    selectedDate = datePickerState.selectedDate,
+                    selectedDate = stateData.selectedStartDate,
                     dateFormatter = dateFormatter,
                     dateValidator = dateValidator,
                     colors = colors
@@ -924,7 +1180,7 @@
         snapshotFlow { lazyListState.firstVisibleItemIndex }.collect {
             val yearOffset = lazyListState.firstVisibleItemIndex / 12
             val month = lazyListState.firstVisibleItemIndex % 12 + 1
-            with(datePickerState) {
+            with(stateData) {
                 if (displayedMonth.month != month ||
                     displayedMonth.year != yearRange.first + yearOffset
                 ) {
@@ -955,8 +1211,8 @@
         dayNames.add(weekdays[i])
     }
     CompositionLocalProvider(LocalContentColor provides colors.weekdayContentColor) {
-        // TODO: Use the value from the tokens, once updated (b/251240936).
-        val textStyle = MaterialTheme.typography.fromToken(WeekdaysLabelTextFont)
+        val textStyle =
+            MaterialTheme.typography.fromToken(DatePickerModalTokens.WeekdaysLabelTextFont)
         ProvideTextStyle(value = textStyle) {
             Row(
                 modifier = Modifier
@@ -1002,11 +1258,11 @@
     dateFormatter: DatePickerFormatter,
     colors: DatePickerColors
 ) {
+    val todayDescription = getString(string = Strings.DatePickerTodayDescription)
     ProvideTextStyle(
-        // TODO: Use the value from the tokens, once updated (b/251240936).
-        MaterialTheme.typography.fromToken(DateLabelTextFont)
+        MaterialTheme.typography.fromToken(DatePickerModalTokens.DateLabelTextFont)
     ) {
-        var cellsCount = 0
+        var cellIndex = 0
         Column(
             modifier = Modifier
                 .requiredHeight(RecommendedSizeForAccessibility * MaxCalendarRows),
@@ -1019,31 +1275,36 @@
                     verticalAlignment = Alignment.CenterVertically
                 ) {
                     repeat(DaysInWeek) {
-                        if (cellsCount < month.daysFromStartOfWeekToFirstOfMonth ||
-                            cellsCount >=
+                        if (cellIndex < month.daysFromStartOfWeekToFirstOfMonth ||
+                            cellIndex >=
                             (month.daysFromStartOfWeekToFirstOfMonth + month.numberOfDays)
                         ) {
                             // Empty cell
-                            Box(
+                            Spacer(
                                 modifier = Modifier.requiredSize(
                                     width = RecommendedSizeForAccessibility,
                                     height = RecommendedSizeForAccessibility
                                 )
                             )
                         } else {
-                            // TODO a11y should announce the day and whether it's selected or not.
-                            val dayNumber = cellsCount - month.daysFromStartOfWeekToFirstOfMonth
+                            val dayNumber = cellIndex - month.daysFromStartOfWeekToFirstOfMonth
                             val dateInMillis = month.startUtcTimeMillis +
                                 (dayNumber * MillisecondsIn24Hours)
+                            val isToday = dateInMillis == today.utcTimeMillis
                             Day(
-                                modifier = Modifier.semantics { role = Role.Button },
+                                modifier = Modifier.semantics {
+                                    role = Role.Button
+                                    if (isToday) {
+                                        contentDescription = todayDescription
+                                    }
+                                },
                                 selected = dateInMillis == selectedDate?.utcTimeMillis,
                                 onClick = { onDateSelected(dateInMillis) },
                                 animateChecked = true,
                                 enabled = remember(dateInMillis) {
                                     dateValidator.invoke(dateInMillis)
                                 },
-                                today = dateInMillis == today.utcTimeMillis,
+                                today = isToday,
                                 colors = colors
                             ) {
                                 val defaultLocale = defaultLocale()
@@ -1061,7 +1322,7 @@
                                 )
                             }
                         }
-                        cellsCount++
+                        cellIndex++
                     }
                 }
             }
@@ -1123,19 +1384,19 @@
     modifier: Modifier,
     onYearSelected: (year: Int) -> Unit,
     colors: DatePickerColors,
-    datePickerState: DatePickerState
+    stateData: StateData
 ) {
     ProvideTextStyle(
         value = MaterialTheme.typography.fromToken(DatePickerModalTokens.SelectionYearLabelTextFont)
     ) {
-        val currentYear = datePickerState.currentMonth.year
-        val displayedYear = datePickerState.displayedMonth.year
+        val currentYear = stateData.currentMonth.year
+        val displayedYear = stateData.displayedMonth.year
         val lazyGridState =
             rememberLazyGridState(
                 // Set the initial index to a few years before the current year to allow quicker
                 // selection of previous years.
                 initialFirstVisibleItemIndex = max(
-                    0, displayedYear - datePickerState.yearRange.first - YearsInRow
+                    0, displayedYear - stateData.yearRange.first - YearsInRow
                 )
             )
         // Match the years container color to any elevated surface color that is composed under it.
@@ -1157,8 +1418,8 @@
             horizontalArrangement = Arrangement.SpaceEvenly,
             verticalArrangement = Arrangement.spacedBy(YearsVerticalPadding)
         ) {
-            items(datePickerState.yearRange.count()) {
-                val selectedYear = it + datePickerState.yearRange.first
+            items(stateData.yearRange.count()) {
+                val selectedYear = it + stateData.yearRange.first
                 Year(
                     modifier = Modifier
                         .requiredSize(
@@ -1336,10 +1597,6 @@
     return formatter.format(this)
 }
 
-// TODO: Remove after b/247694457 for updating the tokens is resolved.
-internal val ContainerWidth = 360.dp
-internal val ContainerHeight = 568.dp
-
 internal val MonthYearHeight = 56.dp
 internal val DatePickerHorizontalPadding = PaddingValues(horizontal = 12.dp)
 internal val HeaderPadding = PaddingValues(
@@ -1353,9 +1610,4 @@
 private const val MaxCalendarRows = 6
 private const val YearsInRow: Int = 3
 
-// TODO: Remove after b/251240936 for updating the typography is resolved.
-private val WeekdaysLabelTextFont = TypographyKeyTokens.BodyLarge
-private val DateLabelTextFont = TypographyKeyTokens.BodyLarge
-private val HeaderSupportingTextFont = TypographyKeyTokens.LabelLarge
-
 private val RecommendedSizeForAccessibility = 48.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
new file mode 100644
index 0000000..b6145ab
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/DateRangePicker.kt
@@ -0,0 +1,152 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+
+/**
+ * Creates a [DateRangePickerState] for a [DateRangePicker] that is remembered across compositions.
+ *
+ * @param initialSelectedStartDateMillis timestamp in _UTC_ milliseconds from the epoch that
+ * represents an initial selection of a start date. Provide a `null` to indicate no selection.
+ * @param initialSelectedEndDateMillis timestamp in _UTC_ milliseconds from the epoch that
+ * represents an initial selection of an end date. Provide a `null` to indicate no selection.
+ * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that represents
+ * an initial selection of a month to be displayed to the user. By default, in case an
+ * `initialSelectedStartDateMillis` is provided, the initial displayed month would be the month of
+ * the selected date. Otherwise, in case `null` is provided, the displayed month would be the
+ * current one.
+ * @param yearRange an [IntRange] that holds the year range that the date picker will be limited to
+ * @param initialDisplayMode an initial [DisplayMode] that this state will hold
+ */
+@Composable
+@ExperimentalMaterial3Api
+internal fun rememberDateRangePickerState(
+    @Suppress("AutoBoxing") initialSelectedStartDateMillis: Long? = null,
+    @Suppress("AutoBoxing") initialSelectedEndDateMillis: Long? = null,
+    @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long? =
+        initialSelectedStartDateMillis,
+    yearRange: IntRange = DatePickerDefaults.YearRange,
+    initialDisplayMode: DisplayMode = DisplayMode.Picker
+): DateRangePickerState = rememberSaveable(
+    saver = DateRangePickerState.Saver()
+) {
+    DateRangePickerState(
+        initialSelectedStartDateMillis = initialSelectedStartDateMillis,
+        initialSelectedEndDateMillis = initialSelectedEndDateMillis,
+        initialDisplayedMonthMillis = initialDisplayedMonthMillis,
+        yearRange = yearRange,
+        initialDisplayMode = initialDisplayMode
+    )
+}
+
+/**
+ * A state object that can be hoisted to observe the date picker state. See
+ * [rememberDateRangePickerState].
+ *
+ * The state's [selectedStartDateMillis] and [selectedEndDateMillis] will provide timestamps for the
+ * _beginning_ of the selected days (i.e. midnight in _UTC_ milliseconds from the epoch).
+ */
+@ExperimentalMaterial3Api
+@Stable
+internal class DateRangePickerState private constructor(internal val stateData: StateData) {
+
+    /**
+     * Constructs a DateRangePickerState.
+     *
+     * @param initialSelectedStartDateMillis timestamp in _UTC_ milliseconds from the epoch that
+     * represents an initial selection of a start date. Provide a `null` to indicate no selection.
+     * @param initialSelectedEndDateMillis timestamp in _UTC_ milliseconds from the epoch that
+     * represents an initial selection of an end date. Provide a `null` to indicate no selection.
+     * @param initialDisplayedMonthMillis timestamp in _UTC_ milliseconds from the epoch that
+     * represents an initial selection of a month to be displayed to the user. By default, in case
+     * an `initialSelectedStartDateMillis` is provided, the initial displayed month would be the
+     * month of the selected date. Otherwise, in case `null` is provided, the displayed month would
+     * be the current one.
+     * @param yearRange an [IntRange] that holds the year range that the date picker will be limited
+     * to
+     * @param initialDisplayMode an initial [DisplayMode] that this state will hold
+     * @see rememberDatePickerState
+     * @throws [IllegalArgumentException] if the initial selected date or displayed month represent
+     * a year that is out of the year range.
+     */
+    constructor(
+        @Suppress("AutoBoxing") initialSelectedStartDateMillis: Long?,
+        @Suppress("AutoBoxing") initialSelectedEndDateMillis: Long?,
+        @Suppress("AutoBoxing") initialDisplayedMonthMillis: Long?,
+        yearRange: IntRange,
+        initialDisplayMode: DisplayMode
+    ) : this(
+        StateData(
+            initialSelectedStartDateMillis = initialSelectedStartDateMillis,
+            initialSelectedEndDateMillis = initialSelectedEndDateMillis,
+            initialDisplayedMonthMillis = initialDisplayedMonthMillis,
+            yearRange = yearRange,
+            initialDisplayMode = initialDisplayMode,
+        )
+    )
+
+    /**
+     * A timestamp that represents the selected range start date.
+     *
+     * The timestamp would hold a value for the _start_ of the day in _UTC_ milliseconds from the
+     * epoch.
+     *
+     * In case a start date was not selected or provided, the state will hold a `null` value.
+     */
+    @get:Suppress("AutoBoxing")
+    val selectedStartDateMillis by derivedStateOf {
+        stateData.selectedStartDate?.utcTimeMillis
+    }
+
+    /**
+     * A timestamp that represents the selected range end date.
+     *
+     * The timestamp would hold a value for the _start_ of the day in _UTC_ milliseconds from the
+     * epoch.
+     *
+     * In case an end date was not selected or provided, the state will hold a `null` value.
+     */
+    @get:Suppress("AutoBoxing")
+    val selectedEndDateMillis by derivedStateOf {
+        stateData.selectedEndDate?.utcTimeMillis
+    }
+
+    /**
+     * A mutable state of [DisplayMode] that represents the current display mode of the UI
+     * (i.e. picker or input).
+     */
+    var displayMode by stateData.displayMode
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [DateRangePickerState].
+         */
+        fun Saver(): Saver<DateRangePickerState, *> = Saver(
+            save = { with(StateData.Saver()) { save(it.stateData) } },
+            restore = { value ->
+                DateRangePickerState(with(StateData.Saver()) { restore(value)!! })
+            }
+        )
+    }
+}
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
index 6f5cb42c..6179ee1 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ListItem.kt
@@ -68,7 +68,6 @@
  * @param shadowElevation the shadow elevation of this list item
  */
 @Composable
-@ExperimentalMaterial3Api
 fun ListItem(
     headlineText: @Composable () -> Unit,
     modifier: Modifier = Modifier,
@@ -276,7 +275,6 @@
  * @param content the content to be displayed in the middle section of this list item
  */
 @Composable
-@ExperimentalMaterial3Api
 private fun ListItem(
     modifier: Modifier = Modifier,
     shape: Shape = ListItemDefaults.shape,
@@ -366,7 +364,6 @@
 /**
  * Contains the default values used by list items.
  */
-@ExperimentalMaterial3Api
 object ListItemDefaults {
     /** The default elevation of a list item */
     val Elevation: Dp = ListTokens.ListItemContainerElevation
@@ -430,7 +427,6 @@
  *
  * - See [ListItemDefaults.colors] for the default colors used in a [ListItem].
  */
-@ExperimentalMaterial3Api
 @Immutable
 class ListItemColors internal constructor(
     private val containerColor: Color,
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.kt
new file mode 100644
index 0000000..eadc6ff
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/ModalBottomSheet.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.compose.material3
+
+import androidx.compose.animation.core.TweenSpec
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.gestures.draggable
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.BoxWithConstraints
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.WindowInsets
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.offset
+import androidx.compose.foundation.layout.widthIn
+import androidx.compose.material3.SheetValue.Expanded
+import androidx.compose.material3.SheetValue.Collapsed
+import androidx.compose.material3.SheetValue.Hidden
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.graphics.isSpecified
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.semantics.collapse
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.dismiss
+import androidx.compose.ui.semantics.expand
+import androidx.compose.ui.semantics.onClick
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.dp
+import androidx.compose.ui.window.Popup
+import kotlin.math.max
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * <a href="https://m3.material.io/components/bottom-sheets/overview" class="external" target="_blank">Material Design modal bottom sheet</a>.
+ *
+ * Modal bottom sheets are used as an alternative to inline menus or simple dialogs on mobile,
+ * especially when offering a long list of action items, or when items require longer descriptions
+ * and icons. Like dialogs, modal bottom sheets appear in front of app content, disabling all other
+ * app functionality when they appear, and remaining on screen until confirmed, dismissed, or a
+ * required action has been taken.
+ *
+ * ![Bottom sheet image](https://developer.android.com/images/reference/androidx/compose/material3/bottom_sheet.png)
+ *
+ * A simple example of a modal bottom sheet looks like this:
+ *
+ * @sample androidx.compose.material3.samples.ModalBottomSheetSample
+ *
+ * @param onDismissRequest Executes when the user clicks outside of the bottom sheet, after sheet
+ * animates to [Hidden].
+ * @param modifier Optional [Modifier] for the bottom sheet.
+ * @param sheetState The state of the bottom sheet.
+ * @param shape The shape of the bottom sheet. By default, the shape changes from
+ * [BottomSheetDefaults.MinimizedShape] to [BottomSheetDefaults.ExpandedShape] when the
+ * sheet targets [Hidden] and non-Hidden states respectively.
+ * @param containerColor The color used for the background of this bottom sheet
+ * @param contentColor The preferred color for content inside this bottom sheet. Defaults to either
+ * the matching content color for [containerColor], or to the current [LocalContentColor] if
+ * [containerColor] is not a color from the theme.
+ * @param tonalElevation The tonal elevation of this bottom sheet.
+ * @param scrimColor Color of the scrim that obscures content when the bottom sheet is open.
+ * @param dragHandle Optional visual marker to swipe the bottom sheet.
+ * @param content The content to be displayed inside the bottom sheet.
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun ModalBottomSheet(
+    onDismissRequest: () -> Unit,
+    modifier: Modifier = Modifier,
+    sheetState: SheetState = rememberSheetState(),
+    shape: Shape = if (sheetState.targetValue == Hidden) BottomSheetDefaults.MinimizedShape
+        else BottomSheetDefaults.ExpandedShape,
+    containerColor: Color = BottomSheetDefaults.ContainerColor,
+    contentColor: Color = contentColorFor(containerColor),
+    tonalElevation: Dp = BottomSheetDefaults.Elevation,
+    scrimColor: Color = BottomSheetDefaults.ScrimColor,
+    dragHandle: @Composable (() -> Unit)? = { BottomSheetDefaults.DragHandle() },
+    content: @Composable ColumnScope.() -> Unit,
+) {
+    val scope = rememberCoroutineScope()
+
+    // Callback that is invoked when the anchors have changed.
+    val anchorChangeHandler = remember(sheetState, scope) {
+        ModalBottomSheetAnchorChangeHandler(
+            state = sheetState,
+            animateTo = { target, velocity ->
+                scope.launch { sheetState.swipeableState.animateTo(target, velocity = velocity) }
+            },
+            snapTo = { target -> scope.launch { sheetState.swipeableState.snapTo(target) } }
+        )
+    }
+    val systemBarHeight = WindowInsets.systemBarsForVisualComponents.getBottom(LocalDensity.current)
+
+    Popup {
+        BoxWithConstraints(Modifier.fillMaxSize()) {
+            val fullHeight = constraints.maxHeight
+            Scrim(
+                color = scrimColor,
+                onDismissRequest = {
+                    scope.launch { sheetState.hide() }.invokeOnCompletion {
+                        if (!sheetState.isVisible) { onDismissRequest() }
+                    }
+                },
+                visible = sheetState.targetValue != Hidden
+            )
+            Surface(
+                modifier = modifier
+                    .widthIn(max = BottomSheetMaxWidth)
+                    .fillMaxWidth()
+                    .align(Alignment.TopCenter)
+                    .offset {
+                        IntOffset(
+                            0,
+                            sheetState
+                                .requireOffset()
+                                .toInt()
+                        )
+                    }
+                    .nestedScroll(
+                        remember(sheetState.swipeableState, Orientation.Vertical) {
+                            ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
+                                state = sheetState.swipeableState,
+                                orientation = Orientation.Vertical
+                            )
+                        }
+                    )
+                    .modalBottomSheetSwipeable(
+                        sheetState = sheetState,
+                        scope = scope,
+                        onDismissRequest = onDismissRequest,
+                        anchorChangeHandler = anchorChangeHandler,
+                        screenHeight = fullHeight.toFloat(),
+                        bottomPadding = systemBarHeight.toFloat(),
+                    ),
+                shape = shape,
+                color = containerColor,
+                contentColor = contentColor,
+                tonalElevation = tonalElevation,
+            ) {
+                Column(Modifier.fillMaxWidth()) {
+                    if (dragHandle != null) {
+                        Box(Modifier.align(Alignment.CenterHorizontally)) {
+                            dragHandle()
+                        }
+                    }
+                    content()
+                }
+            }
+        }
+    }
+    if (sheetState.hasExpandedState) {
+        LaunchedEffect(sheetState) {
+            sheetState.show()
+        }
+    }
+}
+
+@Composable
+private fun Scrim(
+    color: Color,
+    onDismissRequest: () -> Unit,
+    visible: Boolean
+) {
+    val sheetDescription = getString(Strings.CloseSheet)
+    if (color.isSpecified) {
+        val alpha by animateFloatAsState(
+            targetValue = if (visible) 1f else 0f,
+            animationSpec = TweenSpec()
+        )
+        val dismissSheet = if (visible) {
+            Modifier
+                .pointerInput(onDismissRequest) {
+                    detectTapGestures {
+                        onDismissRequest()
+                    }
+                }
+                .semantics(mergeDescendants = true) {
+                    contentDescription = sheetDescription
+                    onClick { onDismissRequest(); true }
+                }
+        } else {
+            Modifier
+        }
+        Canvas(
+            Modifier
+                .fillMaxSize()
+                .then(dismissSheet)
+        ) {
+            drawRect(color = color, alpha = alpha)
+        }
+    }
+}
+
+@ExperimentalMaterial3Api
+private fun Modifier.modalBottomSheetSwipeable(
+    sheetState: SheetState,
+    scope: CoroutineScope,
+    onDismissRequest: () -> Unit,
+    anchorChangeHandler: AnchorChangeHandler<SheetValue>,
+    screenHeight: Float,
+    bottomPadding: Float,
+) = draggable(
+    state = sheetState.swipeableState.draggableState,
+    orientation = Orientation.Vertical,
+    enabled = sheetState.isVisible,
+    startDragImmediately = sheetState.swipeableState.isAnimationRunning,
+    onDragStopped = { velocity ->
+        try {
+            sheetState.settle(velocity)
+        } finally {
+            if (!sheetState.isVisible) onDismissRequest()
+        }
+    }
+).swipeAnchors(
+    state = sheetState.swipeableState,
+    anchorChangeHandler = anchorChangeHandler,
+    possibleValues = setOf(Hidden, Collapsed, Expanded),
+) { value, sheetSize ->
+    when (value) {
+        Hidden -> screenHeight + bottomPadding
+        Collapsed -> when {
+            sheetSize.height < screenHeight / 2 -> null
+            sheetState.skipCollapsed -> null
+            else -> sheetSize.height / 2f
+        }
+        Expanded -> if (sheetSize.height != 0) {
+            max(0f, screenHeight - sheetSize.height)
+        } else null
+    }
+}.semantics {
+    if (sheetState.isVisible) {
+        dismiss {
+            if (sheetState.swipeableState.confirmValueChange(Hidden)) {
+                scope.launch { sheetState.hide() }.invokeOnCompletion {
+                    if (!sheetState.isVisible) { onDismissRequest() }
+                }
+            }
+            true
+        }
+        if (sheetState.swipeableState.currentValue == Collapsed) {
+            expand {
+                if (sheetState.swipeableState.confirmValueChange(Expanded)) {
+                    scope.launch { sheetState.expand() }
+                }
+                true
+            }
+        } else if (sheetState.hasCollapsedState) {
+            collapse {
+                if (sheetState.swipeableState.confirmValueChange(Collapsed)) {
+                    scope.launch { sheetState.collapse() }
+                }
+                true
+            }
+        }
+    }
+}
+
+@ExperimentalMaterial3Api
+private fun ModalBottomSheetAnchorChangeHandler(
+    state: SheetState,
+    animateTo: (target: SheetValue, velocity: Float) -> Unit,
+    snapTo: (target: SheetValue) -> Unit,
+) = AnchorChangeHandler<SheetValue> { previousTarget, previousAnchors, newAnchors ->
+    val previousTargetOffset = previousAnchors[previousTarget]
+    val newTarget = when (previousTarget) {
+        Hidden -> Hidden
+        Collapsed, Expanded -> {
+            val hasCollapsedState = newAnchors.containsKey(Collapsed)
+            val newTarget = if (hasCollapsedState) Collapsed
+            else if (newAnchors.containsKey(Expanded)) Expanded else Hidden
+            newTarget
+        }
+    }
+    val newTargetOffset = newAnchors.getValue(newTarget)
+    if (newTargetOffset != previousTargetOffset) {
+        if (state.swipeableState.isAnimationRunning || previousAnchors.isEmpty()) {
+            // Re-target the animation to the new offset if it changed
+            animateTo(newTarget, state.swipeableState.lastVelocity)
+        } else {
+            // Snap to the new offset value of the target if no animation was running
+            snapTo(newTarget)
+        }
+    }
+}
+
+private val BottomSheetMaxWidth = 640.dp
\ No newline at end of file
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 1655707..5ad72df 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
@@ -144,6 +144,11 @@
     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. */
+internal fun CornerBasedShape.bottom(): CornerBasedShape {
+    return copy(topStart = CornerSize(0.0.dp), topEnd = CornerSize(0.0.dp))
+}
+
 /** Helper function for component shape tokens. Used to grab the end values of a shape parameter. */
 internal fun CornerBasedShape.end(): CornerBasedShape {
     return copy(topStart = CornerSize(0.0.dp), bottomStart = CornerSize(0.0.dp))
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
new file mode 100644
index 0000000..8149339
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SheetDefaults.kt
@@ -0,0 +1,326 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.material3
+
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.size
+import androidx.compose.material3.SheetValue.Collapsed
+import androidx.compose.material3.SheetValue.Hidden
+import androidx.compose.material3.SheetValue.Expanded
+import androidx.compose.material3.tokens.ScrimTokens
+import androidx.compose.material3.tokens.SheetBottomTokens
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.Velocity
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.CancellationException
+
+/**
+ * Create and [remember] a [SheetState].
+ *
+ * @param skipHalfExpanded Whether the half expanded state, if the sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun rememberSheetState(
+    skipHalfExpanded: Boolean = false,
+    confirmValueChange: (SheetValue) -> Boolean = { true }
+): SheetState {
+    return rememberSaveable(
+        skipHalfExpanded, confirmValueChange,
+        saver = SheetState.Saver(
+            skipHalfExpanded = skipHalfExpanded,
+            confirmValueChange = confirmValueChange
+        )
+    ) {
+        SheetState(skipHalfExpanded, confirmValueChange = confirmValueChange)
+    }
+}
+
+/**
+ * State of a sheet composable, such as [ModalBottomSheet]
+ *
+ * Contains states relating to it's swipe position as well as animations between state values.
+ *
+ * @param skipCollapsed Whether the collapsed state, if the bottom sheet is tall enough, should
+ * be skipped. If true, the sheet will always expand to the [Expanded] state and move to the
+ * [Hidden] state when hiding the sheet, either programmatically or by user interaction.
+ * @param initialValue The initial value of the state.
+ * @param confirmValueChange Optional callback invoked to confirm or veto a pending state change.
+ */
+@Stable
+@ExperimentalMaterial3Api
+class SheetState(
+    internal val skipCollapsed: Boolean,
+    initialValue: SheetValue = Hidden,
+    confirmValueChange: (SheetValue) -> Boolean = { true }
+) {
+    /**
+     * The current value of the state.
+     *
+     * If no swipe or animation is in progress, this corresponds to the state the bottom sheet is
+     * currently in. If a swipe or an animation is in progress, this corresponds the state the sheet
+     * was in before the swipe or animation started.
+     */
+    val currentValue: SheetValue get() = swipeableState.currentValue
+
+    /**
+     * The target value of the bottom sheet state.
+     *
+     * If a swipe is in progress, this is the value that the sheet would animate to if the
+     * swipe finishes. If an animation is running, this is the target value of that animation.
+     * Finally, if no swipe or animation is in progress, this is the same as the [currentValue].
+     */
+    val targetValue: SheetValue get() = swipeableState.targetValue
+
+    /**
+     * Whether the bottom sheet is visible.
+     */
+    val isVisible: Boolean
+        get() = swipeableState.currentValue != Hidden
+
+    /**
+     * Require the current offset (in pixels) of the bottom sheet.
+     *
+     * @throws IllegalStateException If the offset has not been initialized yet
+     */
+    fun requireOffset(): Float = swipeableState.requireOffset()
+
+    /**
+     * Show the bottom sheet with animation and suspend until it's shown. If the sheet is taller
+     * than 50% of the parent's height, the bottom sheet will be half expanded. Otherwise it will be
+     * fully expanded.
+     *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun show() {
+        val targetValue = when {
+            hasCollapsedState -> Collapsed
+            else -> Expanded
+        }
+        swipeableState.animateTo(targetValue)
+    }
+
+    /**
+     * Hide the bottom sheet with animation and suspend until it is fully hidden or animation has
+     * been cancelled.
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun hide() {
+        swipeableState.animateTo(Hidden)
+    }
+
+    /**
+     * Hide the bottom sheet with animation and suspend until it is collapsed or animation has
+     * been cancelled.
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    suspend fun collapse() {
+        swipeableState.animateTo(Collapsed)
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [SheetState].
+         */
+        fun Saver(
+            skipHalfExpanded: Boolean,
+            confirmValueChange: (SheetValue) -> Boolean
+        ) = Saver<SheetState, SheetValue>(
+            save = { it.currentValue },
+            restore = { SheetState(skipHalfExpanded, it, confirmValueChange) }
+        )
+    }
+
+    internal var swipeableState = SwipeableV2State(
+        initialValue = initialValue,
+        animationSpec = SwipeableV2Defaults.AnimationSpec,
+        confirmValueChange = confirmValueChange,
+    )
+
+    internal val hasCollapsedState: Boolean
+        get() = swipeableState.hasAnchorForValue(Collapsed)
+
+    internal val hasExpandedState: Boolean
+        get() = swipeableState.hasAnchorForValue(Expanded)
+
+    internal val offset: Float? get() = swipeableState.offset
+
+    /**
+     * Fully expand the bottom sheet with animation and suspend until it if fully expanded or
+     * animation has been cancelled.
+     * *
+     * @throws [CancellationException] if the animation is interrupted
+     */
+    internal suspend fun expand() {
+        swipeableState.animateTo(Expanded)
+    }
+
+    /**
+     * Find the closest anchor taking into account the velocity and settle at it with an animation.
+     */
+    internal suspend fun settle(velocity: Float) {
+        swipeableState.settle(velocity)
+    }
+}
+
+/**
+ * Possible values of [SheetState].
+ */
+@ExperimentalMaterial3Api
+enum class SheetValue {
+    /**
+     * The sheet is not visible.
+     */
+    Hidden,
+
+    /**
+     * The sheet is visible at full height.
+     */
+    Expanded,
+
+    /**
+     * The sheet is partially visible.
+     */
+    Collapsed,
+}
+
+/**
+ * Contains the default values used by [ModalBottomSheet].
+ */
+@Stable
+@ExperimentalMaterial3Api
+object BottomSheetDefaults {
+    /** The default shape for a [ModalBottomSheet] in a [Hidden] state. */
+    val MinimizedShape: Shape
+        @Composable get() =
+        SheetBottomTokens.DockedMinimizedContainerShape.toShape()
+
+    /** The default shape for a [ModalBottomSheet] in [Collapsed] and [Expanded] states. */
+    val ExpandedShape: Shape
+        @Composable get() =
+        SheetBottomTokens.DockedContainerShape.toShape()
+
+    /** The default container color for a bottom sheet. */
+    val ContainerColor: Color
+        @Composable get() =
+        SheetBottomTokens.DockedContainerColor.toColor()
+
+    /** The default elevation for a bottom sheet. */
+    val Elevation = SheetBottomTokens.DockedModalContainerElevation
+
+    /** The default color of the scrim overlay for background content. */
+    val ScrimColor: Color
+        @Composable get() =
+        ScrimTokens.ContainerColor.toColor().copy(ScrimTokens.ContainerOpacity)
+
+    @Composable
+    fun DragHandle(
+        modifier: Modifier = Modifier,
+        width: Dp = SheetBottomTokens.DockedDragHandleWidth,
+        height: Dp = SheetBottomTokens.DockedDragHandleHeight,
+        shape: Shape = MaterialTheme.shapes.extraLarge,
+        color: Color = SheetBottomTokens.DockedDragHandleColor.toColor()
+            .copy(SheetBottomTokens.DockedDragHandleOpacity),
+    ) {
+        Surface(
+            modifier = modifier.padding(vertical = DragHandleVerticalPadding),
+            color = color,
+            shape = shape
+        ) {
+            Box(
+                Modifier
+                    .size(
+                        width = width,
+                        height = height
+                    )
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+internal fun ConsumeSwipeWithinBottomSheetBoundsNestedScrollConnection(
+    state: SwipeableV2State<*>,
+    orientation: Orientation
+): NestedScrollConnection = object : NestedScrollConnection {
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        val delta = available.toFloat()
+        return if (delta < 0 && source == NestedScrollSource.Drag) {
+            state.dispatchRawDelta(delta).toOffset()
+        } else {
+            Offset.Zero
+        }
+    }
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset {
+        return if (source == NestedScrollSource.Drag) {
+            state.dispatchRawDelta(available.toFloat()).toOffset()
+        } else {
+            Offset.Zero
+        }
+    }
+
+    override suspend fun onPreFling(available: Velocity): Velocity {
+        val toFling = available.toFloat()
+        val currentOffset = state.requireOffset()
+        return if (toFling < 0 && currentOffset > state.minBound) {
+            state.settle(velocity = toFling)
+            // since we go to the anchor with tween settling, consume all for the best UX
+            available
+        } else {
+            Velocity.Zero
+        }
+    }
+
+    override suspend fun onPostFling(consumed: Velocity, available: Velocity): Velocity {
+        state.settle(velocity = available.toFloat())
+        return available
+    }
+
+    private fun Float.toOffset(): Offset = Offset(
+        x = if (orientation == Orientation.Horizontal) this else 0f,
+        y = if (orientation == Orientation.Vertical) this else 0f
+    )
+
+    @JvmName("velocityToFloat")
+    private fun Velocity.toFloat() = if (orientation == Orientation.Horizontal) x else y
+
+    @JvmName("offsetToFloat")
+    private fun Offset.toFloat(): Float = if (orientation == Orientation.Horizontal) x else y
+}
+
+private val DragHandleVerticalPadding = 22.dp
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
index 477824a..ce64cb0 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Strings.kt
@@ -21,8 +21,10 @@
 import androidx.compose.runtime.ReadOnlyComposable
 
 @Immutable
-@kotlin.jvm.JvmInline
-internal value class Strings private constructor(@Suppress("unused") private val value: Int) {
+@JvmInline
+internal value class Strings private constructor(
+    @Suppress("unused") private val value: Int
+) {
     companion object {
         val NavigationMenu = Strings(0)
         val CloseDrawer = Strings(1)
@@ -47,15 +49,26 @@
         val DatePickerNavigateToYearDescription = Strings(20)
         val DatePickerHeadlineDescription = Strings(21)
         val DatePickerNoSelectionDescription = Strings(22)
-        val DateInputTitle = Strings(23)
-        val DateInputHeadline = Strings(24)
-        val DateInputLabel = Strings(25)
-        val DateInputHeadlineDescription = Strings(26)
-        val DateInputNoInputHeadlineDescription = Strings(27)
-        val DateInputInvalidNotAllowed = Strings(28)
-        val DateInputInvalidForPattern = Strings(29)
-        val DateInputInvalidYearRange = Strings(30)
-        val TooltipLongPressLabel = Strings(31)
+        val DatePickerTodayDescription = Strings(23)
+        val DateInputTitle = Strings(24)
+        val DateInputHeadline = Strings(25)
+        val DateInputLabel = Strings(26)
+        val DateInputHeadlineDescription = Strings(27)
+        val DateInputNoInputDescription = Strings(28)
+        val DateInputInvalidNotAllowed = Strings(29)
+        val DateInputInvalidForPattern = Strings(30)
+        val DateInputInvalidYearRange = Strings(31)
+        val DatePickerSwitchToCalendarMode = Strings(32)
+        val DatePickerSwitchToInputMode = Strings(33)
+        val TooltipLongPressLabel = Strings(34)
+        val TimePickerAM = Strings(35)
+        val TimePickerPM = Strings(36)
+        val TimePickerPeriodToggle = Strings(37)
+        val TimePickerHourSelection = Strings(38)
+        val TimePickerMinuteSelection = Strings(39)
+        val TimePickerHourSuffix = Strings(40)
+        val TimePicker24HourSuffix = Strings(41)
+        val TimePickerMinuteSuffix = Strings(42)
     }
 }
 
@@ -65,4 +78,4 @@
 
 @Composable
 @ReadOnlyComposable
-internal expect fun getString(string: Strings, vararg formatArgs: Any): String
\ No newline at end of file
+internal expect fun getString(string: Strings, vararg formatArgs: Any): String
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
index 6a64e73..d57af0c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/SwipeableV2.kt
@@ -119,9 +119,10 @@
             }
         }
         if (previousAnchors != newAnchors) {
-            state.updateAnchors(newAnchors)
-            if (previousAnchors.isNotEmpty()) {
-                anchorChangeHandler?.onAnchorsChanged(previousAnchors, newAnchors)
+            val previousTarget = state.targetValue
+            val stateRequiresCleanup = state.updateAnchors(newAnchors)
+            if (stateRequiresCleanup) {
+                anchorChangeHandler?.onAnchorsChanged(previousTarget, previousAnchors, newAnchors)
             }
         }
     },
@@ -241,8 +242,8 @@
     var lastVelocity: Float by mutableStateOf(0f)
         private set
 
-    private val minBound by derivedStateOf { anchors.minOrNull() ?: Float.NEGATIVE_INFINITY }
-    private val maxBound by derivedStateOf { anchors.maxOrNull() ?: Float.POSITIVE_INFINITY }
+    val minBound by derivedStateOf { anchors.minOrNull() ?: Float.NEGATIVE_INFINITY }
+    val maxBound by derivedStateOf { anchors.maxOrNull() ?: Float.POSITIVE_INFINITY }
 
     private var animationTarget: T? by mutableStateOf(null)
     internal val draggableState = DraggableState {
@@ -253,12 +254,25 @@
 
     internal var density: Density? = null
 
-    internal fun updateAnchors(newAnchors: Map<T, Float>) {
+    /**
+     * Update the anchors.
+     * If the previous set of anchors was empty, attempt to update the offset to match the initial
+     * value's anchor.
+     *
+     * @return true if the state needs to be adjusted after updating the anchors, e.g. if the
+     * initial value is not found in the initial set of anchors. false if no further updates are
+     * needed.
+     */
+    internal fun updateAnchors(newAnchors: Map<T, Float>): Boolean {
         val previousAnchorsEmpty = anchors.isEmpty()
         anchors = newAnchors
-        if (previousAnchorsEmpty) {
-            offset = anchors.requireAnchor(this.currentValue)
-        }
+        val initialValueHasAnchor = if (previousAnchorsEmpty) {
+            val initialValueAnchor = anchors[currentValue]
+            val initialValueHasAnchor = initialValueAnchor != null
+            if (initialValueHasAnchor) offset = initialValueAnchor
+            initialValueHasAnchor
+        } else true
+        return !initialValueHasAnchor || !previousAnchorsEmpty
     }
 
     /**
@@ -289,6 +303,8 @@
 
     /**
      * Animate to a [targetValue].
+     * If the [targetValue] is not in the set of anchors, the [currentValue] will be updated to the
+     * [targetValue] without updating the offset.
      *
      * @throws CancellationException if the interaction interrupted by another interaction like a
      * gesture interaction or another programmatic interaction like a [animateTo] or [snapTo] call.
@@ -300,30 +316,34 @@
         targetValue: T,
         velocity: Float = lastVelocity,
     ) {
-        val targetOffset = anchors.requireAnchor(targetValue)
-        try {
-            draggableState.drag {
-                animationTarget = targetValue
-                var prev = offset ?: 0f
-                animate(prev, targetOffset, velocity, animationSpec) { value, velocity ->
-                    // Our onDrag coerces the value within the bounds, but an animation may
-                    // overshoot, for example a spring animation or an overshooting interpolator
-                    // We respect the user's intention and allow the overshoot, but still use
-                    // DraggableState's drag for its mutex.
-                    offset = value
-                    prev = value
-                    lastVelocity = velocity
+        val targetOffset = anchors[targetValue]
+        if (targetOffset != null) {
+            try {
+                draggableState.drag {
+                    animationTarget = targetValue
+                    var prev = offset ?: 0f
+                    animate(prev, targetOffset, velocity, animationSpec) { value, velocity ->
+                        // Our onDrag coerces the value within the bounds, but an animation may
+                        // overshoot, for example a spring animation or an overshooting interpolator
+                        // We respect the user's intention and allow the overshoot, but still use
+                        // DraggableState's drag for its mutex.
+                        offset = value
+                        prev = value
+                        lastVelocity = velocity
+                    }
+                    lastVelocity = 0f
                 }
-                lastVelocity = 0f
+            } finally {
+                animationTarget = null
+                val endOffset = requireOffset()
+                val endState = anchors
+                    .entries
+                    .firstOrNull { (_, anchorOffset) -> abs(anchorOffset - endOffset) < 0.5f }
+                    ?.key
+                this.currentValue = endState ?: currentValue
             }
-        } finally {
-            animationTarget = null
-            val endOffset = requireOffset()
-            val endState = anchors
-                .entries
-                .firstOrNull { (_, anchorOffset) -> abs(anchorOffset - endOffset) < 0.5f }
-                ?.key
-            this.currentValue = endState ?: currentValue
+        } else {
+            currentValue = targetValue
         }
     }
 
@@ -533,8 +553,7 @@
         state: SwipeableV2State<T>,
         animate: (target: T, velocity: Float) -> Unit,
         snap: (target: T) -> Unit
-    ) = AnchorChangeHandler { previousAnchors, newAnchors ->
-        val previousTarget = state.targetValue
+    ) = AnchorChangeHandler { previousTarget, previousAnchors, newAnchors ->
         val previousTargetOffset = previousAnchors[previousTarget]
         val newTargetOffset = newAnchors[previousTarget]
         if (previousTargetOffset != newTargetOffset) {
@@ -562,10 +581,15 @@
      * Callback that is invoked when the anchors have changed, after the [SwipeableV2State] has been
      * updated with them. Use this hook to re-launch animations or interrupt them if needed.
      *
+     * @param previousTargetValue The target value before the anchors were updated
      * @param previousAnchors The previously set anchors
      * @param newAnchors The newly set anchors
      */
-    fun onAnchorsChanged(previousAnchors: Map<T, Float>, newAnchors: Map<T, Float>)
+    fun onAnchorsChanged(
+        previousTargetValue: T,
+        previousAnchors: Map<T, Float>,
+        newAnchors: Map<T, Float>
+    )
 }
 
 @Stable
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimeFormat.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimeFormat.kt
new file mode 100644
index 0000000..01f635a
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimeFormat.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.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+
+internal expect val is24HourFormat: Boolean
+  @Composable
+  @ReadOnlyComposable get
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
new file mode 100644
index 0000000..89aa6d8
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/TimePicker.kt
@@ -0,0 +1,1007 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.animation.Crossfade
+import androidx.compose.animation.core.Animatable
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.BorderStroke
+import androidx.compose.foundation.MutatePriority
+import androidx.compose.foundation.MutatorMutex
+import androidx.compose.foundation.background
+import androidx.compose.foundation.border
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.gestures.detectDragGestures
+import androidx.compose.foundation.gestures.detectTapGestures
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.ColumnScope
+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.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.selection.selectableGroup
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.foundation.shape.CornerBasedShape
+import androidx.compose.material3.tokens.MotionTokens
+import androidx.compose.material3.tokens.TimePickerTokens
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialColor
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialContainerSize
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialLabelTextFont
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialSelectedLabelTextColor
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialSelectorCenterContainerSize
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialSelectorHandleContainerColor
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialSelectorHandleContainerSize
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialSelectorTrackContainerWidth
+import androidx.compose.material3.tokens.TimePickerTokens.ClockDialUnselectedLabelTextColor
+import androidx.compose.material3.tokens.TimePickerTokens.ContainerColor
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorContainerShape
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorOutlineColor
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorSelectedContainerColor
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorSelectedLabelTextColor
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorUnselectedLabelTextColor
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorVerticalContainerHeight
+import androidx.compose.material3.tokens.TimePickerTokens.PeriodSelectorVerticalContainerWidth
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorContainerHeight
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorContainerShape
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorContainerWidth
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorLabelTextFont
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorSelectedContainerColor
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorSelectedLabelTextColor
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorUnselectedContainerColor
+import androidx.compose.material3.tokens.TimePickerTokens.TimeSelectorUnselectedLabelTextColor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.ReadOnlyComposable
+import androidx.compose.runtime.Stable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.saveable.Saver
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.structuralEqualityPolicy
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.draw.drawWithContent
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.center
+import androidx.compose.ui.graphics.BlendMode
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shape
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.layoutId
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.semantics.contentDescription
+import androidx.compose.ui.semantics.role
+import androidx.compose.ui.semantics.selectableGroup
+import androidx.compose.ui.semantics.selected
+import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.text.style.LineHeightStyle
+import androidx.compose.ui.text.style.TextAlign
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.center
+import androidx.compose.ui.unit.dp
+import java.text.NumberFormat
+import kotlin.math.PI
+import kotlin.math.abs
+import kotlin.math.atan2
+import kotlin.math.cos
+import kotlin.math.hypot
+import kotlin.math.roundToInt
+import kotlin.math.sin
+import kotlinx.coroutines.launch
+
+/**
+ * Time pickers help users select and set a specific time.
+ *
+ * Shows a picker that allows the user to select time.
+ * Subscribe to updates through [TimePickerState]
+ *
+ * @sample androidx.compose.material3.samples.TimePickerSample
+ *
+ * [state] state for this timepicker, allows to subscribe to changes to [TimePickerState.hour] and
+ * [TimePickerState.minute], and set the initial time for this picker.
+ *
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun TimePicker(
+    state: TimePickerState,
+    colors: TimePickerColors = TimePickerDefaults.colors()
+) {
+    Column(horizontalAlignment = Alignment.CenterHorizontally) {
+        ClockDisplay(state, colors)
+        Spacer(modifier = Modifier.height(ClockDisplayBottomMargin))
+        ClockFace(state, colors)
+        Spacer(modifier = Modifier.height(ClockFaceBottomMargin))
+    }
+}
+
+/**
+ * Contains the default values used by [TimePicker]
+ */
+@ExperimentalMaterial3Api
+@Stable
+object TimePickerDefaults {
+
+    /**
+     * Default colors used by a [TimePicker] in different states
+     *
+     * @param clockDialColor The color of the clock dial.
+     * @param clockDialSelectedContentColor the color of the numbers of the clock dial when they
+     * are selected or overlapping with the selector
+     * @param clockDialUnselectedContentColor the color of the numbers of the clock dial when they
+     * are unselected
+     * @param selectorColor The color of the clock dial selector.
+     * @param containerColor The container color of the time picker.
+     * @param periodSelectorBorderColor the color used for the border of the AM/PM toggle.
+     * @param periodSelectorSelectedContainerColor the color used for the selected container of
+     * the AM/PM toggle
+     * @param periodSelectorUnselectedContainerColor the color used for the unselected container
+     * of the AM/PM toggle
+     * @param periodSelectorSelectedContentColor color used for the selected content of
+     * the AM/PM toggle
+     * @param periodSelectorUnselectedContentColor color used for the unselected content
+     * of the AM/PM toggle
+     * @param timeSelectorSelectedContainerColor color used for the selected container of the
+     * display buttons to switch between hour and minutes
+     * @param timeSelectorUnselectedContainerColor color used for the unselected container of the
+     * display buttons to switch between hour and minutes
+     * @param timeSelectorSelectedContentColor color used for the selected content of the display
+     * buttons to switch between hour and minutes
+     * @param timeSelectorUnselectedContentColor color used for the unselected content of the
+     * display buttons to switch between hour and minutes
+     */
+    @Composable
+    fun colors(
+        clockDialColor: Color = ClockDialColor.toColor(),
+        clockDialSelectedContentColor: Color = ClockDialSelectedLabelTextColor.toColor(),
+        clockDialUnselectedContentColor: Color = ClockDialUnselectedLabelTextColor.toColor(),
+        selectorColor: Color = ClockDialSelectorHandleContainerColor.toColor(),
+        containerColor: Color = ContainerColor.toColor(),
+        periodSelectorBorderColor: Color = PeriodSelectorOutlineColor.toColor(),
+        periodSelectorSelectedContainerColor: Color =
+            PeriodSelectorSelectedContainerColor.toColor(),
+        periodSelectorUnselectedContainerColor: Color = Color.Transparent,
+        periodSelectorSelectedContentColor: Color =
+            PeriodSelectorSelectedLabelTextColor.toColor(),
+        periodSelectorUnselectedContentColor: Color =
+            PeriodSelectorUnselectedLabelTextColor.toColor(),
+        timeSelectorSelectedContainerColor: Color =
+            TimeSelectorSelectedContainerColor.toColor(),
+        timeSelectorUnselectedContainerColor: Color =
+            TimeSelectorUnselectedContainerColor.toColor(),
+        timeSelectorSelectedContentColor: Color =
+            TimeSelectorSelectedLabelTextColor.toColor(),
+        timeSelectorUnselectedContentColor: Color =
+            TimeSelectorUnselectedLabelTextColor.toColor(),
+    ) = TimePickerColors(
+        clockDialColor = clockDialColor,
+        clockDialSelectedContentColor = clockDialSelectedContentColor,
+        clockDialUnselectedContentColor = clockDialUnselectedContentColor,
+        selectorColor = selectorColor,
+        containerColor = containerColor,
+        periodSelectorBorderColor = periodSelectorBorderColor,
+        periodSelectorSelectedContainerColor = periodSelectorSelectedContainerColor,
+        periodSelectorUnselectedContainerColor = periodSelectorUnselectedContainerColor,
+        periodSelectorSelectedContentColor = periodSelectorSelectedContentColor,
+        periodSelectorUnselectedContentColor = periodSelectorUnselectedContentColor,
+        timeSelectorSelectedContainerColor = timeSelectorSelectedContainerColor,
+        timeSelectorUnselectedContainerColor = timeSelectorUnselectedContainerColor,
+        timeSelectorSelectedContentColor = timeSelectorSelectedContentColor,
+        timeSelectorUnselectedContentColor = timeSelectorUnselectedContentColor
+    )
+}
+
+/**
+ * Represents the colors used by a [TimePicker] in different states
+ *
+ * See [TimePickerDefaults.colors] for the default implementation that follows Material
+ * specifications.
+ */
+@Immutable
+@ExperimentalMaterial3Api
+class TimePickerColors internal constructor(
+    internal val clockDialColor: Color,
+    internal val selectorColor: Color,
+    internal val containerColor: Color,
+    internal val periodSelectorBorderColor: Color,
+    private val clockDialSelectedContentColor: Color,
+    private val clockDialUnselectedContentColor: Color,
+    private val periodSelectorSelectedContainerColor: Color,
+    private val periodSelectorUnselectedContainerColor: Color,
+    private val periodSelectorSelectedContentColor: Color,
+    private val periodSelectorUnselectedContentColor: Color,
+    private val timeSelectorSelectedContainerColor: Color,
+    private val timeSelectorUnselectedContainerColor: Color,
+    private val timeSelectorSelectedContentColor: Color,
+    private val timeSelectorUnselectedContentColor: Color,
+) {
+    internal fun periodSelectorContainerColor(selected: Boolean) =
+        if (selected) {
+            periodSelectorSelectedContainerColor
+        } else {
+            periodSelectorUnselectedContainerColor
+        }
+
+    internal fun periodSelectorContentColor(selected: Boolean) =
+        if (selected) {
+            periodSelectorSelectedContentColor
+        } else {
+            periodSelectorUnselectedContentColor
+        }
+
+    internal fun timeSelectorContainerColor(selected: Boolean) =
+        if (selected) {
+            timeSelectorSelectedContainerColor
+        } else {
+            timeSelectorUnselectedContainerColor
+        }
+
+    internal fun timeSelectorContentColor(selected: Boolean) =
+        if (selected) {
+            timeSelectorSelectedContentColor
+        } else {
+            timeSelectorUnselectedContentColor
+        }
+
+    internal fun clockDialContentColor(selected: Boolean) =
+        if (selected) {
+            clockDialSelectedContentColor
+        } else {
+            clockDialUnselectedContentColor
+        }
+
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (javaClass != other?.javaClass) return false
+
+        other as TimePickerColors
+
+        if (clockDialColor != other.clockDialColor) return false
+        if (selectorColor != other.selectorColor) return false
+        if (containerColor != other.containerColor) return false
+        if (periodSelectorBorderColor != other.periodSelectorBorderColor) return false
+        if (periodSelectorSelectedContainerColor != other.periodSelectorSelectedContainerColor)
+            return false
+        if (periodSelectorUnselectedContainerColor != other.periodSelectorUnselectedContainerColor)
+            return false
+        if (periodSelectorSelectedContentColor != other.periodSelectorSelectedContentColor)
+            return false
+        if (periodSelectorUnselectedContentColor != other.periodSelectorUnselectedContentColor)
+            return false
+        if (timeSelectorSelectedContainerColor != other.timeSelectorSelectedContainerColor)
+            return false
+        if (timeSelectorUnselectedContainerColor != other.timeSelectorUnselectedContainerColor)
+            return false
+        if (timeSelectorSelectedContentColor != other.timeSelectorSelectedContentColor)
+            return false
+        if (timeSelectorUnselectedContentColor != other.timeSelectorUnselectedContentColor)
+            return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = clockDialColor.hashCode()
+        result = 31 * result + selectorColor.hashCode()
+        result = 31 * result + containerColor.hashCode()
+        result = 31 * result + periodSelectorBorderColor.hashCode()
+        result = 31 * result + periodSelectorSelectedContainerColor.hashCode()
+        result = 31 * result + periodSelectorUnselectedContainerColor.hashCode()
+        result = 31 * result + periodSelectorSelectedContentColor.hashCode()
+        result = 31 * result + periodSelectorUnselectedContentColor.hashCode()
+        result = 31 * result + timeSelectorSelectedContainerColor.hashCode()
+        result = 31 * result + timeSelectorUnselectedContainerColor.hashCode()
+        result = 31 * result + timeSelectorSelectedContentColor.hashCode()
+        result = 31 * result + timeSelectorUnselectedContentColor.hashCode()
+        return result
+    }
+}
+
+/**
+ * Creates a [TimePickerState] for a time picker that is remembered across compositions
+ * and configuration changes.
+ *
+ * @param initialHour starting hour for this state, will be displayed in the time picker when launched
+ * Ranges from 0 to 23
+ * @param initialMinute starting minute for this state, will be displayed in the time picker when
+ * launched. Ranges from 0 to 59
+ * @param is24Hour The format for this time picker `false` for 12 hour format with an AM/PM toggle
+ * or `true` for 24 hour format without toggle. Defaults to follow system setting.
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun rememberTimePickerState(
+    initialHour: Int = 0,
+    initialMinute: Int = 0,
+    is24Hour: Boolean = is24HourFormat,
+): TimePickerState = rememberSaveable(
+    saver = TimePickerState.Saver()
+) {
+    TimePickerState(
+        initialHour = initialHour,
+        initialMinute = initialMinute,
+        is24Hour = is24Hour,
+    )
+}
+
+/**
+ * A class to handle state changes in a [TimePicker]
+ *
+ * @sample androidx.compose.material3.samples.TimePickerSample
+ *
+ * @param initialHour
+ *  starting hour for this state, will be displayed in the time picker when launched
+ *  Ranges from 0 to 23
+ * @param initialMinute
+ *  starting minute for this state, will be displayed in the time picker when launched.
+ *  Ranges from 0 to 59
+ * @param is24Hour The format for this time picker `false` for 12 hour format with an AM/PM toggle
+ *  or `true` for 24 hour format without toggle.
+ */
+@Stable
+class TimePickerState(
+    initialHour: Int,
+    initialMinute: Int,
+    is24Hour: Boolean,
+) {
+    init {
+        require(initialHour in 0..23) { "initialHour should in [0..23] range" }
+        require(initialHour in 0..59) { "initialMinute should be in [0..59] range" }
+    }
+
+    val minute: Int get() = minuteAngle.toMinute()
+    val hour: Int get() = hourAngle.toHour() + if (isAfternoon) 12 else 0
+    val is24hour: Boolean = is24Hour
+
+    internal val hourForDisplay: Int get() = hourForDisplay(hour)
+    internal val selectorPos by derivedStateOf(structuralEqualityPolicy()) {
+        val inInnerCircle = isInnerCircle
+        val handleRadiusPx = ClockDialSelectorHandleContainerSize / 2
+        val selectorLength = if (is24Hour && inInnerCircle && selection == Selection.Hour) {
+            InnerCircleRadius
+        } else {
+            OuterCircleSizeRadius
+        }.minus(handleRadiusPx)
+
+        val length = selectorLength + handleRadiusPx
+        val offsetX = length * cos(currentAngle.value) + ClockDialContainerSize / 2
+        val offsetY = length * sin(currentAngle.value) + ClockDialContainerSize / 2
+
+        DpOffset(offsetX, offsetY)
+    }
+
+    internal val values by derivedStateOf {
+        if (selection == Selection.Minute) Minutes else Hours
+    }
+
+    internal var selection by mutableStateOf(Selection.Hour)
+    internal var isAfternoonToggle by mutableStateOf(initialHour > 12 && !is24Hour)
+    internal var isInnerCircle by mutableStateOf(initialHour > 12 || initialHour == 0)
+
+    private var hourAngle by mutableStateOf(RadiansPerHour * initialHour % 12 - FullCircle / 4)
+    private var minuteAngle by mutableStateOf(RadiansPerMinute * initialMinute - FullCircle / 4)
+
+    private val mutex = MutatorMutex()
+    private val isAfternoon by derivedStateOf {
+        (is24hour && isInnerCircle && hourAngle.toHour() != 0) || isAfternoonToggle
+    }
+
+    internal val currentAngle = Animatable(hourAngle)
+
+    internal fun isSelected(value: Int): Boolean =
+        if (selection == Selection.Minute) {
+            value == minute
+        } else {
+            hour == (value + if (isAfternoon) 12 else 0)
+        }
+
+    internal suspend fun update(value: Float, fromTap: Boolean = false) {
+        mutex.mutate(MutatePriority.UserInput) {
+            if (selection == Selection.Hour) {
+                hourAngle = value.toHour() % 12 * RadiansPerHour
+            } else if (fromTap) {
+                minuteAngle = (value.toMinute() - value.toMinute() % 5) * RadiansPerMinute
+            } else {
+                minuteAngle = value.toMinute() * RadiansPerMinute
+            }
+
+            if (fromTap) {
+                currentAngle.snapTo(minuteAngle)
+            } else {
+                currentAngle.snapTo(offsetHour(value))
+            }
+        }
+    }
+
+    internal suspend fun animateToCurrent() {
+        val (start, end) = if (selection == Selection.Hour) {
+            valuesForAnimation(minuteAngle, hourAngle)
+        } else {
+            valuesForAnimation(hourAngle, minuteAngle)
+        }
+
+        currentAngle.snapTo(start)
+        currentAngle.animateTo(end, tween(200))
+    }
+
+    private fun hourForDisplay(hour: Int): Int = when {
+        is24hour && isInnerCircle && hour == 0 -> 12
+        is24hour -> hour % 24
+        hour % 12 == 0 -> 12
+        isAfternoon -> hour - 12
+        else -> hour
+    }
+
+    private fun offsetHour(angle: Float): Float {
+        val ret = angle + QuarterCircle.toFloat()
+        return if (ret < 0) ret + FullCircle else ret
+    }
+
+    private fun Float.toHour(): Int {
+        val hourOffset: Float = RadiansPerHour / 2
+        val totalOffset = hourOffset + QuarterCircle
+        return ((this + totalOffset) / RadiansPerHour).toInt() % 12
+    }
+
+    private fun Float.toMinute(): Int {
+        val hourOffset: Float = RadiansPerMinute / 2
+        val totalOffset = hourOffset + QuarterCircle
+        return ((this + totalOffset) / RadiansPerMinute).toInt() % 60
+    }
+
+    suspend fun settle() {
+        val targetValue = valuesForAnimation(currentAngle.value, minuteAngle)
+        currentAngle.snapTo(targetValue.first)
+        currentAngle.animateTo(targetValue.second, tween(200))
+    }
+
+    companion object {
+        /**
+         * The default [Saver] implementation for [TimePickerState].
+         */
+        fun Saver(): Saver<TimePickerState, *> = Saver(
+            save = {
+                listOf(
+                    it.minute,
+                    it.hour,
+                    it.is24hour
+                )
+            },
+            restore = { value ->
+                TimePickerState(
+                    initialHour = value[0] as Int,
+                    initialMinute = value[1] as Int,
+                    is24Hour = value[2] as Boolean
+                )
+            }
+        )
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun ClockDisplay(state: TimePickerState, colors: TimePickerColors) {
+    Row(horizontalArrangement = Arrangement.Center) {
+        TimeSelector(state.hourForDisplay, state, Selection.Hour, colors = colors)
+        DisplaySeparator()
+        TimeSelector(state.minute, state, Selection.Minute, colors = colors)
+        if (!state.is24hour) {
+            Spacer(modifier = Modifier.width(PeriodToggleTopMargin))
+            PeriodToggle(state, colors)
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun PeriodToggle(state: TimePickerState, colors: TimePickerColors) {
+    val borderStroke = BorderStroke(
+        TimePickerTokens.PeriodSelectorOutlineWidth,
+        colors.periodSelectorBorderColor
+    )
+
+    val shape = PeriodSelectorContainerShape.toShape() as CornerBasedShape
+    val contentDescription = getString(Strings.TimePickerPeriodToggle)
+    Column(
+        Modifier
+            .semantics { this.contentDescription = contentDescription }
+            .selectableGroup()
+            .size(PeriodSelectorVerticalContainerWidth, PeriodSelectorVerticalContainerHeight)
+            .border(border = borderStroke, shape = shape)
+    ) {
+        ToggleItem(
+            checked = !state.isAfternoonToggle,
+            shape = shape.top(),
+            onClick = {
+                state.isAfternoonToggle = false
+            },
+            colors = colors,
+        ) { Text(text = getString(string = Strings.TimePickerAM)) }
+        Spacer(
+            Modifier
+                .fillMaxWidth()
+                .height(TimePickerTokens.PeriodSelectorOutlineWidth)
+                .background(color = PeriodSelectorOutlineColor.toColor())
+        )
+        ToggleItem(
+            checked =
+            state.isAfternoonToggle,
+            shape = shape.bottom(),
+            onClick = {
+                state.isAfternoonToggle = true
+            },
+            colors = colors,
+        ) { Text(getString(string = Strings.TimePickerPM)) }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun ColumnScope.ToggleItem(
+    checked: Boolean,
+    shape: Shape,
+    onClick: () -> Unit,
+    colors: TimePickerColors,
+    content: @Composable RowScope.() -> Unit,
+) {
+    val contentColor = colors.periodSelectorContentColor(checked)
+    val containerColor = colors.periodSelectorContainerColor(checked)
+
+    TextButton(
+        modifier = Modifier
+            .weight(1f)
+            .semantics { selected = checked },
+        contentPadding = PaddingValues(0.dp),
+        shape = shape,
+        onClick = onClick,
+        content = content,
+        colors = ButtonDefaults.textButtonColors(
+            contentColor = contentColor,
+            containerColor = containerColor
+        )
+    )
+}
+
+@Composable
+private fun DisplaySeparator() {
+    val style = copyAndSetFontPadding(
+        style = MaterialTheme.typography.fromToken(TimeSelectorLabelTextFont).copy(
+            textAlign = TextAlign.Center,
+            lineHeightStyle = LineHeightStyle(
+                alignment = LineHeightStyle.Alignment.Center, trim = LineHeightStyle.Trim.Both
+            )
+        ), includeFontPadding = false
+    )
+
+    Box(
+        modifier = Modifier.size(
+            DisplaySeparatorWidth,
+            PeriodSelectorVerticalContainerHeight
+        ), contentAlignment = Alignment.Center
+    ) { Text(text = ":", style = style) }
+}
+
+@Composable
+@OptIn(ExperimentalMaterial3Api::class)
+private fun TimeSelector(
+    value: Int,
+    state: TimePickerState,
+    selection: Selection,
+    colors: TimePickerColors,
+) {
+    val selected = state.selection == selection
+    val selectorContentDescription = getString(
+        if (selection == Selection.Hour) {
+            Strings.TimePickerHourSelection
+        } else {
+            Strings.TimePickerMinuteSelection
+        }
+    )
+
+    val containerColor = colors.timeSelectorContainerColor(selected)
+    val contentColor = colors.timeSelectorContentColor(selected)
+    val scope = rememberCoroutineScope()
+    Surface(
+        modifier = Modifier
+            .size(TimeSelectorContainerWidth, TimeSelectorContainerHeight)
+            .semantics(mergeDescendants = true) {
+                role = Role.RadioButton
+                this.contentDescription = selectorContentDescription
+            },
+        onClick = {
+            if (selection != state.selection) {
+                state.selection = selection
+                scope.launch {
+                    state.animateToCurrent()
+                }
+            }
+        },
+        selected = selected,
+        shape = TimeSelectorContainerShape.toShape(),
+        color = containerColor,
+    ) {
+        val valueContentDescription = getString(
+            numberContentDescription(
+                selection = selection,
+                is24Hour = state.is24hour
+            ),
+            value
+        )
+        Box(contentAlignment = Alignment.Center) {
+            val textStyle = MaterialTheme.typography.fromToken(TimeSelectorLabelTextFont)
+            Text(
+                modifier = Modifier.semantics { contentDescription = valueContentDescription },
+                text = value.toLocalString(minDigits = 2),
+                color = contentColor,
+                style = textStyle,
+            )
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun ClockFace(state: TimePickerState, colors: TimePickerColors) {
+    Crossfade(
+        modifier = Modifier
+            .background(shape = CircleShape, color = colors.clockDialColor)
+            .size(ClockDialContainerSize)
+            .semantics {
+                selectableGroup()
+            },
+        targetState = state.values,
+        animationSpec = tween(durationMillis = MotionTokens.DurationMedium3.toInt())
+    ) { screen ->
+        CircularLayout(
+            modifier = Modifier
+                .clockDial(state)
+                .size(ClockDialContainerSize)
+                .drawSelector(state, colors),
+            radius = OuterCircleSizeRadius,
+        ) {
+            CompositionLocalProvider(
+                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)
+                    )
+                }
+
+                if (state.selection == Selection.Hour && state.is24hour) {
+                    CircularLayout(
+                        modifier = Modifier
+                            .layoutId(LayoutId.InnerCircle)
+                            .size(ClockDialContainerSize)
+                            .background(shape = CircleShape, color = Color.Transparent),
+                        radius = InnerCircleRadius
+                    ) {
+                        repeat(ExtraHours.size) {
+                            val innerValue = ExtraHours[it]
+                            ClockText(
+                                is24Hour = true,
+                                selection = state.selection,
+                                value = innerValue,
+                                selected = state.isSelected(it % 11)
+                            )
+                        }
+                    }
+                }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+private fun Modifier.drawSelector(
+    state: TimePickerState,
+    colors: TimePickerColors,
+): Modifier = this.drawWithContent {
+    val selectorOffsetPx = Offset(state.selectorPos.x.toPx(), state.selectorPos.y.toPx())
+
+    val selectorRadius = ClockDialSelectorHandleContainerSize.toPx() / 2
+    val selectorColor = colors.selectorColor
+
+    // clear out the selector section
+    drawCircle(
+        radius = selectorRadius,
+        center = selectorOffsetPx,
+        color = Color.Black,
+        blendMode = BlendMode.Clear,
+    )
+
+    // draw the text composables
+    drawContent()
+
+    // draw the selector and clear out the numbers overlapping
+    drawCircle(
+        radius = selectorRadius,
+        center = selectorOffsetPx,
+        color = selectorColor,
+        blendMode = BlendMode.Xor
+    )
+
+    val strokeWidth = ClockDialSelectorTrackContainerWidth.toPx()
+    val lineLength = selectorOffsetPx.minus(
+        Offset(
+            (selectorRadius * cos(state.currentAngle.value)),
+            (selectorRadius * sin(state.currentAngle.value))
+        )
+    )
+
+    // draw the selector line
+    drawLine(
+        start = size.center,
+        strokeWidth = strokeWidth,
+        end = lineLength,
+        color = selectorColor,
+        blendMode = BlendMode.SrcOver
+    )
+
+    // draw the selector small dot
+    drawCircle(
+        radius = ClockDialSelectorCenterContainerSize.toPx() / 2,
+        center = size.center,
+        color = selectorColor,
+    )
+
+    // draw the portion of the number that was overlapping
+    drawCircle(
+        radius = selectorRadius,
+        center = selectorOffsetPx,
+        color = colors.clockDialContentColor(selected = true),
+        blendMode = BlendMode.DstOver
+    )
+}
+
+private fun Modifier.clockDial(state: TimePickerState): Modifier = composed(debugInspectorInfo {
+    name = "clockDial"
+    properties["state"] = state
+}) {
+    var offsetX by remember { mutableStateOf(0f) }
+    var offsetY by remember { mutableStateOf(0f) }
+    var 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) {
+            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()
+                        }
+                    }
+                },
+            )
+        }
+        .pointerInput(state, maxDist, center) {
+            detectDragGestures(onDragEnd = {
+                scope.launch {
+                    if (state.selection == Selection.Hour) {
+                        state.selection = Selection.Minute
+                        state.animateToCurrent()
+                    } else {
+                        state.settle()
+                    }
+                }
+            }) { _, dragAmount ->
+                scope.launch {
+                    offsetX += dragAmount.x
+                    offsetY += dragAmount.y
+                    state.update(atan(offsetY - center.y, offsetX - center.x))
+                }
+                moveSelector(offsetX, offsetY)
+            }
+        }
+}
+
+@Composable
+private fun ClockText(
+    is24Hour: Boolean,
+    selected: Boolean,
+    selection: Selection,
+    value: Int
+) {
+    val style = MaterialTheme.typography.fromToken(ClockDialLabelTextFont).let {
+        remember(it) {
+            copyAndSetFontPadding(style = it, false)
+        }
+    }
+
+    val contentDescription = getString(
+        numberContentDescription(
+            selection = selection,
+            is24Hour = is24Hour
+        ),
+        value
+    )
+
+    Box(
+        contentAlignment = Alignment.Center,
+        modifier = Modifier
+            .minimumInteractiveComponentSize()
+            .size(MinimumInteractiveSize)
+            .focusable()
+            .semantics(mergeDescendants = true) {
+                this.selected = selected
+                this.contentDescription = contentDescription
+            }
+    ) {
+        Text(
+            text = value.toLocalString(minDigits = 1),
+            style = style,
+        )
+    }
+}
+
+/** Distribute elements evenly on a circle of [radius] */
+@Composable
+private fun CircularLayout(
+    modifier: Modifier = Modifier,
+    radius: Dp,
+    content: @Composable () -> Unit,
+) {
+    Layout(
+        modifier = modifier, content = content
+    ) { measurables, constraints ->
+        val radiusPx = radius.toPx()
+        val itemConstraints = constraints.copy(minWidth = 0, minHeight = 0)
+        val placeables = measurables.filter {
+            it.layoutId != LayoutId.Selector && it.layoutId != LayoutId.InnerCircle
+        }.map { measurable -> measurable.measure(itemConstraints) }
+        val selectorMeasurable = measurables.find { it.layoutId == LayoutId.Selector }
+        val innerMeasurable = measurables.find { it.layoutId == LayoutId.InnerCircle }
+        val theta = FullCircle / (placeables.count())
+        val selectorPlaceable = selectorMeasurable?.measure(itemConstraints)
+        val innerCirclePlaceable = innerMeasurable?.measure(itemConstraints)
+
+        layout(
+            width = constraints.minWidth,
+            height = constraints.minHeight,
+        ) {
+            selectorPlaceable?.place(0, 0)
+
+            placeables.forEachIndexed { i, it ->
+                val centerOffsetX = constraints.maxWidth / 2 - it.width / 2
+                val centerOffsetY = constraints.maxHeight / 2 - it.height / 2
+                val offsetX = radiusPx * cos(theta * i - QuarterCircle) + centerOffsetX
+                val offsetY = radiusPx * sin(theta * i - QuarterCircle) + centerOffsetY
+                it.place(
+                    x = offsetX.roundToInt(), y = offsetY.roundToInt()
+                )
+            }
+
+            innerCirclePlaceable?.place(
+                (constraints.minWidth - innerCirclePlaceable.width) / 2,
+                (constraints.minHeight - innerCirclePlaceable.height) / 2
+            )
+        }
+    }
+}
+
+@Composable
+@ReadOnlyComposable
+private fun numberContentDescription(selection: Selection, is24Hour: Boolean): Strings {
+    if (selection == Selection.Minute) {
+        return Strings.TimePickerMinuteSuffix
+    }
+
+    if (is24Hour) {
+        return Strings.TimePicker24HourSuffix
+    }
+
+    return Strings.TimePickerHourSuffix
+}
+
+private fun valuesForAnimation(current: Float, new: Float): Pair<Float, Float> {
+    var start = current
+    var end = new
+    if (abs(start - end) <= PI) {
+        return Pair(start, end)
+    }
+
+    if (start > PI && end < PI) {
+        end += FullCircle
+    } else if (current < PI && new > PI) {
+        start += FullCircle
+    }
+
+    return Pair(start, end)
+}
+
+private fun dist(x1: Float, y1: Float, x2: Int, y2: Int): Float {
+    val x = x2 - x1
+    val y = y2 - y1
+    return hypot(x.toDouble(), y.toDouble()).toFloat()
+}
+
+private fun atan(y: Float, x: Float): Float {
+    val ret = atan2(y, x) - QuarterCircle.toFloat()
+    return if (ret < 0) ret + FullCircle else ret
+}
+
+private enum class LayoutId {
+    Selector, InnerCircle,
+}
+
+internal enum class Selection {
+    Hour, Minute
+}
+
+private const val FullCircle: Float = (PI * 2).toFloat()
+private const val QuarterCircle = PI / 2
+private const val RadiansPerMinute: Float = FullCircle / 60
+private const val RadiansPerHour: Float = FullCircle / 12f
+
+private val OuterCircleSizeRadius = 101.dp
+private val InnerCircleRadius = 69.dp
+private val ClockDisplayBottomMargin = 36.dp
+private val ClockFaceBottomMargin = 24.dp
+private val PeriodToggleTopMargin = 12.dp
+private val DisplaySeparatorWidth = 24.dp
+
+private val MaxDistance = 74.dp
+private val MinimumInteractiveSize = 48.dp
+private val Minutes = listOf(0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 55)
+private val Hours = listOf(12, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11)
+private val ExtraHours = Hours.map { (it % 12 + 12) }
+
+private fun Int.toLocalString(minDigits: Int): String {
+    val formatter = NumberFormat.getIntegerInstance()
+    // Eliminate any use of delimiters when formatting the integer.
+    formatter.isGroupingUsed = false
+    formatter.minimumIntegerDigits = minDigits
+    return formatter.format(this)
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
index 1c40955..302f4c8 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Tooltip.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -28,12 +28,18 @@
 import androidx.compose.foundation.gestures.awaitFirstDown
 import androidx.compose.foundation.gestures.waitForUpOrCancellation
 import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.paddingFromBaseline
+import androidx.compose.foundation.layout.requiredHeightIn
 import androidx.compose.foundation.layout.sizeIn
 import androidx.compose.material3.tokens.PlainTooltipTokens
+import androidx.compose.material3.tokens.RichTooltipTokens
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.SideEffect
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
@@ -64,6 +70,7 @@
 import androidx.compose.ui.window.PopupPositionProvider
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.launch
+import kotlinx.coroutines.suspendCancellableCoroutine
 
 // TODO: add link to m3 doc once created by designer at the top
 /**
@@ -90,7 +97,7 @@
 fun PlainTooltipBox(
     tooltip: @Composable () -> Unit,
     modifier: Modifier = Modifier,
-    tooltipState: TooltipState = remember { TooltipState() },
+    tooltipState: PlainTooltipState = remember { PlainTooltipState() },
     shape: Shape = TooltipDefaults.plainTooltipContainerShape,
     containerColor: Color = TooltipDefaults.plainTooltipContainerColor,
     contentColor: Color = TooltipDefaults.plainTooltipContentColor,
@@ -117,6 +124,68 @@
     )
 }
 
+// TODO: add link to m3 doc once created by designer
+/**
+ * Rich text tooltip that allows the user to pass in a title, text, and action.
+ * Tooltips are used to provide a descriptive message for an anchor.
+ *
+ * Tooltip that is invoked when the anchor is long pressed:
+ *
+ * @sample androidx.compose.material3.samples.RichTooltipSample
+ *
+ * If control of when the tooltip is shown is desired please see
+ *
+ * @sample androidx.compose.material3.samples.RichTooltipWithManualInvocationSample
+ *
+ * @param text the message to be displayed in the center of the tooltip.
+ * @param modifier the [Modifier] to be applied to the tooltip.
+ * @param tooltipState handles the state of the tooltip's visibility.
+ * @param title An optional title for the tooltip.
+ * @param action An optional action for the tooltip.
+ * @param shape the [Shape] that should be applied to the tooltip container.
+ * @param colors [RichTooltipColors] that will be applied to the tooltip's container and content.
+ * @param content the composable that the tooltip will anchor to.
+ */
+@Composable
+@ExperimentalMaterial3Api
+fun RichTooltipBox(
+    text: @Composable () -> Unit,
+    modifier: Modifier = Modifier,
+    tooltipState: RichTooltipState = remember { RichTooltipState() },
+    title: (@Composable () -> Unit)? = null,
+    action: (@Composable () -> Unit)? = null,
+    shape: Shape = TooltipDefaults.richTooltipContainerShape,
+    colors: RichTooltipColors = TooltipDefaults.richTooltipColors(),
+    content: @Composable TooltipBoxScope.() -> Unit
+) {
+    val tooltipAnchorPadding = with(LocalDensity.current) { TooltipAnchorPadding.roundToPx() }
+    val positionProvider = remember { RichTooltipPositionProvider(tooltipAnchorPadding) }
+
+    SideEffect {
+        // Make the rich tooltip persistent if an action is provided.
+        tooltipState.isPersistent = (action != null)
+    }
+
+    TooltipBox(
+        tooltipContent = {
+            RichTooltipImpl(
+                colors = colors,
+                title = title,
+                text = text,
+                action = action
+            )
+        },
+        shape = shape,
+        containerColor = colors.containerColor,
+        tooltipPositionProvider = positionProvider,
+        tooltipState = tooltipState,
+        elevation = RichTooltipTokens.ContainerElevation,
+        maxWidth = RichTooltipMaxWidth,
+        modifier = modifier,
+        content = content
+    )
+}
+
 @OptIn(ExperimentalMaterial3Api::class)
 @Composable
 private fun TooltipBox(
@@ -221,28 +290,135 @@
     }
 }
 
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+private fun RichTooltipImpl(
+    colors: RichTooltipColors,
+    text: @Composable () -> Unit,
+    title: (@Composable () -> Unit)?,
+    action: (@Composable () -> Unit)?
+) {
+    val actionLabelTextStyle =
+        MaterialTheme.typography.fromToken(RichTooltipTokens.ActionLabelTextFont)
+    val subheadTextStyle =
+        MaterialTheme.typography.fromToken(RichTooltipTokens.SubheadFont)
+    val supportingTextStyle =
+        MaterialTheme.typography.fromToken(RichTooltipTokens.SupportingTextFont)
+    Column(
+        modifier = Modifier.padding(horizontal = RichTooltipHorizontalPadding)
+    ) {
+        title?.let {
+            Box(
+                modifier = Modifier.paddingFromBaseline(top = HeightToSubheadFirstLine)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.titleContentColor,
+                    LocalTextStyle provides subheadTextStyle,
+                    content = it
+                )
+            }
+        }
+        Box(
+            modifier = Modifier.textVerticalPadding(title != null, action != null)
+        ) {
+            CompositionLocalProvider(
+                LocalContentColor provides colors.contentColor,
+                LocalTextStyle provides supportingTextStyle,
+                content = text
+            )
+        }
+        action?.let {
+            Box(
+                modifier = Modifier
+                    .requiredHeightIn(min = ActionLabelMinHeight)
+                    .padding(bottom = ActionLabelBottomPadding)
+            ) {
+                CompositionLocalProvider(
+                    LocalContentColor provides colors.actionContentColor,
+                    LocalTextStyle provides actionLabelTextStyle,
+                    content = it
+                )
+            }
+        }
+    }
+}
+
 /**
- * Tooltip defaults that contain default values for both [PlainTooltipBox] and RichTooltipBox
+ * Tooltip defaults that contain default values for both [PlainTooltipBox] and [RichTooltipBox]
  */
 @ExperimentalMaterial3Api
 object TooltipDefaults {
     /**
-     * The default [Shape] for the tooltip's container.
+     * The default [Shape] for a [PlainTooltipBox]'s container.
      */
     val plainTooltipContainerShape: Shape
         @Composable get() = PlainTooltipTokens.ContainerShape.toShape()
 
     /**
-     * The default [Color] for the tooltip's container.
+     * The default [Color] for a [PlainTooltipBox]'s container.
      */
     val plainTooltipContainerColor: Color
         @Composable get() = PlainTooltipTokens.ContainerColor.toColor()
 
     /**
-     * The default [color] for the content within the tooltip.
+     * The default [color] for the content within the [PlainTooltipBox].
      */
     val plainTooltipContentColor: Color
         @Composable get() = PlainTooltipTokens.SupportingTextColor.toColor()
+
+    /**
+     * The default [Shape] for a [RichTooltipBox]'s container.
+     */
+    val richTooltipContainerShape: Shape @Composable get() =
+        RichTooltipTokens.ContainerShape.toShape()
+
+    /**
+     * Method to create a [RichTooltipColors] for [RichTooltipBox]
+     * using [RichTooltipTokens] to obtain the default colors.
+     */
+    @Composable
+    fun richTooltipColors(
+        containerColor: Color = RichTooltipTokens.ContainerColor.toColor(),
+        contentColor: Color = RichTooltipTokens.SupportingTextColor.toColor(),
+        titleContentColor: Color = RichTooltipTokens.SubheadColor.toColor(),
+        actionContentColor: Color = RichTooltipTokens.ActionLabelTextColor.toColor(),
+    ): RichTooltipColors =
+        RichTooltipColors(
+            containerColor = containerColor,
+            contentColor = contentColor,
+            titleContentColor = titleContentColor,
+            actionContentColor = actionContentColor
+        )
+}
+
+@Stable
+@Immutable
+@ExperimentalMaterial3Api
+class RichTooltipColors(
+    val containerColor: Color,
+    val contentColor: Color,
+    val titleContentColor: Color,
+    val actionContentColor: Color
+) {
+    override fun equals(other: Any?): Boolean {
+        if (this === other) return true
+        if (other !is RichTooltipColors) return false
+
+        if (containerColor != other.containerColor) return false
+        if (contentColor != other.contentColor) return false
+        if (titleContentColor != other.titleContentColor) return false
+        if (actionContentColor != other.actionContentColor) return false
+
+        return true
+    }
+
+    override fun hashCode(): Int {
+        var result = containerColor.hashCode()
+        result = 31 * result + contentColor.hashCode()
+        result = 31 * result + titleContentColor.hashCode()
+        result = 31 * result + actionContentColor.hashCode()
+        return result
+    }
 }
 
 private class PlainTooltipPositionProvider(
@@ -266,6 +442,47 @@
     }
 }
 
+private data class RichTooltipPositionProvider(
+    val tooltipAnchorPadding: Int
+) : PopupPositionProvider {
+    override fun calculatePosition(
+        anchorBounds: IntRect,
+        windowSize: IntSize,
+        layoutDirection: LayoutDirection,
+        popupContentSize: IntSize
+    ): IntOffset {
+        var x = anchorBounds.right
+        // Try to shift it to the left of the anchor
+        // if the tooltip would collide with the right side of the screen
+        if (x + popupContentSize.width > windowSize.width) {
+            x = anchorBounds.left - popupContentSize.width
+            // Center if it'll also collide with the left side of the screen
+            if (x < 0) x = anchorBounds.left + (anchorBounds.width - popupContentSize.width) / 2
+        }
+
+        // Tooltip prefers to be above the anchor,
+        // but if this causes the tooltip to overlap with the anchor
+        // then we place it below the anchor
+        var y = anchorBounds.top - popupContentSize.height - tooltipAnchorPadding
+        if (y < 0)
+            y = anchorBounds.bottom + tooltipAnchorPadding
+        return IntOffset(x, y)
+    }
+}
+
+private fun Modifier.textVerticalPadding(
+    subheadExists: Boolean,
+    actionExists: Boolean
+): Modifier {
+    return if (!subheadExists && !actionExists) {
+        this.padding(vertical = PlainTooltipVerticalPadding)
+    } else {
+        this
+            .paddingFromBaseline(top = HeightFromSubheadToTextFirstLine)
+            .padding(bottom = TextBottomPadding)
+    }
+}
+
 private fun Modifier.animateTooltip(
     showTooltip: Boolean
 ): Modifier = composed(
@@ -336,70 +553,189 @@
 
 /**
  * The state that is associated with an instance of a tooltip.
- * Each instance of tooltips should have its own [TooltipState]
- * while will be used to synchronize the tooltips shown.
+ * Each instance of tooltips should have its own [TooltipState] it
+ * will be used to synchronize the tooltips shown via [TooltipSync].
  */
 @Stable
 @ExperimentalMaterial3Api
-class TooltipState {
+internal sealed interface TooltipState {
     /**
      * [Boolean] that will be used to update the visibility
      * state of the associated tooltip.
      */
-    var isVisible by mutableStateOf(false)
-        private set
+    val isVisible: Boolean
 
     /**
      * Show the tooltip associated with the current [TooltipState].
+     * When this method is called all of the other tooltips currently
+     * being shown will dismiss.
      */
-    suspend fun show() { show(this) }
+    suspend fun show()
 
     /**
      * Dismiss the tooltip associated with
      * this [TooltipState] if it's currently being shown.
      */
-    suspend fun dismiss() {
-        if (this == mutexOwner)
-            dismissCurrentTooltip()
+    suspend fun dismiss()
+}
+
+/**
+ * The [TooltipState] that should be used with [RichTooltipBox]
+ */
+@Stable
+@ExperimentalMaterial3Api
+class RichTooltipState : TooltipState {
+    /**
+     * [Boolean] that will be used to update the visibility
+     * state of the associated tooltip.
+     */
+    override var isVisible: Boolean by mutableStateOf(false)
+        internal set
+
+    /**
+     * If isPersistent is true, then the tooltip will only be dismissed when the user clicks
+     * outside the bounds of the tooltip or if [TooltipState.dismiss] is called. When isPersistent
+     * is false, the tooltip will dismiss after a short duration. If an action composable is
+     * provided to the [RichTooltipBox] that the [RichTooltipState] is associated with, then the
+     * isPersistent will be set to true.
+     */
+    internal var isPersistent: Boolean by mutableStateOf(false)
+
+    /**
+     * Show the tooltip associated with the current [RichTooltipState].
+     * It will persist or dismiss after a short duration depending on [isPersistent].
+     * When this method is called, all of the other tooltips currently
+     * being shown will dismiss.
+     */
+    override suspend fun show() {
+        TooltipSync.show(
+            state = this,
+            persistent = isPersistent
+        )
     }
 
     /**
-     * Companion object used to synchronize
-     * multiple [TooltipState]s, ensuring that there will
-     * only be one tooltip shown on the screen at any given time.
+     * Dismiss the tooltip associated with
+     * this [RichTooltipState] if it's currently being shown.
      */
-    private companion object {
-        val mutatorMutex: MutatorMutex = MutatorMutex()
-        var mutexOwner: TooltipState? = null
+    override suspend fun dismiss() {
+        TooltipSync.dismissCurrentTooltip(this)
+    }
+}
 
-        /**
-         * Shows the tooltip associated with [TooltipState],
-         * it dismisses any tooltip currently being shown.
-         */
-        suspend fun show(
-            state: TooltipState
-        ) {
-            mutatorMutex.mutate(MutatePriority.Default) {
-                try {
-                    mutexOwner = state
-                    // show the tooltip associated with the
-                    // tooltipState until dismissal or timeout.
+/**
+ * The [TooltipState] that should be used with [RichTooltipBox]
+ */
+@Stable
+@ExperimentalMaterial3Api
+class PlainTooltipState : TooltipState {
+    /**
+     * [Boolean] that will be used to update the visibility
+     * state of the associated tooltip.
+     */
+    override var isVisible by mutableStateOf(false)
+        internal set
+
+    /**
+     * Show the tooltip associated with the current [PlainTooltipState].
+     * It will dismiss after a short duration. When this method is called,
+     * all of the other tooltips currently being shown will dismiss.
+     */
+    override suspend fun show() {
+        TooltipSync.show(
+            state = this,
+            persistent = false
+        )
+    }
+
+    /**
+     * Dismiss the tooltip associated with
+     * this [PlainTooltipState] if it's currently being shown.
+     */
+    override suspend fun dismiss() {
+        TooltipSync.dismissCurrentTooltip(this)
+    }
+}
+
+/**
+ * Object used to synchronize
+ * multiple [TooltipState]s, ensuring that there will
+ * only be one tooltip shown on the screen at any given time.
+ */
+@Stable
+@ExperimentalMaterial3Api
+private object TooltipSync {
+    val mutatorMutex: MutatorMutex = MutatorMutex()
+    var mutexOwner: TooltipState? = null
+
+    /**
+     * Shows the tooltip associated with [TooltipState],
+     * it dismisses any tooltip currently being shown.
+     */
+    suspend fun show(
+        state: TooltipState,
+        persistent: Boolean
+    ) {
+        val runBlock: suspend () -> Unit
+        val cleanUp: () -> Unit
+
+        when (state) {
+            is PlainTooltipState -> {
+                /**
+                 * Show associated tooltip for [TooltipDuration] amount of time.
+                 */
+                runBlock = {
                     state.isVisible = true
                     delay(TooltipDuration)
-                } finally {
-                    mutexOwner = null
-                    // timeout or cancellation has occurred
-                    // and we close out the current tooltip.
-                    state.isVisible = false
                 }
+                /**
+                 * When the mutex is taken, we just dismiss the associated tooltip.
+                 */
+                cleanUp = { state.isVisible = false }
+            }
+            is RichTooltipState -> {
+                /**
+                 * Show associated tooltip for [TooltipDuration] amount of time
+                 * or until tooltip is explicitly dismissed depending on [persistent].
+                 */
+                runBlock = {
+                    if (persistent) {
+                        suspendCancellableCoroutine<Unit> {
+                            state.isVisible = true
+                        }
+                    } else {
+                        state.isVisible = true
+                        delay(TooltipDuration)
+                    }
+                }
+                /**
+                 * When the mutex is taken, we just dismiss the associated tooltip.
+                 */
+                cleanUp = { state.isVisible = false }
             }
         }
 
-        /**
-         * Dismisses the tooltip currently
-         * being shown by freeing up the lock.
-         */
-        suspend fun dismissCurrentTooltip() {
+        mutatorMutex.mutate(MutatePriority.Default) {
+            try {
+                mutexOwner = state
+                runBlock()
+            } finally {
+                mutexOwner = null
+                // timeout or cancellation has occurred
+                // and we close out the current tooltip.
+                cleanUp()
+            }
+        }
+    }
+
+    /**
+     * Dismisses the tooltip currently
+     * being shown by freeing up the lock.
+     */
+    suspend fun dismissCurrentTooltip(
+        state: TooltipState
+    ) {
+        if (state == mutexOwner) {
             mutatorMutex.mutate(MutatePriority.UserInput) {
                 /* Do nothing, we're just freeing up the mutex */
             }
@@ -411,8 +747,18 @@
 internal val TooltipMinHeight = 24.dp
 internal val TooltipMinWidth = 40.dp
 private val PlainTooltipMaxWidth = 200.dp
-private val PlainTooltipContentPadding = PaddingValues(8.dp, 4.dp)
+private val PlainTooltipVerticalPadding = 4.dp
+private val PlainTooltipHorizontalPadding = 8.dp
+private val PlainTooltipContentPadding =
+    PaddingValues(PlainTooltipHorizontalPadding, PlainTooltipVerticalPadding)
+private val RichTooltipMaxWidth = 320.dp
+internal val RichTooltipHorizontalPadding = 16.dp
+private val HeightToSubheadFirstLine = 28.dp
+private val HeightFromSubheadToTextFirstLine = 24.dp
+private val TextBottomPadding = 16.dp
+private val ActionLabelMinHeight = 36.dp
+private val ActionLabelBottomPadding = 8.dp
 internal const val TooltipDuration = 1500L
-// No specification for fade in and fade out duration, so aligning it with the behavior for snackbar
+// No specification for fade in and fade out duration, so aligning it with the behavior for snack bar
 private const val TooltipFadeInDuration = 150
 private const val TooltipFadeOutDuration = 75
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DateInputModalTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DateInputModalTokens.kt
index 4742993..8201608 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DateInputModalTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DateInputModalTokens.kt
@@ -1,4 +1,19 @@
-// VERSION: v0_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.
+ */
+// VERSION: v0_157
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -17,5 +32,5 @@
     val HeaderHeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val HeaderHeadlineFont = TypographyKeyTokens.HeadlineLarge
     val HeaderSupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val HeaderSupportingTextFont = TypographyKeyTokens.LabelMedium
+    val HeaderSupportingTextFont = TypographyKeyTokens.LabelLarge
 }
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
index f84cc5d..fbb8baf 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/DatePickerModalTokens.kt
@@ -1,4 +1,19 @@
-// VERSION: v0_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.
+ */
+// VERSION: v0_157
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -8,14 +23,14 @@
 internal object DatePickerModalTokens {
     val ContainerColor = ColorSchemeKeyTokens.Surface
     val ContainerElevation = ElevationTokens.Level3
-    val ContainerHeight = 512.0.dp
+    val ContainerHeight = 568.0.dp
     val ContainerShape = ShapeKeyTokens.CornerExtraLarge
     val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
-    val ContainerWidth = 328.0.dp
+    val ContainerWidth = 360.0.dp
     val DateContainerHeight = 40.0.dp
     val DateContainerShape = ShapeKeyTokens.CornerFull
     val DateContainerWidth = 40.0.dp
-    val DateLabelTextFont = TypographyKeyTokens.BodySmall
+    val DateLabelTextFont = TypographyKeyTokens.BodyLarge
     val DateSelectedContainerColor = ColorSchemeKeyTokens.Primary
     val DateSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimary
     val DateStateLayerHeight = 40.0.dp
@@ -26,11 +41,11 @@
     val DateTodayLabelTextColor = ColorSchemeKeyTokens.Primary
     val DateUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface
     val HeaderContainerHeight = 120.0.dp
-    val HeaderContainerWidth = 328.0.dp
+    val HeaderContainerWidth = 360.0.dp
     val HeaderHeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val HeaderHeadlineFont = TypographyKeyTokens.HeadlineLarge
     val HeaderSupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
-    val HeaderSupportingTextFont = TypographyKeyTokens.LabelMedium
+    val HeaderSupportingTextFont = TypographyKeyTokens.LabelLarge
     val RangeSelectionActiveIndicatorContainerColor = ColorSchemeKeyTokens.PrimaryContainer
     val RangeSelectionActiveIndicatorContainerHeight = 40.0.dp
     val RangeSelectionActiveIndicatorContainerShape = ShapeKeyTokens.CornerFull
@@ -42,7 +57,7 @@
     val RangeSelectionMonthSubheadColor = ColorSchemeKeyTokens.OnSurfaceVariant
     val RangeSelectionMonthSubheadFont = TypographyKeyTokens.TitleSmall
     val WeekdaysLabelTextColor = ColorSchemeKeyTokens.OnSurface
-    val WeekdaysLabelTextFont = TypographyKeyTokens.BodySmall
+    val WeekdaysLabelTextFont = TypographyKeyTokens.BodyLarge
     val SelectionYearContainerHeight = 36.0.dp
     val SelectionYearContainerWidth = 72.0.dp
     val SelectionYearLabelTextFont = TypographyKeyTokens.BodyLarge
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PlainTooltipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PlainTooltipTokens.kt
index f26a370..37b4364 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PlainTooltipTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/PlainTooltipTokens.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2022 The Android Open Source Project
+ * Copyright 2023 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.kt
new file mode 100644
index 0000000..9df3345
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/RichTooltipTokens.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.compose.material3.tokens
+
+internal object RichTooltipTokens {
+    val ActionFocusLabelTextColor = ColorSchemeKeyTokens.Primary
+    val ActionHoverLabelTextColor = ColorSchemeKeyTokens.Primary
+    val ActionLabelTextColor = ColorSchemeKeyTokens.Primary
+    val ActionLabelTextFont = TypographyKeyTokens.LabelLarge
+    val ActionPressedLabelTextColor = ColorSchemeKeyTokens.Primary
+    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerElevation = ElevationTokens.Level2
+    val ContainerShape = ShapeKeyTokens.CornerSmall
+    val ContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
+    val SubheadColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val SubheadFont = TypographyKeyTokens.TitleSmall
+    val SupportingTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val SupportingTextFont = TypographyKeyTokens.BodyMedium
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.kt
new file mode 100644
index 0000000..ecc53a4
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/SheetBottomTokens.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.
+ */
+// VERSION: v0_126
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object SheetBottomTokens {
+    val DockedContainerColor = ColorSchemeKeyTokens.Surface
+    val DockedContainerShape = ShapeKeyTokens.CornerExtraLargeTop
+    val DockedContainerSurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
+    val DockedDragHandleColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val DockedDragHandleHeight = 4.0.dp
+    const val DockedDragHandleOpacity = 0.4f
+    val DockedDragHandleWidth = 32.0.dp
+    val DockedMinimizedContainerShape = ShapeKeyTokens.CornerNone
+    val DockedModalContainerElevation = ElevationTokens.Level1
+    val DockedStandardContainerElevation = ElevationTokens.Level1
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.kt
new file mode 100644
index 0000000..335414e
--- /dev/null
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/TimePickerTokens.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.
+ */
+// VERSION: v0_157
+// GENERATED CODE - DO NOT MODIFY BY HAND
+
+package androidx.compose.material3.tokens
+
+import androidx.compose.ui.unit.dp
+
+internal object TimePickerTokens {
+    val ClockDialColor = ColorSchemeKeyTokens.SurfaceVariant
+    val ClockDialContainerSize = 256.0.dp
+    val ClockDialLabelTextFont = TypographyKeyTokens.BodyLarge
+    val ClockDialSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimary
+    val ClockDialSelectorCenterContainerColor = ColorSchemeKeyTokens.Primary
+    val ClockDialSelectorCenterContainerShape = ShapeKeyTokens.CornerFull
+    val ClockDialSelectorCenterContainerSize = 8.0.dp
+    val ClockDialSelectorHandleContainerColor = ColorSchemeKeyTokens.Primary
+    val ClockDialSelectorHandleContainerShape = ShapeKeyTokens.CornerFull
+    val ClockDialSelectorHandleContainerSize = 48.0.dp
+    val ClockDialSelectorTrackContainerColor = ColorSchemeKeyTokens.Primary
+    val ClockDialSelectorTrackContainerWidth = 2.0.dp
+    val ClockDialShape = ShapeKeyTokens.CornerFull
+    val ClockDialUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val ContainerColor = ColorSchemeKeyTokens.Surface
+    val ContainerElevation = ElevationTokens.Level3
+    val ContainerShape = ShapeKeyTokens.CornerExtraLarge
+    val HeadlineColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val HeadlineFont = TypographyKeyTokens.LabelMedium
+    val PeriodSelectorContainerShape = ShapeKeyTokens.CornerSmall
+    val PeriodSelectorHorizontalContainerHeight = 38.0.dp
+    val PeriodSelectorHorizontalContainerWidth = 216.0.dp
+    val PeriodSelectorLabelTextFont = TypographyKeyTokens.TitleMedium
+    val PeriodSelectorOutlineColor = ColorSchemeKeyTokens.Outline
+    val PeriodSelectorOutlineWidth = 1.0.dp
+    val PeriodSelectorSelectedContainerColor = ColorSchemeKeyTokens.TertiaryContainer
+    val PeriodSelectorSelectedFocusLabelTextColor = ColorSchemeKeyTokens.OnTertiaryContainer
+    val PeriodSelectorSelectedHoverLabelTextColor = ColorSchemeKeyTokens.OnTertiaryContainer
+    val PeriodSelectorSelectedLabelTextColor = ColorSchemeKeyTokens.OnTertiaryContainer
+    val PeriodSelectorSelectedPressedLabelTextColor = ColorSchemeKeyTokens.OnTertiaryContainer
+    val PeriodSelectorUnselectedFocusLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PeriodSelectorUnselectedHoverLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PeriodSelectorUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PeriodSelectorUnselectedPressedLabelTextColor = ColorSchemeKeyTokens.OnSurfaceVariant
+    val PeriodSelectorVerticalContainerHeight = 80.0.dp
+    val PeriodSelectorVerticalContainerWidth = 52.0.dp
+    val SurfaceTintLayerColor = ColorSchemeKeyTokens.SurfaceTint
+    val TimeSelector24HVerticalContainerWidth = 114.0.dp
+    val TimeSelectorContainerHeight = 80.0.dp
+    val TimeSelectorContainerShape = ShapeKeyTokens.CornerSmall
+    val TimeSelectorContainerWidth = 96.0.dp
+    val TimeSelectorLabelTextFont = TypographyKeyTokens.DisplayLarge
+    val TimeSelectorSelectedContainerColor = ColorSchemeKeyTokens.PrimaryContainer
+    val TimeSelectorSelectedFocusLabelTextColor = ColorSchemeKeyTokens.OnPrimaryContainer
+    val TimeSelectorSelectedHoverLabelTextColor = ColorSchemeKeyTokens.OnPrimaryContainer
+    val TimeSelectorSelectedLabelTextColor = ColorSchemeKeyTokens.OnPrimaryContainer
+    val TimeSelectorSelectedPressedLabelTextColor = ColorSchemeKeyTokens.OnPrimaryContainer
+    val TimeSelectorSeparatorColor = ColorSchemeKeyTokens.OnSurface
+    val TimeSelectorSeparatorFont = TypographyKeyTokens.DisplayLarge
+    val TimeSelectorUnselectedContainerColor = ColorSchemeKeyTokens.SurfaceVariant
+    val TimeSelectorUnselectedFocusLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val TimeSelectorUnselectedHoverLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val TimeSelectorUnselectedLabelTextColor = ColorSchemeKeyTokens.OnSurface
+    val TimeSelectorUnselectedPressedLabelTextColor = ColorSchemeKeyTokens.OnSurface
+}
\ No newline at end of file
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/CalendarModel.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/CalendarModel.desktop.kt
index 7d35284..5d4556e7 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/CalendarModel.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/CalendarModel.desktop.kt
@@ -21,10 +21,10 @@
 import java.util.Locale
 
 /**
- * Creates a [CalendarModel] to be used by the date picker.
+ * Returns a [CalendarModel] to be used by the date picker.
  */
 @ExperimentalMaterial3Api
-internal actual fun createCalendarModel(): CalendarModel = LegacyCalendarModelImpl()
+internal actual fun CalendarModel(): CalendarModel = LegacyCalendarModelImpl()
 
 /**
  * Formats a UTC timestamp into a string with a given date format skeleton.
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
index 132d0b1..0421c3b 100644
--- a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/Strings.desktop.kt
@@ -48,15 +48,26 @@
         Strings.DatePickerNavigateToYearDescription -> "Navigate to year %1$"
         Strings.DatePickerHeadlineDescription -> "Current selection: %1$"
         Strings.DatePickerNoSelectionDescription -> "None"
+        Strings.DatePickerTodayDescription -> "Today"
         Strings.DateInputTitle -> "Select date"
         Strings.DateInputHeadline -> "Entered date"
         Strings.DateInputLabel -> "Date"
         Strings.DateInputHeadlineDescription -> "Entered date: %1$"
-        Strings.DateInputNoInputHeadlineDescription -> "None"
+        Strings.DateInputNoInputDescription -> "None"
         Strings.DateInputInvalidNotAllowed -> "Date not allowed: %1$"
         Strings.DateInputInvalidForPattern -> "Date does not match expected pattern: %1$"
         Strings.DateInputInvalidYearRange -> "Date out of expected year range %1$ - %2$"
+        Strings.DatePickerSwitchToCalendarMode -> "Switch to calendar input mode"
+        Strings.DatePickerSwitchToInputMode -> "Switch to text input mode"
         Strings.TooltipLongPressLabel -> "Show tooltip"
+        Strings.TimePickerAM -> "AM"
+        Strings.TimePickerPM -> "PM"
+        Strings.TimePickerPeriodToggle -> "Select AM or PM"
+        Strings.TimePickerMinuteSelection -> "Select minutes"
+        Strings.TimePickerHourSelection -> "Select hour"
+        Strings.TimePickerHourSuffix -> "%1$ o\\'clock"
+        Strings.TimePickerMinuteSuffix -> "%1$ minutes"
+        Strings.TimePicker24HourSuffix -> "%1$ hours"
         else -> ""
     }
 }
diff --git a/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/TimeFormat.desktop.kt b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/TimeFormat.desktop.kt
new file mode 100644
index 0000000..6f365ff
--- /dev/null
+++ b/compose/material3/material3/src/desktopMain/kotlin/androidx/compose/material/TimeFormat.desktop.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.runtime.Composable
+import androidx.compose.runtime.ReadOnlyComposable
+
+internal actual val is24HourFormat: Boolean
+    @Composable
+    @ReadOnlyComposable get() = false
\ No newline at end of file
diff --git a/compose/runtime/runtime-livedata/build.gradle b/compose/runtime/runtime-livedata/build.gradle
index 8e02589..d9d8529 100644
--- a/compose/runtime/runtime-livedata/build.gradle
+++ b/compose/runtime/runtime-livedata/build.gradle
@@ -30,7 +30,7 @@
     implementation(libs.kotlinStdlib)
 
     api(project(":compose:runtime:runtime"))
-    api("androidx.lifecycle:lifecycle-livedata:2.2.0")
+    api(project(":lifecycle:lifecycle-livedata"))
     api(projectOrArtifact(":lifecycle:lifecycle-runtime"))
     implementation("androidx.compose.ui:ui:1.2.1")
 
diff --git a/compose/runtime/runtime/api/current.txt b/compose/runtime/runtime/api/current.txt
index 20f83f5eb..71912fe 100644
--- a/compose/runtime/runtime/api/current.txt
+++ b/compose/runtime/runtime/api/current.txt
@@ -97,6 +97,12 @@
   @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ComposeCompilerApi {
   }
 
+  public interface ComposeNodeLifecycleCallback {
+    method public void onDeactivate();
+    method public void onRelease();
+    method public void onReuse();
+  }
+
   public sealed interface Composer {
     method @androidx.compose.runtime.ComposeCompilerApi public <V, T> void apply(V? value, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(Object? value);
@@ -113,7 +119,7 @@
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
     method @androidx.compose.runtime.ComposeCompilerApi public void deactivateToEndGroup(boolean changed);
     method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
-    method public void disableSourceInformation();
+    method @org.jetbrains.annotations.TestOnly public void disableSourceInformation();
     method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
diff --git a/compose/runtime/runtime/api/public_plus_experimental_current.txt b/compose/runtime/runtime/api/public_plus_experimental_current.txt
index 20810fc..291f5f9 100644
--- a/compose/runtime/runtime/api/public_plus_experimental_current.txt
+++ b/compose/runtime/runtime/api/public_plus_experimental_current.txt
@@ -102,6 +102,12 @@
   @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ComposeCompilerApi {
   }
 
+  public interface ComposeNodeLifecycleCallback {
+    method public void onDeactivate();
+    method public void onRelease();
+    method public void onReuse();
+  }
+
   public sealed interface Composer {
     method @androidx.compose.runtime.ComposeCompilerApi public <V, T> void apply(V? value, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block);
     method @androidx.compose.runtime.InternalComposeApi public androidx.compose.runtime.CompositionContext buildContext();
@@ -120,7 +126,7 @@
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
     method @androidx.compose.runtime.ComposeCompilerApi public void deactivateToEndGroup(boolean changed);
     method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
-    method public void disableSourceInformation();
+    method @org.jetbrains.annotations.TestOnly public void disableSourceInformation();
     method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index 3cc2fcc..f0329cb 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -99,6 +99,12 @@
   @kotlin.annotation.Target(allowedTargets={kotlin.annotation.AnnotationTarget.CLASS, kotlin.annotation.AnnotationTarget.FUNCTION, kotlin.annotation.AnnotationTarget.PROPERTY, kotlin.annotation.AnnotationTarget.TYPEALIAS}) public @interface ComposeCompilerApi {
   }
 
+  public interface ComposeNodeLifecycleCallback {
+    method public void onDeactivate();
+    method public void onRelease();
+    method public void onReuse();
+  }
+
   public sealed interface Composer {
     method @androidx.compose.runtime.ComposeCompilerApi public <V, T> void apply(V? value, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit> block);
     method @androidx.compose.runtime.ComposeCompilerApi public boolean changed(Object? value);
@@ -115,7 +121,7 @@
     method @androidx.compose.runtime.ComposeCompilerApi public <T> void createNode(kotlin.jvm.functions.Function0<? extends T> factory);
     method @androidx.compose.runtime.ComposeCompilerApi public void deactivateToEndGroup(boolean changed);
     method @androidx.compose.runtime.ComposeCompilerApi public void disableReusing();
-    method public void disableSourceInformation();
+    method @org.jetbrains.annotations.TestOnly public void disableSourceInformation();
     method @androidx.compose.runtime.ComposeCompilerApi public void enableReusing();
     method @androidx.compose.runtime.ComposeCompilerApi public void endDefaults();
     method @androidx.compose.runtime.ComposeCompilerApi public void endMovableGroup();
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
index ce6ea33..dd3b5c1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composables.kt
@@ -290,9 +290,7 @@
     } else {
         currentComposer.useNode()
     }
-    currentComposer.disableReusing()
     Updater<T>(currentComposer).update()
-    currentComposer.enableReusing()
     currentComposer.endNode()
 }
 
@@ -371,9 +369,7 @@
     } else {
         currentComposer.useNode()
     }
-    currentComposer.disableReusing()
     Updater<T>(currentComposer).update()
-    currentComposer.enableReusing()
     content()
     currentComposer.endNode()
 }
@@ -464,9 +460,7 @@
     } else {
         currentComposer.useNode()
     }
-    currentComposer.disableReusing()
     Updater<T>(currentComposer).update()
-    currentComposer.enableReusing()
     SkippableUpdater<T>(currentComposer).skippableUpdate()
     currentComposer.startReplaceableGroup(0x7ab4aae9)
     content()
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeNodeLifecycleCallback.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeNodeLifecycleCallback.kt
new file mode 100644
index 0000000..55377e1
--- /dev/null
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/ComposeNodeLifecycleCallback.kt
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.runtime
+
+/**
+ * Observes lifecycle of the node emitted with [ReusableComposeNode] or [ComposeNode] inside
+ * [ReusableContentHost] and [ReusableContent].
+ *
+ * The [ReusableContentHost] introduces the concept of reusing (or recycling) nodes, as well as
+ * deactivating parts of composition, while keeping the nodes around to reuse common structures
+ * in the next iteration. In this state, [RememberObserver] is not sufficient to track lifetime
+ * of data associated with reused node, as deactivated or reused parts of composition is disposed.
+ *
+ * These callbacks track intermediate states of the node in reusable groups for managing
+ * data contained inside reusable nodes or associated with them (e.g. subcomposition).
+ *
+ * Important: the runtime only supports node implementation of this interface.
+ */
+interface ComposeNodeLifecycleCallback {
+    /**
+     * Invoked when the node was reused in the composition.
+     * Consumers might use this callback to reset data associated with the previous content, as
+     * it is no longer valid.
+     */
+    fun onReuse()
+
+    /**
+     * Invoked when the group containing the node was deactivated.
+     * This happens when the content of [ReusableContentHost] is deactivated.
+     *
+     * The node will not be reused in this recompose cycle, but might be reused or released in
+     * the future. Consumers might use this callback to release expensive resources or stop
+     * continuous process that was dependent on the node being used in composition.
+     *
+     * If the node is reused, [onReuse] will be called again to prepare the node for reuse.
+     * Similarly, [onRelease] will indicate that deactivated node will never be reused again.
+     */
+    fun onDeactivate()
+
+    /**
+     * Invoked when the node exits the composition entirely and won't be reused again.
+     * All intermediate data related to the node can be safely disposed.
+     */
+    fun onRelease()
+}
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
index 9d387be..f0f620f 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composer.kt
@@ -79,6 +79,16 @@
      * notifications are sent.
      */
     fun sideEffect(effect: () -> Unit)
+
+    /**
+     * The [ComposeNodeLifecycleCallback] is being deactivated.
+     */
+    fun deactivating(instance: ComposeNodeLifecycleCallback)
+
+    /**
+     * The [ComposeNodeLifecycleCallback] is being released.
+     */
+    fun releasing(instance: ComposeNodeLifecycleCallback)
 }
 
 /**
@@ -1059,6 +1069,15 @@
     val composition: ControlledComposition
         @TestOnly get
 
+    /**
+     * Disable the collection of source information, that may introduce groups to store the source
+     * information, in order to be able to more accurately calculate the actual number of groups a
+     * composable function generates in a release build.
+     *
+     * This function is only safe to call in a test and will produce incorrect composition results
+     * if called on a composer not under test.
+     */
+    @TestOnly
     fun disableSourceInformation()
 
     companion object {
@@ -1615,7 +1634,14 @@
     override fun useNode() {
         validateNodeExpected()
         runtimeCheck(!inserting) { "useNode() called while inserting" }
-        recordDown(reader.node)
+        val node = reader.node
+        recordDown(node)
+
+        if (reusing && node is ComposeNodeLifecycleCallback) {
+            recordApplierOperation { applier, _, _ ->
+                (applier.current as ComposeNodeLifecycleCallback).onReuse()
+            }
+        }
     }
 
     /**
@@ -2744,6 +2770,14 @@
             val start = reader.currentGroup
             val end = reader.currentEnd
             for (group in start until end) {
+                if (reader.isNode(group)) {
+                    val node = reader.node(group)
+                    if (node is ComposeNodeLifecycleCallback) {
+                        record { _, _, rememberManager ->
+                            rememberManager.deactivating(node)
+                        }
+                    }
+                }
                 reader.forEachData(group) { index, data ->
                     when (data) {
                         is RememberObserver -> {
@@ -4171,18 +4205,20 @@
 
     // To ensure this order, we call `enters` as a pre-order traversal
     // of the group tree, and then call `leaves` in the inverse order.
-
     for (slot in groupSlots()) {
-        when (slot) {
-            is RememberObserver -> {
-                rememberManager.forgetting(slot)
-            }
-            is RecomposeScopeImpl -> {
-                val composition = slot.composition
-                if (composition != null) {
-                    composition.pendingInvalidScopes = true
-                    slot.release()
-                }
+        // even that in the documentation we claim ComposeNodeLifecycleCallback should be only
+        // implemented on the nodes we do not really enforce it here as doing so will be expensive.
+        if (slot is ComposeNodeLifecycleCallback) {
+            rememberManager.releasing(slot)
+        }
+        if (slot is RememberObserver) {
+            rememberManager.forgetting(slot)
+        }
+        if (slot is RecomposeScopeImpl) {
+            val composition = slot.composition
+            if (composition != null) {
+                composition.pendingInvalidScopes = true
+                slot.release()
             }
         }
     }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
index 0f27069..165ab21 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Composition.kt
@@ -622,6 +622,7 @@
                         }
                         applier.clear()
                         manager.dispatchRememberObservers()
+                        manager.dispatchNodeCallbacks()
                     }
                     manager.dispatchAbandons()
                 }
@@ -792,6 +793,7 @@
             writer.removeCurrentGroup(manager)
         }
         manager.dispatchRememberObservers()
+        manager.dispatchNodeCallbacks()
     }
 
     private fun applyChangesInLocked(changes: MutableList<Change>) {
@@ -816,6 +818,7 @@
             // that implement RememberObserver receive onRemembered before a side effect
             // that captured it and operates on it can run.
             manager.dispatchRememberObservers()
+            manager.dispatchNodeCallbacks()
             manager.dispatchSideEffects()
 
             if (pendingInvalidScopes) {
@@ -1033,9 +1036,6 @@
 
     /**
      * Helper for collecting remember observers for later strictly ordered dispatch.
-     *
-     * This includes support for the deprecated [RememberObserver] which should be
-     * removed with it.
      */
     private class RememberEventDispatcher(
         private val abandoning: MutableSet<RememberObserver>
@@ -1043,6 +1043,8 @@
         private val remembering = mutableListOf<RememberObserver>()
         private val forgetting = mutableListOf<RememberObserver>()
         private val sideEffects = mutableListOf<() -> Unit>()
+        private var deactivating: MutableList<ComposeNodeLifecycleCallback>? = null
+        private var releasing: MutableList<ComposeNodeLifecycleCallback>? = null
 
         override fun remembering(instance: RememberObserver) {
             forgetting.lastIndexOf(instance).let { index ->
@@ -1070,6 +1072,18 @@
             sideEffects += effect
         }
 
+        override fun deactivating(instance: ComposeNodeLifecycleCallback) {
+            (deactivating ?: mutableListOf<ComposeNodeLifecycleCallback>().also {
+                deactivating = it
+            }) += instance
+        }
+
+        override fun releasing(instance: ComposeNodeLifecycleCallback) {
+            (releasing ?: mutableListOf<ComposeNodeLifecycleCallback>().also {
+                releasing = it
+            }) += instance
+        }
+
         fun dispatchRememberObservers() {
             // Send forgets
             if (forgetting.isNotEmpty()) {
@@ -1117,6 +1131,32 @@
                 }
             }
         }
+
+        fun dispatchNodeCallbacks() {
+            val deactivating = deactivating
+            if (!deactivating.isNullOrEmpty()) {
+                trace("Compose:deactivations") {
+                    for (i in deactivating.size - 1 downTo 0) {
+                        val instance = deactivating[i]
+                        instance.onDeactivate()
+                    }
+                }
+                deactivating.clear()
+            }
+
+            val releasing = releasing
+            if (!releasing.isNullOrEmpty()) {
+                // note that in contrast with objects from `forgetting` we will invoke the callback
+                // even for objects being abandoned.
+                trace("Compose:releases") {
+                    for (i in releasing.size - 1 downTo 0) {
+                        val instance = releasing[i]
+                        instance.onRelease()
+                    }
+                }
+                releasing.clear()
+            }
+        }
     }
 }
 
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
index 69e4e27..625b369 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/CompositionReusingTests.kt
@@ -346,6 +346,349 @@
         expectChanges()
         revalidate()
     }
+
+    @Test
+    fun onReuseIsCalledWhenReusableContentKeyChanges() = compositionTest {
+        var reuseKey by mutableStateOf(0)
+        var onReuseCalls = 0
+        val onReuse: () -> Unit = {
+            onReuseCalls++
+        }
+
+        compose {
+            ReusableContent(reuseKey) {
+                Linear(onReuse = onReuse) { }
+            }
+        }
+
+        validate {
+            Linear {
+            }
+        }
+
+        assertEquals(0, onReuseCalls)
+
+        reuseKey++
+        expectChanges()
+        revalidate()
+
+        assertEquals(1, onReuseCalls)
+
+        reuseKey++
+        expectChanges()
+        revalidate()
+
+        assertEquals(2, onReuseCalls)
+    }
+
+    @Test
+    fun onReuseIsCalledBeforeSetter() = compositionTest {
+        var reuseKey by mutableStateOf(0)
+        var onReuseCalls = 0
+        val onReuseCallsWhenSetCalled = mutableListOf<Int>()
+        val onReuse: () -> Unit = {
+            onReuseCalls++
+        }
+        val onSet: () -> Unit = {
+            onReuseCallsWhenSetCalled.add(onReuseCalls)
+        }
+
+        compose {
+            ReusableContent(reuseKey) {
+                Linear(onReuse = onReuse, onSet = onSet) { }
+            }
+        }
+
+        validate {
+            Linear {
+            }
+        }
+
+        assertEquals(listOf(0), onReuseCallsWhenSetCalled)
+        onReuseCallsWhenSetCalled.clear()
+
+        reuseKey++
+        expectChanges()
+        revalidate()
+
+        assertEquals(listOf(1), onReuseCallsWhenSetCalled)
+    }
+
+    @Test
+    fun onReuseIsCalledInApplyStage() = compositionTest {
+        var reuseKey by mutableStateOf(0)
+        var compositionFinished = false
+        val onReuseCalls = mutableListOf<Boolean>()
+        val onReuse: () -> Unit = {
+            onReuseCalls.add(compositionFinished)
+        }
+
+        compose {
+            ReusableContent(reuseKey) {
+                Linear(onReuse = onReuse) { }
+            }
+            compositionFinished = true
+        }
+
+        validate {
+            Linear {
+            }
+        }
+
+        assertEquals(emptyList(), onReuseCalls)
+        compositionFinished = false
+
+        reuseKey++
+        expectChanges()
+        revalidate()
+
+        assertEquals(listOf(true), onReuseCalls)
+    }
+
+    @Test
+    fun onDeactivateIsCalledWhenReusableContentDeactivated() = compositionTest {
+        var active by mutableStateOf(true)
+        var onDeactivateCalls = 0
+        val onDeactivate: () -> Unit = {
+            onDeactivateCalls++
+        }
+
+        compose {
+            ReusableContentHost(active) {
+                ReusableContent(0) {
+                    Linear(onDeactivate = onDeactivate) { }
+                }
+            }
+        }
+
+        validate {
+            Linear {
+            }
+        }
+
+        assertEquals(0, onDeactivateCalls)
+
+        active = false
+        expectChanges()
+        revalidate()
+
+        assertEquals(1, onDeactivateCalls)
+
+        active = true
+        expectChanges()
+        revalidate()
+
+        assertEquals(1, onDeactivateCalls)
+    }
+
+    @Test
+    fun onReuseIsCalledBeforeSetterAfterDeactivation() = compositionTest {
+        var active by mutableStateOf(true)
+        var onReuseCalls = 0
+        val onReuseCallsWhenSetCalled = mutableListOf<Int>()
+        val onReuse: () -> Unit = {
+            onReuseCalls++
+        }
+        val onSet: () -> Unit = {
+            onReuseCallsWhenSetCalled.add(onReuseCalls)
+        }
+
+        compose {
+            ReusableContentHost(active) {
+                ReusableContent(0) {
+                    Linear(onReuse = onReuse, onSet = onSet) { }
+                }
+            }
+        }
+
+        validate {
+            Linear {
+            }
+        }
+
+        active = false
+
+        expectChanges()
+        revalidate()
+
+        active = true
+
+        expectChanges()
+        revalidate()
+
+        assertEquals(listOf(0, 1), onReuseCallsWhenSetCalled)
+    }
+
+    @Test
+    fun onReuseIsNotCalledWhenDisposed() = compositionTest {
+        var emit by mutableStateOf(true)
+        var onReuseCalls = 0
+        val onReuse: () -> Unit = {
+            onReuseCalls++
+        }
+
+        compose {
+            if (emit) {
+                ReusableContent(0) {
+                    Linear(onReuse = onReuse) { }
+                }
+            }
+        }
+
+        emit = false
+        expectChanges()
+
+        assertEquals(0, onReuseCalls)
+    }
+
+    @Test
+    fun onDeactivateIsCalledInApplyStage() = compositionTest {
+        var active by mutableStateOf(true)
+        var compositionFinished = false
+        val onDeactivateCalls = mutableListOf<Boolean>()
+        val onDeactivate: () -> Unit = {
+            onDeactivateCalls.add(compositionFinished)
+        }
+
+        compose {
+            ReusableContentHost(active) {
+                ReusableContent(0) {
+                    Linear(onDeactivate = onDeactivate) { }
+                }
+            }
+            if (!active) {
+                compositionFinished = true
+            }
+        }
+
+        active = false
+        expectChanges()
+
+        assertEquals(listOf(true), onDeactivateCalls)
+    }
+
+    @Test
+    fun onReleaseIsCalledWhenNodeIsRemoved() = compositionTest {
+        var emit by mutableStateOf(true)
+        var onReleaseCalls = 0
+        val onRelease: () -> Unit = {
+            onReleaseCalls++
+        }
+
+        compose {
+            if (emit) {
+                ReusableContent(0) {
+                    Linear(onRelease = onRelease) { }
+                }
+            }
+        }
+
+        emit = false
+        expectChanges()
+
+        assertEquals(1, onReleaseCalls)
+    }
+
+    @Test
+    fun onReleaseIsNotCalledOnReuse() = compositionTest {
+        var key by mutableStateOf(0)
+        var onReleaseCalls = 0
+        val onRelease: () -> Unit = {
+            onReleaseCalls++
+        }
+
+        compose {
+            ReusableContent(key) {
+                Linear(onRelease = onRelease) { }
+            }
+        }
+
+        key++
+        expectChanges()
+
+        assertEquals(0, onReleaseCalls)
+    }
+
+    @Test
+    fun onReleaseIsNotCalledWithReusableContentHost() = compositionTest {
+        var active by mutableStateOf(true)
+        var emit by mutableStateOf(true)
+        var onReleaseCalls = 0
+        val onRelease: () -> Unit = {
+            onReleaseCalls++
+        }
+
+        compose {
+            if (emit) {
+                ReusableContentHost(active) {
+                    Linear(onRelease = onRelease) { }
+                }
+            }
+        }
+
+        active = false
+        expectChanges()
+
+        assertEquals(0, onReleaseCalls)
+
+        emit = false
+        expectChanges()
+
+        assertEquals(1, onReleaseCalls)
+    }
+
+    @Test
+    fun onReleaseIsNotCalledWithMovableContentMovement() = compositionTest {
+        var wrap by mutableStateOf(true)
+        var onReleaseCalls = 0
+        val onRelease: () -> Unit = {
+            onReleaseCalls++
+        }
+
+        val movableContent = movableContentOf {
+            Linear(onRelease = onRelease) { }
+        }
+
+        compose {
+            if (wrap) {
+                ReusableContent(0) {
+                    movableContent()
+                }
+            } else {
+                movableContent()
+            }
+        }
+
+        wrap = false
+        expectChanges()
+
+        assertEquals(0, onReleaseCalls)
+    }
+
+    @Test
+    fun onReleaseIsCalledInApplyStage() = compositionTest {
+        var emit by mutableStateOf(true)
+        var compositionFinished = false
+        val onReleaseCalls = mutableListOf<Boolean>()
+        val onRelease: () -> Unit = {
+            onReleaseCalls.add(compositionFinished)
+        }
+
+        compose {
+            if (emit) {
+                ReusableContent(0) {
+                    Linear(onRelease = onRelease) { }
+                }
+            } else {
+                compositionFinished = true
+            }
+        }
+
+        emit = false
+        expectChanges()
+
+        assertEquals(listOf(true), onReleaseCalls)
+    }
 }
 
 private fun View.findTextWith(contains: String) =
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/Views.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/Views.kt
index 5f90678..1c332b0 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/Views.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/Views.kt
@@ -20,7 +20,10 @@
 import androidx.compose.runtime.ComposeNode
 import androidx.compose.runtime.ReusableComposeNode
 import androidx.compose.runtime.NonRestartableComposable
+import androidx.compose.runtime.ComposeNodeLifecycleCallback
+import androidx.compose.runtime.getValue
 import androidx.compose.runtime.key
+import androidx.compose.runtime.rememberUpdatedState
 
 @Composable
 fun <T : Any> Repeated(
@@ -45,6 +48,45 @@
 }
 
 @Composable
+fun Linear(
+    onReuse: () -> Unit = {},
+    onDeactivate: () -> Unit = {},
+    onRelease: () -> Unit = {},
+    onSet: () -> Unit = {},
+    content: @Composable () -> Unit
+) {
+    val currentOnReuse by rememberUpdatedState(onReuse)
+    val currentOnDeactivate by rememberUpdatedState(onDeactivate)
+    val currentOnRelease by rememberUpdatedState(onRelease)
+    ReusableComposeNode<View, ViewApplier>(
+        factory = {
+            object : View(), ComposeNodeLifecycleCallback {
+                init {
+                    name = "linear"
+                }
+
+                override fun onRelease() {
+                    currentOnRelease()
+                }
+
+                override fun onReuse() {
+                    currentOnReuse()
+                }
+
+                override fun onDeactivate() {
+                    currentOnDeactivate()
+                }
+            }
+        },
+        update = {
+            set(onSet) { onSet() }
+        },
+    ) {
+        content()
+    }
+}
+
+@Composable
 fun NonReusableLinear(content: @Composable () -> Unit) {
     ComposeNode<View, ViewApplier>(
         factory = { View().also { it.name = "linear" } },
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt
index afacfe3..64a36aa 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/PathMeasureTest.kt
@@ -41,9 +41,9 @@
 
         val tangent = pathMeasure.getTangent(distance * 0.5f)
 
-        Assert.assertEquals(50f, position.x)
-        Assert.assertEquals(50f, position.y)
-        Assert.assertEquals(0.707106f, tangent.x, 0.00001f)
-        Assert.assertEquals(0.707106f, tangent.y, 0.00001f)
+        Assert.assertEquals(50f, position.x, 0.0001f)
+        Assert.assertEquals(50f, position.y, 0.0001f)
+        Assert.assertEquals(0.707106f, tangent.x, 0.0001f)
+        Assert.assertEquals(0.707106f, tangent.y, 0.0001f)
     }
 }
\ No newline at end of file
diff --git a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/BitmapCapturingTest.kt b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/BitmapCapturingTest.kt
index 9f03269..df7537a 100644
--- a/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/BitmapCapturingTest.kt
+++ b/compose/ui/ui-test/src/androidAndroidTest/kotlin/androidx/compose/ui/test/BitmapCapturingTest.kt
@@ -47,6 +47,7 @@
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
 import kotlin.math.roundToInt
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -138,6 +139,7 @@
             .assertContainsColor(Color.Red)
     }
 
+    @Ignore // b/266737024
     @Test
     fun capturePopup_verifyBackground() {
         setContent {
diff --git a/compose/ui/ui-text-google-fonts/api/current.txt b/compose/ui/ui-text-google-fonts/api/current.txt
index 7ac97ea..5bcabac 100644
--- a/compose/ui/ui-text-google-fonts/api/current.txt
+++ b/compose/ui/ui-text-google-fonts/api/current.txt
@@ -15,12 +15,6 @@
   public static final class GoogleFont.Provider {
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, java.util.List<? extends java.util.List<byte[]>> certificates);
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, @ArrayRes int certificates);
-    field public static final androidx.compose.ui.text.googlefonts.GoogleFont.Provider.Companion Companion;
-  }
-
-  public static final class GoogleFont.Provider.Companion {
-    method public android.net.Uri getAllFontsListUri();
-    property public final android.net.Uri AllFontsListUri;
   }
 
   public final class GoogleFontKt {
diff --git a/compose/ui/ui-text-google-fonts/api/public_plus_experimental_current.txt b/compose/ui/ui-text-google-fonts/api/public_plus_experimental_current.txt
index 7ac97ea..5bcabac 100644
--- a/compose/ui/ui-text-google-fonts/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text-google-fonts/api/public_plus_experimental_current.txt
@@ -15,12 +15,6 @@
   public static final class GoogleFont.Provider {
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, java.util.List<? extends java.util.List<byte[]>> certificates);
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, @ArrayRes int certificates);
-    field public static final androidx.compose.ui.text.googlefonts.GoogleFont.Provider.Companion Companion;
-  }
-
-  public static final class GoogleFont.Provider.Companion {
-    method public android.net.Uri getAllFontsListUri();
-    property public final android.net.Uri AllFontsListUri;
   }
 
   public final class GoogleFontKt {
diff --git a/compose/ui/ui-text-google-fonts/api/restricted_current.txt b/compose/ui/ui-text-google-fonts/api/restricted_current.txt
index 7ac97ea..5bcabac 100644
--- a/compose/ui/ui-text-google-fonts/api/restricted_current.txt
+++ b/compose/ui/ui-text-google-fonts/api/restricted_current.txt
@@ -15,12 +15,6 @@
   public static final class GoogleFont.Provider {
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, java.util.List<? extends java.util.List<byte[]>> certificates);
     ctor public GoogleFont.Provider(String providerAuthority, String providerPackage, @ArrayRes int certificates);
-    field public static final androidx.compose.ui.text.googlefonts.GoogleFont.Provider.Companion Companion;
-  }
-
-  public static final class GoogleFont.Provider.Companion {
-    method public android.net.Uri getAllFontsListUri();
-    property public final android.net.Uri AllFontsListUri;
   }
 
   public final class GoogleFontKt {
diff --git a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
index 36d7450..5af4b91 100644
--- a/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
+++ b/compose/ui/ui-text-google-fonts/src/main/java/androidx/compose/ui/text/googlefonts/GoogleFont.kt
@@ -21,7 +21,6 @@
 
 import android.content.Context
 import android.graphics.Typeface
-import android.net.Uri
 import android.os.Handler
 import android.os.Looper
 import androidx.annotation.ArrayRes
@@ -81,6 +80,9 @@
  * To learn more about the features supported by Google Fonts, see
  * [Get Started with the Google Fonts for Android](https://developers.google.com/fonts/docs/android)
  *
+ * For a full list of fonts available on Android, see the
+ * [Google Fonts Directory For Android XML](https://fonts.gstatic.com/s/a/directory.xml).
+ *
  * @param name Name of a font on Google fonts, such as "Roboto" or "Open Sans"
  * @param bestEffort If besteffort is true and your query specifies a valid family name but the
  * requested width/weight/italic value is not supported Google Fonts will return the best match it
@@ -166,6 +168,7 @@
             if (providerAuthority != other.providerAuthority) return false
             if (providerPackage != other.providerPackage) return false
             if (certificates != other.certificates) return false
+            if (certificatesRes != other.certificatesRes) return false
 
             return true
         }
@@ -173,17 +176,10 @@
         override fun hashCode(): Int {
             var result = providerAuthority.hashCode()
             result = 31 * result + providerPackage.hashCode()
-            result = 31 * result + certificates.hashCode()
+            result = 31 * result + (certificates?.hashCode() ?: 0)
+            result = 31 * result + certificatesRes
             return result
         }
-
-        companion object {
-            /**
-             * Url with a canonical list of all Google Fonts that are currently supported on
-             * Android.
-             */
-            val AllFontsListUri: Uri = Uri.parse("https://fonts.gstatic.com/s/a/directory.xml")
-        }
     }
 }
 
@@ -368,7 +364,7 @@
         FAIL_REASON_FONT_LOAD_ERROR -> "Generic error loading font, for example variation " +
             "settings were not parsable"
         FAIL_REASON_FONT_NOT_FOUND -> "Font not found, please check availability on " +
-            "GoogleFont.Provider.AllFontsList: ${GoogleFont.Provider.AllFontsListUri}"
+            "GoogleFont.Provider.AllFontsList: https://fonts.gstatic.com/s/a/directory.xml"
         FAIL_REASON_FONT_UNAVAILABLE -> "The provider found the queried font, but it is " +
             "currently unavailable."
         FAIL_REASON_MALFORMED_QUERY -> "The given query was not supported by this provider."
diff --git a/compose/ui/ui-text/api/current.txt b/compose/ui/ui-text/api/current.txt
index c265249..d2dd899 100644
--- a/compose/ui/ui-text/api/current.txt
+++ b/compose/ui/ui-text/api/current.txt
@@ -710,11 +710,11 @@
     property public abstract int style;
     property public abstract androidx.compose.ui.text.font.FontWeight weight;
     field public static final androidx.compose.ui.text.font.Font.Companion Companion;
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   public static final class Font.Companion {
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   @Deprecated public static interface Font.ResourceLoader {
@@ -1357,15 +1357,15 @@
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
   }
 
-  public final class Hyphens {
+  @kotlin.jvm.JvmInline public final value class Hyphens {
     field public static final androidx.compose.ui.text.style.Hyphens.Companion Companion;
   }
 
   public static final class Hyphens.Companion {
-    method public androidx.compose.ui.text.style.Hyphens getAuto();
-    method public androidx.compose.ui.text.style.Hyphens getNone();
-    property public final androidx.compose.ui.text.style.Hyphens Auto;
-    property public final androidx.compose.ui.text.style.Hyphens None;
+    method public int getAuto();
+    method public int getNone();
+    property public final int Auto;
+    property public final int None;
   }
 
   @androidx.compose.runtime.Immutable public final class LineBreak {
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 04f88e9..c3c178e 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -772,11 +772,11 @@
     property public abstract int style;
     property public abstract androidx.compose.ui.text.font.FontWeight weight;
     field public static final androidx.compose.ui.text.font.Font.Companion Companion;
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   public static final class Font.Companion {
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   @Deprecated public static interface Font.ResourceLoader {
@@ -1425,15 +1425,15 @@
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
   }
 
-  public final class Hyphens {
+  @kotlin.jvm.JvmInline public final value class Hyphens {
     field public static final androidx.compose.ui.text.style.Hyphens.Companion Companion;
   }
 
   public static final class Hyphens.Companion {
-    method public androidx.compose.ui.text.style.Hyphens getAuto();
-    method public androidx.compose.ui.text.style.Hyphens getNone();
-    property public final androidx.compose.ui.text.style.Hyphens Auto;
-    property public final androidx.compose.ui.text.style.Hyphens None;
+    method public int getAuto();
+    method public int getNone();
+    property public final int Auto;
+    property public final int None;
   }
 
   @androidx.compose.runtime.Immutable public final class LineBreak {
diff --git a/compose/ui/ui-text/api/restricted_current.txt b/compose/ui/ui-text/api/restricted_current.txt
index c265249..d2dd899 100644
--- a/compose/ui/ui-text/api/restricted_current.txt
+++ b/compose/ui/ui-text/api/restricted_current.txt
@@ -710,11 +710,11 @@
     property public abstract int style;
     property public abstract androidx.compose.ui.text.font.FontWeight weight;
     field public static final androidx.compose.ui.text.font.Font.Companion Companion;
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   public static final class Font.Companion {
-    field public static final long MaximumAsyncTimeout = 15000L; // 0x3a98L
+    field public static final long MaximumAsyncTimeoutMillis = 15000L; // 0x3a98L
   }
 
   @Deprecated public static interface Font.ResourceLoader {
@@ -1357,15 +1357,15 @@
     method @androidx.compose.runtime.Stable public static float lerp(float start, float stop, float fraction);
   }
 
-  public final class Hyphens {
+  @kotlin.jvm.JvmInline public final value class Hyphens {
     field public static final androidx.compose.ui.text.style.Hyphens.Companion Companion;
   }
 
   public static final class Hyphens.Companion {
-    method public androidx.compose.ui.text.style.Hyphens getAuto();
-    method public androidx.compose.ui.text.style.Hyphens getNone();
-    property public final androidx.compose.ui.text.style.Hyphens Auto;
-    property public final androidx.compose.ui.text.style.Hyphens None;
+    method public int getAuto();
+    method public int getNone();
+    property public final int Auto;
+    property public final int None;
   }
 
   @androidx.compose.runtime.Immutable public final class LineBreak {
diff --git a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
index 03466cd..7183f6c 100644
--- a/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
+++ b/compose/ui/ui-text/benchmark/src/androidTest/java/androidx/compose/ui/text/benchmark/HyphensLineBreakBenchmark.kt
@@ -39,7 +39,7 @@
 @OptIn(ExperimentalTextApi::class, InternalPlatformTextApi::class)
 class HyphensLineBreakBenchmark(
     private val textLength: Int,
-    private val hyphens: Hyphens,
+    private val hyphensWrapper: HyphensWrapper,
     private val lineBreak: LineBreak
 ) {
     companion object {
@@ -48,7 +48,7 @@
         fun initParameters(): List<Array<Any?>> {
             return cartesian(
                 arrayOf(32, 128, 512),
-                arrayOf(Hyphens.None, Hyphens.Auto),
+                arrayOf(Hyphens.None.wrap, Hyphens.Auto.wrap),
                 arrayOf(LineBreak.Paragraph, LineBreak.Simple, LineBreak.Heading)
             )
         }
@@ -62,7 +62,7 @@
 
     private val width = 100
     private val textSize: Float = 10F
-    private val hyphenationFrequency = toLayoutHyphenationFrequency(hyphens)
+    private val hyphenationFrequency = toLayoutHyphenationFrequency(hyphensWrapper.hyphens)
     private val lineBreakStyle = toLayoutLineBreakStyle(lineBreak.strictness)
     private val breakStrategy = toLayoutBreakStrategy(lineBreak.strategy)
     private val lineBreakWordStyle = toLayoutLineBreakWordStyle(lineBreak.wordBreak)
@@ -138,4 +138,13 @@
             LineBreak.WordBreak.Phrase -> LayoutCompat.LINE_BREAK_WORD_STYLE_PHRASE
             else -> LayoutCompat.LINE_BREAK_WORD_STYLE_NONE
         }
-}
\ No newline at end of file
+}
+
+/**
+ * Required to make this test work due to a bug with value classes and Parameterized JUnit tests.
+ * https://youtrack.jetbrains.com/issue/KT-35523
+ */
+data class HyphensWrapper(val hyphens: Hyphens)
+
+val Hyphens.wrap: HyphensWrapper
+    get() = HyphensWrapper(this)
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplPreloadTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplPreloadTest.kt
index fe9f249..ec16989 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplPreloadTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontFamilyResolverImplPreloadTest.kt
@@ -197,7 +197,7 @@
             fallbackFont
         )
         val deferred = testScope.async { subject.preload(fontFamily) }
-        testScope.advanceTimeBy(Font.MaximumAsyncTimeout)
+        testScope.advanceTimeBy(Font.MaximumAsyncTimeoutMillis)
         assertThat(deferred.isCompleted).isTrue()
         testScope.runBlockingTest {
             deferred.await() // actually throw here
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterPreloadTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterPreloadTest.kt
index c86dd18..f477e95 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterPreloadTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterPreloadTest.kt
@@ -196,7 +196,7 @@
             subject.preload(fontFamily, fontLoader)
         }
         assertThat(typefaceLoader.pendingRequests()).containsExactly(asyncFont)
-        scope.advanceTimeBy(Font.MaximumAsyncTimeout)
+        scope.advanceTimeBy(Font.MaximumAsyncTimeoutMillis)
         scope.runBlockingTest {
             preloadJob.await()
         }
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
index 761092c..26eda8c 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapterTest.kt
@@ -294,7 +294,7 @@
                 assertThat(it).currentAsyncTypefaceValue(Typeface.DEFAULT)
             },
             doCompleteAsync = {
-                scope.advanceTimeBy(Font.MaximumAsyncTimeout)
+                scope.advanceTimeBy(Font.MaximumAsyncTimeoutMillis)
                 scope.runCurrent()
                 typefaceLoader.completeOne(asyncFontFallback, expected)
             }
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
index 01033ed..4a790ed 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/font/AndroidFont.kt
@@ -141,6 +141,17 @@
         typefaceLoader: TypefaceLoader,
     ) : this(loadingStrategy, typefaceLoader, FontVariation.Settings())
 
+    /**
+     * The settings that will be applied to this font, if supported by the font.
+     *
+     * If the font does not support a [FontVariation.Setting], it has no effect.
+     *
+     * Subclasses are required to apply these variation settings during font loading path on
+     * appropriate API levels, for example by using [Typeface.Builder.setFontVariationSettings].
+     *
+     * Subclasses may safely apply all variation settings without querying the font file. Android
+     * will ignore any unsupported axis.
+     */
     val variationSettings: FontVariation.Settings = variationSettings
 
     /**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/Font.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/Font.kt
index 79f18a2..66e5205 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/Font.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/Font.kt
@@ -95,7 +95,7 @@
          *
          * This timeout is not configurable, and timers are maintained globally.
          */
-        const val MaximumAsyncTimeout = 15_000L
+        const val MaximumAsyncTimeoutMillis = 15_000L
     }
 }
 
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
index 399974d..d928ccf 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontFamily.kt
@@ -196,7 +196,7 @@
 }
 
 /**
- * Defines a font family with an generic font family name.
+ * Defines a font family with a generic font family name.
  *
  * If the platform cannot find the passed generic font family, use the platform default one.
  *
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt
index ab77242..10b9df2 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/font/FontListFontFamilyTypefaceAdapter.kt
@@ -102,7 +102,7 @@
                     async {
                         asyncTypefaceCache.runCached(font, resourceLoader, true) {
                             try {
-                                withTimeout(Font.MaximumAsyncTimeout) {
+                                withTimeout(Font.MaximumAsyncTimeoutMillis) {
                                     resourceLoader.awaitLoad(font)
                                 }
                             } catch (cause: Exception) {
@@ -297,7 +297,7 @@
         return try {
             // case 0: load completes - success (non-null)
             // case 1: we timeout - permanent failure (null)
-            withTimeoutOrNull(Font.MaximumAsyncTimeout) {
+            withTimeoutOrNull(Font.MaximumAsyncTimeoutMillis) {
                 platformFontLoader.awaitLoad(this@loadWithTimeoutOrNull)
             }
         } catch (cancel: CancellationException) {
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
index dadedd9..5d8db44 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/style/Hyphens.kt
@@ -41,7 +41,8 @@
  * The default configuration for [Hyphens] = [Hyphens.None]
  *
  */
-class Hyphens private constructor() {
+@JvmInline
+value class Hyphens private constructor(internal val value: Int) {
     companion object {
         /**
          *  Lines will break with no hyphenation.
@@ -58,7 +59,7 @@
          * +---------+
          * </pre>
          */
-        val None = Hyphens()
+        val None = Hyphens(1)
 
         /**
          * The words will be automatically broken at appropriate hyphenation points.
@@ -73,7 +74,7 @@
          * +---------+
          * </pre>
          */
-        val Auto = Hyphens()
+        val Auto = Hyphens(2)
     }
 
     override fun toString() = when (this) {
diff --git a/compose/ui/ui-tooling/build.gradle b/compose/ui/ui-tooling/build.gradle
index 04f8fc0..80af0795 100644
--- a/compose/ui/ui-tooling/build.gradle
+++ b/compose/ui/ui-tooling/build.gradle
@@ -41,7 +41,8 @@
         implementation(project(":lifecycle:lifecycle-viewmodel"))
         implementation("androidx.savedstate:savedstate-ktx:1.2.0")
         implementation("androidx.compose.material:material:1.0.0")
-        implementation("androidx.activity:activity-compose:1.3.0")
+        implementation(project(":activity:activity-compose"))
+        implementation(project(":lifecycle:lifecycle-common"))
 
         // kotlin-reflect and animation-tooling-internal are provided by Studio at runtime
         compileOnly(project(":compose:animation:animation-tooling-internal"))
@@ -88,7 +89,8 @@
                 implementation("androidx.savedstate:savedstate-ktx:1.2.0")
 
                 implementation(project(":compose:material:material"))
-                implementation("androidx.activity:activity-compose:1.3.0")
+                implementation(project(":activity:activity-compose"))
+                implementation(project(":lifecycle:lifecycle-common"))
 
                 // kotlin-reflect and tooling-animation-internal are provided by Studio at runtime
                 compileOnly(project(":compose:animation:animation-tooling-internal"))
@@ -108,8 +110,8 @@
                 // Outside of androidx this is resolved via constraint added to lifecycle-common,
                 // but it doesn't work in androidx.
                 // See aosp/1804059
-                implementation("androidx.lifecycle:lifecycle-common-java8:2.5.1")
-                implementation("androidx.lifecycle:lifecycle-viewmodel-savedstate:2.5.1")
+                implementation(project(":lifecycle:lifecycle-common-java8"))
+                implementation(project(":lifecycle:lifecycle-viewmodel-savedstate"))
 
                 implementation(libs.junit)
                 implementation(libs.testRunner)
diff --git a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
index 541262b..aea52a6 100644
--- a/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
+++ b/compose/ui/ui-tooling/src/androidMain/kotlin/androidx/compose/ui/tooling/ComposeViewAdapter.kt
@@ -642,7 +642,8 @@
         override val savedStateRegistry: SavedStateRegistry
             get() = controller.savedStateRegistry
 
-        override fun getLifecycle(): Lifecycle = lifecycleRegistry
+        override val lifecycle: LifecycleRegistry
+            get() = lifecycleRegistry
     }
 
     private val FakeViewModelStoreOwner = object : ViewModelStoreOwner {
@@ -652,14 +653,14 @@
     }
 
     private val FakeOnBackPressedDispatcherOwner = object : OnBackPressedDispatcherOwner {
-        private val onBackPressedDispatcher = OnBackPressedDispatcher()
+        override val onBackPressedDispatcher = OnBackPressedDispatcher()
 
-        override fun getOnBackPressedDispatcher() = onBackPressedDispatcher
-        override fun getLifecycle() = FakeSavedStateRegistryOwner.lifecycleRegistry
+        override val lifecycle: LifecycleRegistry
+            get() = FakeSavedStateRegistryOwner.lifecycleRegistry
     }
 
     private val FakeActivityResultRegistryOwner = object : ActivityResultRegistryOwner {
-        private val activityResultRegistry = object : ActivityResultRegistry() {
+        override val activityResultRegistry = object : ActivityResultRegistry() {
             override fun <I : Any?, O : Any?> onLaunch(
                 requestCode: Int,
                 contract: ActivityResultContract<I, O>,
@@ -669,7 +670,5 @@
                 throw IllegalStateException("Calling launch() is not supported in Preview")
             }
         }
-
-        override fun getActivityResultRegistry(): ActivityResultRegistry = activityResultRegistry
     }
 }
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index f9d4bb9..d7dd5c0 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -71,6 +71,7 @@
         implementation("androidx.autofill:autofill:1.0.0")
         implementation(libs.kotlinCoroutinesAndroid)
 
+        implementation(project(":activity:activity"))
         implementation("androidx.activity:activity-ktx:1.5.1")
         implementation("androidx.core:core:1.9.0")
         implementation('androidx.collection:collection:1.0.0')
@@ -168,6 +169,7 @@
                 implementation("androidx.autofill:autofill:1.0.0")
                 implementation(libs.kotlinCoroutinesAndroid)
 
+                implementation(project(":activity:activity"))
                 implementation("androidx.activity:activity-ktx:1.5.1")
                 implementation("androidx.core:core:1.9.0")
                 implementation('androidx.collection:collection:1.0.0')
@@ -243,6 +245,7 @@
                 implementation("androidx.recyclerview:recyclerview:1.3.0-alpha02")
                 implementation("androidx.core:core-ktx:1.2.0")
                 implementation("androidx.activity:activity-compose:1.5.1")
+                implementation(project(":lifecycle:lifecycle-common"))
             }
 
             desktopTest.dependencies {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index a1855c5..e27cd97 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -125,6 +125,7 @@
 import org.junit.Assert.assertTrue
 import org.junit.Before
 import org.junit.Rule
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -3272,6 +3273,7 @@
         }
     }
 
+    @Ignore // b/266748671
     // Tests that an invalidation on a detached view will draw correctly when attached.
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
index 53b352a..f9d9136d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/draw/ShadowTest.kt
@@ -56,6 +56,7 @@
 import org.junit.Assert.assertNotEquals
 import org.junit.Assert.assertTrue
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -123,6 +124,7 @@
         }
     }
 
+    @Ignore // b/266748959
     @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     @Test
     fun switchFromShadowToNoShadow() {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
index 7d5100e..30f35ea 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/input/focus/FocusAwareEventPropagationTest.kt
@@ -205,6 +205,7 @@
         rule.runOnIdle { assertThat(sentEvent).isEqualTo(receivedEvent) }
     }
 
+    @Ignore("b/264466323")
     @Test
     fun onPreFocusAwareEvent_triggered() {
         // Arrange.
@@ -278,6 +279,7 @@
         }
     }
 
+    @Ignore // b/266984867
     @Test
     fun onPreFocusAwareEvent_triggeredBefore_onFocusAwareEvent_1() {
         // Arrange.
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistryTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistryTest.kt
index 6d919b5..e6e19a8 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistryTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/DisposableSaveableStateRegistryTest.kt
@@ -25,7 +25,9 @@
 import android.util.SizeF
 import android.util.SparseArray
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryController
 import androidx.savedstate.SavedStateRegistryOwner
@@ -252,19 +254,18 @@
 
 private class TestOwner(
     restoredBundle: Bundle? = null
-) : SavedStateRegistryOwner {
+) : SavedStateRegistryOwner, LifecycleOwner by TestLifecycleOwner(Lifecycle.State.INITIALIZED) {
 
-    private val lifecycle = LifecycleRegistry(this)
     private val controller = SavedStateRegistryController.create(this).apply {
         performRestore(restoredBundle ?: Bundle())
     }
+
     init {
-        lifecycle.currentState = Lifecycle.State.RESUMED
+        (lifecycle as LifecycleRegistry).currentState = Lifecycle.State.RESUMED
     }
 
     override val savedStateRegistry: SavedStateRegistry
         get() = controller.savedStateRegistry
-    override fun getLifecycle(): Lifecycle = lifecycle
 
     fun save() = Bundle().apply {
         controller.performSave(this)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
index 2ec7077..b4d9bbe 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/platform/WindowRecomposerTest.kt
@@ -38,8 +38,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.core.view.get
 import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleOwner
-import androidx.lifecycle.LifecycleRegistry
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
@@ -226,13 +225,9 @@
     fun lifecycleAwareWindowRecomposerJoinsAfterDetach(): Unit = runBlocking {
         ActivityScenario.launch(ComponentActivity::class.java).use { scenario ->
             lateinit var recomposer: Recomposer
-            val lifecycleOwner = object : LifecycleOwner {
-                val lifecycle = LifecycleRegistry(this)
-                override fun getLifecycle(): Lifecycle = lifecycle
-            }
+            val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
             scenario.onActivity { activity ->
                 val view = View(activity)
-                lifecycleOwner.lifecycle.currentState = Lifecycle.State.RESUMED
                 recomposer = view.createLifecycleAwareWindowRecomposer(
                     lifecycle = lifecycleOwner.lifecycle
                 )
@@ -265,11 +260,7 @@
             lateinit var recomposer: Recomposer
             scenario.onActivity { activity ->
                 val view = View(activity)
-                val lifecycleOwner = object : LifecycleOwner {
-                    val lifecycle = LifecycleRegistry(this)
-                    override fun getLifecycle(): Lifecycle = lifecycle
-                }
-                lifecycleOwner.lifecycle.currentState = Lifecycle.State.RESUMED
+                val lifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
                 recomposer = view.createLifecycleAwareWindowRecomposer(
                     lifecycle = lifecycleOwner.lifecycle
                 )
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 49fc678f..22b0751 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
@@ -71,9 +71,9 @@
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
-import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.findViewTreeLifecycleOwner
+import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.savedstate.SavedStateRegistry
 import androidx.savedstate.SavedStateRegistryOwner
 import androidx.savedstate.findViewTreeSavedStateRegistryOwner
@@ -458,8 +458,7 @@
     @Test
     fun androidView_propagatesLocalLifecycleOwnerAsViewTreeOwner() {
         lateinit var parentLifecycleOwner: LifecycleOwner
-        // We don't actually need to ever get the actual lifecycle.
-        val compositionLifecycleOwner = LifecycleOwner { throw UnsupportedOperationException() }
+        val compositionLifecycleOwner = TestLifecycleOwner()
         var childViewTreeLifecycleOwner: LifecycleOwner? = null
 
         rule.setContent {
@@ -492,13 +491,12 @@
     @Test
     fun androidView_propagatesLocalSavedStateRegistryOwnerAsViewTreeOwner() {
         lateinit var parentSavedStateRegistryOwner: SavedStateRegistryOwner
-        val compositionSavedStateRegistryOwner = object : SavedStateRegistryOwner {
-            // We don't actually need to ever get actual instances.
-            override fun getLifecycle(): Lifecycle = throw UnsupportedOperationException()
-
-            override val savedStateRegistry: SavedStateRegistry
-                get() = throw UnsupportedOperationException()
-        }
+        val compositionSavedStateRegistryOwner =
+            object : SavedStateRegistryOwner, LifecycleOwner by TestLifecycleOwner() {
+                // We don't actually need to ever get actual instance.
+                override val savedStateRegistry: SavedStateRegistry
+                    get() = throw UnsupportedOperationException()
+            }
         var childViewTreeSavedStateRegistryOwner: SavedStateRegistryOwner? = null
 
         rule.setContent {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
index 4f60fb3..b6579e5 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
@@ -267,6 +267,7 @@
         rule.onNodeWithText(defaultText).assertIsDisplayed()
     }
 
+    @Ignore // b/266613263
     @Test
     @SdkSuppress(maxSdkVersion = 33) // b/262909049: Failing on SDK 34
     fun dialogTest_backHandler_isCalled_backButtonPressed() {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/UiComposable.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/UiComposable.kt
index 25b550b..ed1427b 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/UiComposable.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/UiComposable.kt
@@ -19,7 +19,7 @@
 import androidx.compose.runtime.ComposableTargetMarker
 
 /**
- * An annotation that can be used to mark an composable function as being expected to be use in a
+ * An annotation that can be used to mark a composable function as being expected to be use in a
  * composable function that is also marked or inferred to be marked as a [UiComposable].
  *
  * Using this annotation explicitly is rarely necessary as the Compose compiler plugin will infer
diff --git a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/Motion.java b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/Motion.java
index 7bbecb4..044ff78 100644
--- a/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/Motion.java
+++ b/constraintlayout/constraintlayout-core/src/main/java/androidx/constraintlayout/core/motion/Motion.java
@@ -47,8 +47,8 @@
 import java.util.HashSet;
 
 /**
- * This contains the picture of a view through the a transition and is used to interpolate it
- * During an transition every view has a MotionController which drives its position.
+ * Contains the picture of a view through a transition and is used to interpolate it.
+ * During a transition every view has a MotionController which drives its position.
  * <p>
  * All parameter which affect a views motion are added to MotionController and then setup()
  * builds out the splines that control the view.
@@ -231,7 +231,7 @@
     }
 
     /**
-     * Will return the id of the view to move relative to
+     * Returns the id of the view to move relative to.
      * The position at the start and then end will be viewed relative to this view
      * -1 is the return value if NOT in polar mode
      *
@@ -275,7 +275,8 @@
     }
 
     /**
-     * fill the array point with the center coordinates point[0] is filled with the
+     * Fills the array point with the center coordinates.
+     * point[0] is filled with the
      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
      * 1.0
      *
@@ -364,7 +365,8 @@
     }
 
     /**
-     * fill the array point with the center coordinates point[0] is filled with the
+     * Fills the array point with the center coordinates.
+     * point[0] is filled with the
      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
      * 1.0
      *
@@ -1200,7 +1202,7 @@
     }
 
     /**
-     * Calculates the adjusted (and optional velocity)
+     * Calculates the adjusted position (and optional velocity).
      * Note if requesting velocity staggering is not considered
      *
      * @param position position pre stagger
@@ -1634,8 +1636,8 @@
     }
 
     /**
-     * Get the keyFrames for the view controlled by this MotionController
-     * the info data structure is of the form
+     * Gets the keyFrames for the view controlled by this MotionController.
+     * The info data structure is of the form
      * 0 length if your are at index i the [i+len+1] is the next entry
      * 1 type  1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
      * 2 position
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionController.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionController.java
index 78ae33e..af29c05 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionController.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/motion/widget/MotionController.java
@@ -54,7 +54,7 @@
 import java.util.HashSet;
 
 /**
- * This contains the picture of a view through the a transition and is used to interpolate it
+ * Contains the picture of a view through a transition and is used to interpolate it.
  * During an transition every view has a MotionController which drives its position.
  * <p>
  * All parameter which affect a views motion are added to MotionController and then setup()
@@ -228,7 +228,7 @@
     }
 
     /**
-     * Will return the id of the view to move relative to
+     * Will return the id of the view to move relative to.
      * The position at the start and then end will be viewed relative to this view
      * -1 is the return value if NOT in polar mode
      *
@@ -287,7 +287,8 @@
     }
 
     /**
-     * fill the array point with the center coordinates point[0] is filled with the
+     * fill the array point with the center coordinates.
+     * point[0] is filled with the
      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
      * 1.0
      *
@@ -375,7 +376,8 @@
     }
 
     /**
-     * fill the array point with the center coordinates point[0] is filled with the
+     * fill the array point with the center coordinates.
+     * point[0] is filled with the
      * x coordinate of "time" 0.0 mPoints[point.length-1] is filled with the y coordinate of "time"
      * 1.0
      *
@@ -1232,7 +1234,7 @@
     }
 
     /**
-     * Calculates the adjusted (and optional velocity)
+     * Calculates the adjusted position (and optional velocity).
      * Note if requesting velocity staggering is not considered
      *
      * @param position position pre stagger
@@ -1663,7 +1665,7 @@
     }
 
     /**
-     * Get the keyFrames for the view controlled by this MotionController
+     * Get the keyFrames for the view controlled by this MotionController.
      * the info data structure is of the form
      * 0 length if your are at index i the [i+len+1] is the next entry
      * 1 type  1=Attributes, 2=Position, 3=TimeCycle 4=Cycle 5=Trigger
diff --git a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
index c675c67..d0ece80 100644
--- a/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
+++ b/constraintlayout/constraintlayout/src/main/java/androidx/constraintlayout/widget/ConstraintLayout.java
@@ -67,33 +67,15 @@
  * <p>
  * There are currently various types of constraints that you can use:
  * <ul>
- * <li>
- * <a href="#RelativePositioning">Relative positioning</a>
- * </li>
- * <li>
- * <a href="#Margins">Margins</a>
- * </li>
- * <li>
- * <a href="#CenteringPositioning">Centering positioning</a>
- * </li>
- * <li>
- * <a href="#CircularPositioning">Circular positioning</a>
- * </li>
- * <li>
- * <a href="#VisibilityBehavior">Visibility behavior</a>
- * </li>
- * <li>
- * <a href="#DimensionConstraints">Dimension constraints</a>
- * </li>
- * <li>
- * <a href="#Chains">Chains</a>
- * </li>
- * <li>
- * <a href="#VirtualHelpers">Virtual Helpers objects</a>
- * </li>
- * <li>
- * <a href="#Optimizer">Optimizer</a>
- * </li>
+ * <li>Relative positioning</li>
+ * <li>Margins</li>
+ * <li>Centering positioning</li>
+ * <li>Circular positioning</li>
+ * <li>Visibility behavior</li>
+ * <li>Dimension constraints</li>
+ * <li>Chains</li>
+ * <li>Virtual Helpers objects</li>
+ * <li>Optimizer</li>
  * </ul>
  * </p>
  *
@@ -105,9 +87,9 @@
  * ConstraintLayout.LayoutParams} for layout attributes
  * </p>
  *
- * <h3>Developer Guide</h3>
+ * <h2>Developer Guide</h2>
  *
- * <h4 id="RelativePositioning"> Relative positioning </h4>
+ * <h3 id="RelativePositioning"> Relative positioning </h3>
  * <p>
  * Relative positioning is one of the basic building blocks of creating layouts in ConstraintLayout.
  * Those constraints allow you to position a given widget relative to another one. You can constrain
@@ -170,7 +152,7 @@
  *
  * </p>
  *
- * <h4 id="Margins"> Margins </h4>
+ * <h3 id="Margins"> Margins </h3>
  * <p>
  * <div align="center" >
  * <img width="325px" src="resources/images/relative-positioning-margin.png">
@@ -190,7 +172,7 @@
  * </ul>
  * <p>Note that a margin can only be positive or equal to zero,
  * and takes a {@code Dimension}.</p>
- * <h4 id="GoneMargin"> Margins when connected to a GONE widget</h4>
+ * <h3 id="GoneMargin"> Margins when connected to a GONE widget</h3>
  * <p>When a position constraint target's visibility is {@code View.GONE},
  * you can also indicate a different
  * margin value to be used using the following attributes:</p>
@@ -206,7 +188,7 @@
  * </p>
  *
  * </p>
- * <h4 id="CenteringPositioning"> Centering positioning and bias</h4>
+ * <h3 id="CenteringPositioning"> Centering positioning and bias</h3>
  * <p>
  * A useful aspect of {@code ConstraintLayout} is in how it deals with "impossible" constraints.
  * For example, if
@@ -234,7 +216,7 @@
  * in the parent container.
  * This will apply similarly for vertical constraints.
  * </p>
- * <h5 id="Bias">Bias</h5>
+ * <b>Bias</b>
  * <p>
  * The default when encountering such opposite constraints is to center the widget;
  * but you can tweak
@@ -265,7 +247,7 @@
  * </p>
  * </p>
  *
- * <h4 id="CircularPositioning"> Circular positioning (<b>Added in 1.1</b>)</h4>
+ * <h3 id="CircularPositioning"> Circular positioning (<b>Added in 1.1</b>)</h3>
  * <p>
  * You can constrain a widget center relative to another widget center,
  * at an angle and a distance. This allows
@@ -291,7 +273,7 @@
  *         }
  *     </pre>
  * </p>
- * <h4 id="VisibilityBehavior"> Visibility behavior </h4>
+ * <h3 id="VisibilityBehavior"> Visibility behavior </h3>
  * <p>
  * {@code ConstraintLayout} has a specific handling of widgets being marked as {@code View.GONE}.
  * <p>{@code GONE} widgets, as usual, are not going to be displayed and
@@ -326,8 +308,8 @@
  * (see <a href="#GoneMargin">the section above about the gone margin attributes</a>).
  * </p>
  *
- * <h4 id="DimensionConstraints"> Dimensions constraints </h4>
- * <h5>Minimum dimensions on ConstraintLayout</h5>
+ * <h3 id="DimensionConstraints"> Dimensions constraints </h3>
+ * <b>Minimum dimensions on ConstraintLayout</b>
  * <p>
  * You can define minimum and maximum sizes for the {@code ConstraintLayout} itself:
  * <ul>
@@ -339,7 +321,7 @@
  * Those minimum and maximum dimensions will be used by
  * {@code ConstraintLayout} when its dimensions are set to {@code WRAP_CONTENT}.
  * </p>
- * <h5>Widgets dimension constraints</h5>
+ * <b>Widgets dimension constraints</b>
  * <p>
  * The dimension of the widgets can be specified by setting the
  * {@code android:layout_width} and
@@ -366,7 +348,7 @@
  * left/right or top/bottom constraints being set to {@code "parent"}.
  * </p>
  * </p>
- * <h5>WRAP_CONTENT : enforcing constraints (<i><b>Added in 1.1</b></i>)</h5>
+ * <b>WRAP_CONTENT : enforcing constraints (<i>Added in 1.1</i>)</b>
  * <p>
  * If a dimension is set to {@code WRAP_CONTENT}, in versions before 1.1
  * they will be treated as a literal dimension -- meaning, constraints will
@@ -379,11 +361,12 @@
  * <li>{@code app:layout_constrainedHeight="true|false"}</li>
  * </ul>
  * </p>
- * <h5>MATCH_CONSTRAINT dimensions (<i><b>Added in 1.1</b></i>)</h5>
+ * <b>MATCH_CONSTRAINT dimensions (<i>Added in 1.1</i>)</b>
  * <p>
  * When a dimension is set to {@code MATCH_CONSTRAINT},
  * the default behavior is to have the resulting size take all the available space.
  * Several additional modifiers are available:
+ * </p>
  * <ul>
  * <li>{@code layout_constraintWidth_min} and {@code layout_constraintHeight_min} :
  * will set the minimum size for this dimension</li>
@@ -392,11 +375,11 @@
  * <li>{@code layout_constraintWidth_percent} and {@code layout_constraintHeight_percent} :
  * will set the size of this dimension as a percentage of the parent</li>
  * </ul>
- * <h6>Min and Max</h6>
- * The value indicated for min and max can be either a dimension in Dp,
- * or "wrap", which will use the same value as what {@code WRAP_CONTENT} would do.
- * <h6>Percent dimension</h6>
- * To use percent, you need to set the following:
+ * <b>Min and Max</b>
+ * <p>The value indicated for min and max can be either a dimension in Dp,
+ * or "wrap", which will use the same value as what {@code WRAP_CONTENT} would do.</p>
+ * <b>Percent dimension</b>
+ * <p>To use percent, you need to set the following</p>
  * <ul>
  * <li>The dimension should be set to {@code MATCH_CONSTRAINT} (0dp)</li>
  * <li>The default should be set to percent {@code app:layout_constraintWidth_default="percent"}
@@ -404,8 +387,7 @@
  * <li>Then set the {@code layout_constraintWidth_percent}
  * or {@code layout_constraintHeight_percent} attributes to a value between 0 and 1</li>
  * </ul>
- * </p>
- * <h5>Ratio</h5>
+ * <b>Ratio</b>
  * <p>
  * You can also define one dimension of a widget as a ratio of the other one.
  * In order to do that, you
@@ -458,10 +440,10 @@
  *
  * </p>
  *
- * <h4 id="Chains">Chains</h4>
+ * <h3 id="Chains">Chains</h3>
  * <p>Chains provide group-like behavior in a single axis (horizontally or vertically).
  * The other axis can be constrained independently.</p>
- * <h5>Creating a chain</h5>
+ * <b>Creating a chain</b>
  * <p>
  * A set of widgets are considered a chain if they are linked together via a
  * bi-directional connection (see Fig. 9, showing a minimal chain, with two widgets).
@@ -471,7 +453,7 @@
  * <br><b><i>Fig. 9 - Chain</i></b>
  * </div>
  * <p>
- * <h5>Chain heads</h5>
+ * <b>Chain heads</b>
  * <p>
  * Chains are controlled by attributes set on the first element of the chain
  * (the "head" of the chain):
@@ -482,10 +464,10 @@
  * </div>
  * <p>The head is the left-most widget for horizontal chains,
  * and the top-most widget for vertical chains.</p>
- * <h5>Margins in chains</h5>
+ * <b>Margins in chains</b>
  * <p>If margins are specified on connections, they will be taken into account.
  * In the case of spread chains, margins will be deducted from the allocated space.</p>
- * <h5>Chain Style</h5>
+ * <b>Chain Style</b>
  * <p>When setting the attribute {@code layout_constraintHorizontal_chainStyle} or
  * {@code layout_constraintVertical_chainStyle} on the first element of a chain,
  * the behavior of the chain will change according to the specified style
@@ -505,7 +487,7 @@
  * <br><b><i>Fig. 11 - Chains Styles</i></b>
  * </div>
  * </p>
- * <h5>Weighted chains</h5>
+ * <b>Weighted chains</b>
  * <p>The default behavior of a chain is to spread the elements equally in the available space.
  * If one or more elements are using {@code MATCH_CONSTRAINT}, they
  * will use the available empty space (equally divided among themselves).
@@ -517,7 +499,7 @@
  * with the first element using a weight of 2 and the second a weight of 1,
  * the space occupied by the first element will be twice that of the second element.</p>
  *
- * <h5>Margins and chains (<i><b>in 1.1</b></i>)</h5>
+ * <b>Margins and chains (<i>in 1.1</i>)</b>
  * <p>When using margins on elements in a chain, the margins are additive.</p>
  * <p>For example, on a horizontal chain, if one element defines
  * a right margin of 10dp and the next element
@@ -527,7 +509,7 @@
  * leftover space used by chains
  * to position items. The leftover space does not contain the margins.</p>
  *
- * <h4 id="VirtualHelpers"> Virtual Helper objects </h4>
+ * <h3 id="VirtualHelpers"> Virtual Helper objects </h3>
  * <p>In addition to the intrinsic capabilities detailed previously,
  * you can also use special helper objects
  * in {@code ConstraintLayout} to help you with your layout. Currently, the
@@ -537,7 +519,7 @@
  * then be positioned by constraining them to such guidelines. In <b>1.1</b>,
  * {@code Barrier} and {@code Group} were added too.</p>
  *
- * <h4 id="Optimizer">Optimizer (<i><b>in 1.1</b></i>)</h4>
+ * <h3 id="Optimizer">Optimizer (<i><b>in 1.1</b></i>)</h3>
  * <p>
  * In 1.1 we exposed the constraints optimizer. You can decide which optimizations
  * are applied by adding the tag <i>app:layout_optimizationLevel</i>
diff --git a/core/core-animation/src/main/java/androidx/core/animation/AccelerateInterpolator.java b/core/core-animation/src/main/java/androidx/core/animation/AccelerateInterpolator.java
index bb2b79f..de01686 100644
--- a/core/core-animation/src/main/java/androidx/core/animation/AccelerateInterpolator.java
+++ b/core/core-animation/src/main/java/androidx/core/animation/AccelerateInterpolator.java
@@ -27,7 +27,7 @@
 
 /**
  * An interpolator where the rate of change starts out slowly and
- * and then accelerates.
+ * then accelerates.
  */
 public class AccelerateInterpolator implements Interpolator {
     private final float mFactor;
diff --git a/core/core-animation/src/main/java/androidx/core/animation/DecelerateInterpolator.java b/core/core-animation/src/main/java/androidx/core/animation/DecelerateInterpolator.java
index 7ee1d90..3483c0b 100644
--- a/core/core-animation/src/main/java/androidx/core/animation/DecelerateInterpolator.java
+++ b/core/core-animation/src/main/java/androidx/core/animation/DecelerateInterpolator.java
@@ -28,7 +28,7 @@
 
 /**
  * An interpolator where the rate of change starts out quickly and
- * and then decelerates.
+ * then decelerates.
  *
  */
 public class DecelerateInterpolator implements Interpolator {
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 d3dda08..4d30246 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompat.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompat.java
@@ -1073,7 +1073,7 @@
         BubbleMetadata mBubbleMetadata;
         Notification mNotification = new Notification();
         boolean mSilent;
-        Icon mSmallIcon;
+        Object mSmallIcon; // Icon
 
         /**
          * @deprecated This field was not meant to be public.
@@ -4101,8 +4101,7 @@
                     Api28Impl.setGroupConversation((Notification.MessagingStyle) frameworkStyle,
                             mIsGroupConversation);
                 }
-                Api16Impl.setBuilder((Notification.MessagingStyle) frameworkStyle,
-                        builder.getBuilder());
+                Api16Impl.setBuilder((Notification.Style) frameworkStyle, builder.getBuilder());
             } else {
                 Message latestIncomingMessage = findLatestIncomingMessage();
                 // Set the title
@@ -5057,7 +5056,7 @@
                 }
                 if (style != null) {
                     // Before applying the style, we clear the actions.
-                    Api24Impl.setActions(builderAccessor.getBuilder());
+                    Api24Impl.clearActions(builderAccessor.getBuilder());
 
                     Api16Impl.setBuilder(style, builderAccessor.getBuilder());
                     if (mAnswerButtonColor != null) {
@@ -5116,7 +5115,7 @@
                     List<Action> actionsList = getActionsListWithSystemActions();
                     // Clear any existing actions.
                     if (Build.VERSION.SDK_INT >= 24) {
-                        Api24Impl.setActions(builder);
+                        Api24Impl.clearActions(builder);
                     }
                     // Adds the actions to the builder in the proper order.
                     for (Action action : actionsList) {
@@ -5429,10 +5428,12 @@
             private Api24Impl() {
             }
 
+            /**
+             * Clears actions by calling setActions() with an empty list of arguments.
+             */
             @DoNotInline
-            static Notification.Builder setActions(Notification.Builder builder,
-                    Notification.Action... actions) {
-                return builder.setActions(actions);
+            static Notification.Builder clearActions(Notification.Builder builder) {
+                return builder.setActions();
             }
 
             @DoNotInline
@@ -7076,8 +7077,7 @@
                     Action[] actions = new Action[parcelables.size()];
                     for (int i = 0; i < actions.length; i++) {
                         if (Build.VERSION.SDK_INT >= 20) {
-                            actions[i] = NotificationCompat.getActionCompatFromAction(
-                                    (Notification.Action) parcelables.get(i));
+                            actions[i] = Api20Impl.getActionCompatFromAction(parcelables, i);
                         } else if (Build.VERSION.SDK_INT >= 16) {
                             actions[i] = NotificationCompatJellybean.getActionFromBundle(
                                     (Bundle) parcelables.get(i));
@@ -7882,6 +7882,14 @@
             static Notification.Action build(Notification.Action.Builder builder) {
                 return builder.build();
             }
+
+            @DoNotInline
+            public static Action getActionCompatFromAction(ArrayList<Parcelable> parcelables,
+                    int i) {
+                // Cast to Notification.Action (added in API 19) must happen in static inner class.
+                return NotificationCompat.getActionCompatFromAction(
+                        (Notification.Action) parcelables.get(i));
+            }
         }
 
         /**
diff --git a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
index 7dbdfda..a0181d8 100644
--- a/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
+++ b/core/core/src/main/java/androidx/core/app/NotificationCompatBuilder.java
@@ -708,8 +708,8 @@
 
         @DoNotInline
         static Notification.Builder setSound(Notification.Builder builder, Uri sound,
-                AudioAttributes audioAttributes) {
-            return builder.setSound(sound, audioAttributes);
+                Object audioAttributes /** AudioAttributes **/) {
+            return builder.setSound(sound, (AudioAttributes) audioAttributes);
         }
     }
 
@@ -729,8 +729,9 @@
         }
 
         @DoNotInline
-        static Notification.Builder setSmallIcon(Notification.Builder builder, Icon icon) {
-            return builder.setSmallIcon(icon);
+        static Notification.Builder setSmallIcon(Notification.Builder builder,
+                Object icon /** Icon **/) {
+            return builder.setSmallIcon((Icon) icon);
         }
     }
 
@@ -860,8 +861,9 @@
         }
 
         @DoNotInline
-        static Notification.Builder setLocusId(Notification.Builder builder, LocusId locusId) {
-            return builder.setLocusId(locusId);
+        static Notification.Builder setLocusId(Notification.Builder builder,
+                Object locusId /** LocusId **/) {
+            return builder.setLocusId((LocusId) locusId);
         }
 
         @DoNotInline
diff --git a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/BeginSignInControllerUtility.kt b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/BeginSignInControllerUtility.kt
index 8fa9bfa..3f915a5 100644
--- a/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/BeginSignInControllerUtility.kt
+++ b/credentials/credentials-play-services-auth/src/main/java/androidx/credentials/playservices/controllers/BeginSignIn/BeginSignInControllerUtility.kt
@@ -52,7 +52,7 @@
             BeginSignInRequest {
             var isPublicKeyCredReqFound = false
             val requestBuilder = BeginSignInRequest.Builder()
-            for (option in request.getCredentialOptions) {
+            for (option in request.credentialOptions) {
                 if (option is GetPasswordOption) {
                     requestBuilder.setPasswordRequestOptions(
                         BeginSignInRequest.PasswordRequestOptions.Builder()
diff --git a/credentials/credentials/api/current.txt b/credentials/credentials/api/current.txt
index 58f48fd..9bba4ab 100644
--- a/credentials/credentials/api/current.txt
+++ b/credentials/credentials/api/current.txt
@@ -8,18 +8,27 @@
   public abstract class CreateCredentialRequest {
   }
 
+  public static final class CreateCredentialRequest.DisplayInfo {
+    ctor public CreateCredentialRequest.DisplayInfo(String userId, optional String? userDisplayName);
+    ctor public CreateCredentialRequest.DisplayInfo(String userId);
+    method public String? getUserDisplayName();
+    method public String getUserId();
+    property public final String? userDisplayName;
+    property public final String userId;
+  }
+
   public abstract class CreateCredentialResponse {
   }
 
   public class CreateCustomCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, androidx.credentials.CreateCredentialRequest.DisplayInfo displayInfo);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getCredentialData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
     property public final android.os.Bundle credentialData;
-    property public final boolean requireSystemProvider;
+    property public final boolean isSystemProviderRequired;
     property public final String type;
   }
 
@@ -44,23 +53,23 @@
   }
 
   public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequest(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
   public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -68,8 +77,8 @@
   public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
     ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
-    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
   }
@@ -87,10 +96,10 @@
     method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
-    method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
-    method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
+    method public suspend Object? createCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
+    method public void createCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
+    method public suspend Object? getCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
+    method public void getCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -103,6 +112,9 @@
     method public void onResult(R? result);
   }
 
+  public abstract class CredentialOption {
+  }
+
   public interface CredentialProvider {
     method public boolean isAvailableOnDevice();
     method public void onClearCredential(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
@@ -118,24 +130,21 @@
     property public final String type;
   }
 
-  public abstract class GetCredentialOption {
-  }
-
   public final class GetCredentialRequest {
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
-    method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional boolean isAutoSelectAllowed);
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
+    method public java.util.List<androidx.credentials.CredentialOption> getCredentialOptions();
     method public boolean isAutoSelectAllowed();
-    property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
     property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
-    method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
+    method public androidx.credentials.GetCredentialRequest.Builder addCredentialOption(androidx.credentials.CredentialOption credentialOption);
     method public androidx.credentials.GetCredentialRequest build();
     method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
-    method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
+    method public androidx.credentials.GetCredentialRequest.Builder setCredentialOptions(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
   }
 
   public final class GetCredentialResponse {
@@ -144,40 +153,40 @@
     property public final androidx.credentials.Credential credential;
   }
 
-  public class GetCustomCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+  public class GetCustomCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getRequestData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
+    property public final boolean isSystemProviderRequired;
     property public final android.os.Bundle requestData;
-    property public final boolean requireSystemProvider;
     property public final String type;
   }
 
-  public final class GetPasswordOption extends androidx.credentials.GetCredentialOption {
+  public final class GetPasswordOption extends androidx.credentials.CredentialOption {
     ctor public GetPasswordOption();
   }
 
-  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOption(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
-  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -185,8 +194,8 @@
   public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
     ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
-    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
   }
diff --git a/credentials/credentials/api/public_plus_experimental_current.txt b/credentials/credentials/api/public_plus_experimental_current.txt
index 58f48fd..9bba4ab 100644
--- a/credentials/credentials/api/public_plus_experimental_current.txt
+++ b/credentials/credentials/api/public_plus_experimental_current.txt
@@ -8,18 +8,27 @@
   public abstract class CreateCredentialRequest {
   }
 
+  public static final class CreateCredentialRequest.DisplayInfo {
+    ctor public CreateCredentialRequest.DisplayInfo(String userId, optional String? userDisplayName);
+    ctor public CreateCredentialRequest.DisplayInfo(String userId);
+    method public String? getUserDisplayName();
+    method public String getUserId();
+    property public final String? userDisplayName;
+    property public final String userId;
+  }
+
   public abstract class CreateCredentialResponse {
   }
 
   public class CreateCustomCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, androidx.credentials.CreateCredentialRequest.DisplayInfo displayInfo);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getCredentialData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
     property public final android.os.Bundle credentialData;
-    property public final boolean requireSystemProvider;
+    property public final boolean isSystemProviderRequired;
     property public final String type;
   }
 
@@ -44,23 +53,23 @@
   }
 
   public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequest(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
   public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -68,8 +77,8 @@
   public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
     ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
-    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
   }
@@ -87,10 +96,10 @@
     method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
-    method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
-    method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
+    method public suspend Object? createCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
+    method public void createCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
+    method public suspend Object? getCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
+    method public void getCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -103,6 +112,9 @@
     method public void onResult(R? result);
   }
 
+  public abstract class CredentialOption {
+  }
+
   public interface CredentialProvider {
     method public boolean isAvailableOnDevice();
     method public void onClearCredential(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
@@ -118,24 +130,21 @@
     property public final String type;
   }
 
-  public abstract class GetCredentialOption {
-  }
-
   public final class GetCredentialRequest {
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
-    method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional boolean isAutoSelectAllowed);
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
+    method public java.util.List<androidx.credentials.CredentialOption> getCredentialOptions();
     method public boolean isAutoSelectAllowed();
-    property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
     property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
-    method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
+    method public androidx.credentials.GetCredentialRequest.Builder addCredentialOption(androidx.credentials.CredentialOption credentialOption);
     method public androidx.credentials.GetCredentialRequest build();
     method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
-    method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
+    method public androidx.credentials.GetCredentialRequest.Builder setCredentialOptions(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
   }
 
   public final class GetCredentialResponse {
@@ -144,40 +153,40 @@
     property public final androidx.credentials.Credential credential;
   }
 
-  public class GetCustomCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+  public class GetCustomCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getRequestData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
+    property public final boolean isSystemProviderRequired;
     property public final android.os.Bundle requestData;
-    property public final boolean requireSystemProvider;
     property public final String type;
   }
 
-  public final class GetPasswordOption extends androidx.credentials.GetCredentialOption {
+  public final class GetPasswordOption extends androidx.credentials.CredentialOption {
     ctor public GetPasswordOption();
   }
 
-  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOption(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
-  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -185,8 +194,8 @@
   public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
     ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
-    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
   }
diff --git a/credentials/credentials/api/restricted_current.txt b/credentials/credentials/api/restricted_current.txt
index 58f48fd..9bba4ab 100644
--- a/credentials/credentials/api/restricted_current.txt
+++ b/credentials/credentials/api/restricted_current.txt
@@ -8,18 +8,27 @@
   public abstract class CreateCredentialRequest {
   }
 
+  public static final class CreateCredentialRequest.DisplayInfo {
+    ctor public CreateCredentialRequest.DisplayInfo(String userId, optional String? userDisplayName);
+    ctor public CreateCredentialRequest.DisplayInfo(String userId);
+    method public String? getUserDisplayName();
+    method public String getUserId();
+    property public final String? userDisplayName;
+    property public final String userId;
+  }
+
   public abstract class CreateCredentialResponse {
   }
 
   public class CreateCustomCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+    ctor public CreateCustomCredentialRequest(String type, android.os.Bundle credentialData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired, androidx.credentials.CreateCredentialRequest.DisplayInfo displayInfo);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getCredentialData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
     property public final android.os.Bundle credentialData;
-    property public final boolean requireSystemProvider;
+    property public final boolean isSystemProviderRequired;
     property public final String type;
   }
 
@@ -44,23 +53,23 @@
   }
 
   public final class CreatePublicKeyCredentialRequest extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequest(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequest(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
   public final class CreatePublicKeyCredentialRequestPrivileged extends androidx.credentials.CreateCredentialRequest {
-    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+    ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public CreatePublicKeyCredentialRequestPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -68,8 +77,8 @@
   public static final class CreatePublicKeyCredentialRequestPrivileged.Builder {
     ctor public CreatePublicKeyCredentialRequestPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged build();
-    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Builder setRequestJson(String requestJson);
   }
@@ -87,10 +96,10 @@
     method public suspend Object? clearCredentialState(androidx.credentials.ClearCredentialStateRequest request, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public void clearCredentialStateAsync(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
     method public static androidx.credentials.CredentialManager create(android.content.Context context);
-    method public suspend Object? executeCreateCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
-    method public void executeCreateCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
-    method public suspend Object? executeGetCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
-    method public void executeGetCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
+    method public suspend Object? createCredential(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.CreateCredentialResponse>);
+    method public void createCredentialAsync(androidx.credentials.CreateCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.CreateCredentialResponse,androidx.credentials.exceptions.CreateCredentialException> callback);
+    method public suspend Object? getCredential(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, kotlin.coroutines.Continuation<? super androidx.credentials.GetCredentialResponse>);
+    method public void getCredentialAsync(androidx.credentials.GetCredentialRequest request, android.app.Activity activity, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<androidx.credentials.GetCredentialResponse,androidx.credentials.exceptions.GetCredentialException> callback);
     field public static final androidx.credentials.CredentialManager.Companion Companion;
   }
 
@@ -103,6 +112,9 @@
     method public void onResult(R? result);
   }
 
+  public abstract class CredentialOption {
+  }
+
   public interface CredentialProvider {
     method public boolean isAvailableOnDevice();
     method public void onClearCredential(androidx.credentials.ClearCredentialStateRequest request, android.os.CancellationSignal? cancellationSignal, java.util.concurrent.Executor executor, androidx.credentials.CredentialManagerCallback<java.lang.Void,androidx.credentials.exceptions.ClearCredentialException> callback);
@@ -118,24 +130,21 @@
     property public final String type;
   }
 
-  public abstract class GetCredentialOption {
-  }
-
   public final class GetCredentialRequest {
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions, optional boolean isAutoSelectAllowed);
-    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
-    method public java.util.List<androidx.credentials.GetCredentialOption> getGetCredentialOptions();
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions, optional boolean isAutoSelectAllowed);
+    ctor public GetCredentialRequest(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
+    method public java.util.List<androidx.credentials.CredentialOption> getCredentialOptions();
     method public boolean isAutoSelectAllowed();
-    property public final java.util.List<androidx.credentials.GetCredentialOption> getCredentialOptions;
+    property public final java.util.List<androidx.credentials.CredentialOption> credentialOptions;
     property public final boolean isAutoSelectAllowed;
   }
 
   public static final class GetCredentialRequest.Builder {
     ctor public GetCredentialRequest.Builder();
-    method public androidx.credentials.GetCredentialRequest.Builder addGetCredentialOption(androidx.credentials.GetCredentialOption getCredentialOption);
+    method public androidx.credentials.GetCredentialRequest.Builder addCredentialOption(androidx.credentials.CredentialOption credentialOption);
     method public androidx.credentials.GetCredentialRequest build();
     method public androidx.credentials.GetCredentialRequest.Builder setAutoSelectAllowed(boolean autoSelectAllowed);
-    method public androidx.credentials.GetCredentialRequest.Builder setGetCredentialOptions(java.util.List<? extends androidx.credentials.GetCredentialOption> getCredentialOptions);
+    method public androidx.credentials.GetCredentialRequest.Builder setCredentialOptions(java.util.List<? extends androidx.credentials.CredentialOption> credentialOptions);
   }
 
   public final class GetCredentialResponse {
@@ -144,40 +153,40 @@
     property public final androidx.credentials.Credential credential;
   }
 
-  public class GetCustomCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean requireSystemProvider);
+  public class GetCustomCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetCustomCredentialOption(String type, android.os.Bundle requestData, android.os.Bundle candidateQueryData, boolean isSystemProviderRequired);
     method public final android.os.Bundle getCandidateQueryData();
     method public final android.os.Bundle getRequestData();
-    method public final boolean getRequireSystemProvider();
     method public final String getType();
+    method public final boolean isSystemProviderRequired();
     property public final android.os.Bundle candidateQueryData;
+    property public final boolean isSystemProviderRequired;
     property public final android.os.Bundle requestData;
-    property public final boolean requireSystemProvider;
     property public final String type;
   }
 
-  public final class GetPasswordOption extends androidx.credentials.GetCredentialOption {
+  public final class GetPasswordOption extends androidx.credentials.CredentialOption {
     ctor public GetPasswordOption();
   }
 
-  public final class GetPublicKeyCredentialOption extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOption extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOption(String requestJson, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOption(String requestJson);
-    method public boolean getAllowHybrid();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String requestJson;
   }
 
-  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.GetCredentialOption {
-    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean allowHybrid);
+  public final class GetPublicKeyCredentialOptionPrivileged extends androidx.credentials.CredentialOption {
+    ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash, optional boolean preferImmediatelyAvailableCredentials);
     ctor public GetPublicKeyCredentialOptionPrivileged(String requestJson, String relyingParty, String clientDataHash);
-    method public boolean getAllowHybrid();
     method public String getClientDataHash();
+    method public boolean getPreferImmediatelyAvailableCredentials();
     method public String getRelyingParty();
     method public String getRequestJson();
-    property public final boolean allowHybrid;
     property public final String clientDataHash;
+    property public final boolean preferImmediatelyAvailableCredentials;
     property public final String relyingParty;
     property public final String requestJson;
   }
@@ -185,8 +194,8 @@
   public static final class GetPublicKeyCredentialOptionPrivileged.Builder {
     ctor public GetPublicKeyCredentialOptionPrivileged.Builder(String requestJson, String relyingParty, String clientDataHash);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged build();
-    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setAllowHybrid(boolean allowHybrid);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setClientDataHash(String clientDataHash);
+    method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setPreferImmediatelyAvailableCredentials(boolean preferImmediatelyAvailableCredentials);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRelyingParty(String relyingParty);
     method public androidx.credentials.GetPublicKeyCredentialOptionPrivileged.Builder setRequestJson(String requestJson);
   }
diff --git a/credentials/credentials/build.gradle b/credentials/credentials/build.gradle
index e93d59b..0c8c3fb 100644
--- a/credentials/credentials/build.gradle
+++ b/credentials/credentials/build.gradle
@@ -27,6 +27,7 @@
     api(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesCore)
 
+    androidTestImplementation("androidx.activity:activity:1.2.0")
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/credentials/credentials/src/androidTest/AndroidManifest.xml b/credentials/credentials/src/androidTest/AndroidManifest.xml
index 4995896..79077dd 100644
--- a/credentials/credentials/src/androidTest/AndroidManifest.xml
+++ b/credentials/credentials/src/androidTest/AndroidManifest.xml
@@ -16,4 +16,9 @@
   -->
 
 <manifest xmlns:android="http://schemas.android.com/apk/res/android">
+    <application>
+        <activity
+            android:name="androidx.credentials.TestActivity"
+            android:exported="false"/>
+    </application>
 </manifest>
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoJavaTest.java
new file mode 100644
index 0000000..b00bdb0
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoJavaTest.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.credentials;
+
+import static androidx.credentials.internal.FrameworkImplHelper.getFinalCreateCredentialData;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import android.content.Context;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class CreateCredentialRequestDisplayInfoJavaTest {
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
+    @Test
+    public void constructor_nullUserId_throws() {
+        assertThrows(
+                NullPointerException.class,
+                () -> new CreateCredentialRequest.DisplayInfo(null)
+        );
+    }
+
+    @Test
+    public void constructor_emptyUserId_throws() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new CreateCredentialRequest.DisplayInfo("")
+        );
+    }
+
+    @Test
+    public void constructWithUserIdOnly_success() {
+        String expectedUserId = "userId";
+
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                new CreateCredentialRequest.DisplayInfo(expectedUserId);
+
+        assertThat(displayInfo.getUserId()).isEqualTo(expectedUserId);
+        assertThat(displayInfo.getUserDisplayName()).isNull();
+        assertThat(displayInfo.getCredentialTypeIcon()).isNull();
+    }
+
+    @Test
+    public void constructWithUserIdAndDisplayName_success() {
+        String expectedUserId = "userId";
+        String expectedDisplayName = "displayName";
+
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                new CreateCredentialRequest.DisplayInfo(expectedUserId,
+                        expectedDisplayName);
+
+        assertThat(displayInfo.getUserId()).isEqualTo(expectedUserId);
+        assertThat(displayInfo.getUserDisplayName()).isEqualTo(expectedDisplayName);
+        assertThat(displayInfo.getCredentialTypeIcon()).isNull();
+    }
+
+    @SdkSuppress(minSdkVersion = 28)
+    @Test
+    public void constructFromBundle_success() {
+        String expectedUserId = "userId";
+        CreatePasswordRequest request = new CreatePasswordRequest(expectedUserId, "password");
+
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                CreateCredentialRequest.DisplayInfo.parseFromCredentialDataBundle(
+                        getFinalCreateCredentialData(
+                                request, mContext)
+                );
+
+        assertThat(displayInfo.getUserId()).isEqualTo(expectedUserId);
+        assertThat(displayInfo.getUserDisplayName()).isNull();
+        assertThat(displayInfo.getCredentialTypeIcon().getResId()).isEqualTo(
+                R.drawable.ic_password);
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.kt
new file mode 100644
index 0000000..2fb3e1d
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCredentialRequestDisplayInfoTest.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.credentials
+
+import androidx.credentials.CreateCredentialRequest.DisplayInfo
+import androidx.credentials.CreateCredentialRequest.DisplayInfo.Companion.parseFromCredentialDataBundle
+import androidx.credentials.internal.FrameworkImplHelper.Companion.getFinalCreateCredentialData
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
+import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import org.junit.Assert.assertThrows
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class CreateCredentialRequestDisplayInfoTest {
+
+    private val mContext = InstrumentationRegistry.getInstrumentation().context
+
+    @Test
+    fun constructor_emptyUserId_throws() {
+        assertThrows(
+            IllegalArgumentException::class.java
+        ) { DisplayInfo("") }
+    }
+
+    @Test
+    fun constructWithUserIdOnly_success() {
+        val expectedUserId = "userId"
+
+        val displayInfo = DisplayInfo(expectedUserId)
+
+        assertThat(displayInfo.userId).isEqualTo(expectedUserId)
+        assertThat(displayInfo.userDisplayName).isNull()
+        assertThat(displayInfo.credentialTypeIcon).isNull()
+    }
+
+    @Test
+    fun constructWithUserIdAndDisplayName_success() {
+        val expectedUserId = "userId"
+        val expectedDisplayName = "displayName"
+
+        val displayInfo = DisplayInfo(
+            expectedUserId,
+            expectedDisplayName
+        )
+
+        assertThat(displayInfo.userId).isEqualTo(expectedUserId)
+        assertThat(displayInfo.userDisplayName).isEqualTo(expectedDisplayName)
+        assertThat(displayInfo.credentialTypeIcon).isNull()
+    }
+
+    @SdkSuppress(minSdkVersion = 28)
+    @Test
+    fun constructFromBundle_success() {
+        val expectedUserId = "userId"
+        val request = CreatePasswordRequest(expectedUserId, "password")
+
+        val displayInfo = parseFromCredentialDataBundle(
+            getFinalCreateCredentialData(
+                request, mContext
+            )
+        )
+
+        assertThat(displayInfo!!.userId).isEqualTo(expectedUserId)
+        assertThat(displayInfo.userDisplayName).isNull()
+        assertThat(displayInfo.credentialTypeIcon?.resId).isEqualTo(
+            R.drawable.ic_password
+        )
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestJavaTest.java
index 1615117..b5be84f 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestJavaTest.java
@@ -29,15 +29,26 @@
     public void constructor_nullType_throws() {
         assertThrows("Expected null type to throw NPE",
                 NullPointerException.class,
-                () -> new CreateCustomCredentialRequest(null, new Bundle(), new Bundle(), false)
+                () -> new CreateCustomCredentialRequest(null, new Bundle(), new Bundle(), false,
+                        new CreateCredentialRequest.DisplayInfo("userId"))
         );
     }
 
     @Test
-    public void constructor_nullBundle_throws() {
-        assertThrows("Expected null bundle to throw NPE",
+    public void constructor_nullCredentialData_throws() {
+        assertThrows(
                 NullPointerException.class,
-                () -> new CreateCustomCredentialRequest("T", null, new Bundle(), true)
+                () -> new CreateCustomCredentialRequest("T", null, new Bundle(), true,
+                        new CreateCredentialRequest.DisplayInfo("userId"))
+        );
+    }
+
+    @Test
+    public void constructor_nullCandidateQueryData_throws() {
+        assertThrows(
+                NullPointerException.class,
+                () -> new CreateCustomCredentialRequest("T", new Bundle(), null, true,
+                        new CreateCredentialRequest.DisplayInfo("userId"))
         );
     }
 
@@ -45,34 +56,47 @@
     public void constructor_emptyType_throws() {
         assertThrows("Expected empty type to throw IAE",
                 IllegalArgumentException.class,
-                () -> new CreateCustomCredentialRequest("", new Bundle(), new Bundle(), false)
+                () -> new CreateCustomCredentialRequest("", new Bundle(), new Bundle(), false,
+                        new CreateCredentialRequest.DisplayInfo("userId"))
         );
     }
 
     @Test
     public void constructor_nonEmptyTypeNonNullBundle_success() {
-        new CreateCustomCredentialRequest("T", new Bundle(), new Bundle(), true);
+        new CreateCustomCredentialRequest("T", new Bundle(), new Bundle(), true,
+                new CreateCredentialRequest.DisplayInfo("userId"));
     }
 
     @Test
-    public void getter_frameworkProperties() {
+    public void getter() {
         String expectedType = "TYPE";
         Bundle expectedCredentialDataBundle = new Bundle();
         expectedCredentialDataBundle.putString("Test", "Test");
         Bundle expectedCandidateQueryDataBundle = new Bundle();
         expectedCandidateQueryDataBundle.putBoolean("key", true);
-
+        CreateCredentialRequest.DisplayInfo expectedDisplayInfo =
+                new CreateCredentialRequest.DisplayInfo("userId");
         boolean expectedSystemProvider = true;
-        CreateCustomCredentialRequest option = new CreateCustomCredentialRequest(expectedType,
+
+        CreateCustomCredentialRequest request = new CreateCustomCredentialRequest(expectedType,
                 expectedCredentialDataBundle,
                 expectedCandidateQueryDataBundle,
-                expectedSystemProvider);
+                expectedSystemProvider,
+                expectedDisplayInfo);
 
-        assertThat(option.getType()).isEqualTo(expectedType);
-        assertThat(TestUtilsKt.equals(option.getCredentialData(), expectedCredentialDataBundle))
+        assertThat(request.getType()).isEqualTo(expectedType);
+        assertThat(TestUtilsKt.equals(request.getCredentialData(), expectedCredentialDataBundle))
                 .isTrue();
-        assertThat(TestUtilsKt.equals(option.getCandidateQueryData(),
+        assertThat(TestUtilsKt.equals(request.getCandidateQueryData(),
                 expectedCandidateQueryDataBundle)).isTrue();
-        assertThat(option.requireSystemProvider()).isEqualTo(expectedSystemProvider);
+        assertThat(request.isSystemProviderRequired()).isEqualTo(expectedSystemProvider);
+        assertThat(request.getDisplayInfo$credentials_debug()).isEqualTo(expectedDisplayInfo);
+    }
+
+    @Test
+    public void constructionWithNullRequestDisplayInfo_throws() {
+        assertThrows(
+                NullPointerException.class, () -> new CreateCustomCredentialRequest("type",
+                        new Bundle(), new Bundle(), false, /* requestDisplayInfo= */null));
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
index 75b2b80..5978e6d 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreateCustomCredentialRequestTest.kt
@@ -17,55 +17,53 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.credentials.CreateCredentialRequest.DisplayInfo
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assert
+import org.junit.Assert.assertThrows
 import org.junit.Test
 
 class CreateCustomCredentialRequestTest {
     @Test
     fun constructor_emptyType_throws() {
-        Assert.assertThrows(
+        assertThrows(
             "Expected empty type to throw IAE",
             IllegalArgumentException::class.java
         ) {
             CreateCustomCredentialRequest(
-                "",
-                Bundle(),
-                Bundle(),
-                true
+                "", Bundle(), Bundle(), false,
+                DisplayInfo("userId")
             )
         }
     }
 
     @Test
-    fun constructor_nonEmptyTypeNonNullBundle_success() {
-        CreateCustomCredentialRequest("T", Bundle(), Bundle(), false)
-    }
-
-    @Test
-    fun getter_frameworkProperties() {
+    fun getter() {
         val expectedType = "TYPE"
         val expectedCredentialDataBundle = Bundle()
         expectedCredentialDataBundle.putString("Test", "Test")
         val expectedCandidateQueryDataBundle = Bundle()
         expectedCandidateQueryDataBundle.putBoolean("key", true)
-
+        val expectedDisplayInfo = DisplayInfo("userId")
         val expectedSystemProvider = true
-        val option = CreateCustomCredentialRequest(
+
+        val request = CreateCustomCredentialRequest(
             expectedType,
-            expectedCredentialDataBundle, expectedCandidateQueryDataBundle,
-            expectedSystemProvider
+            expectedCredentialDataBundle,
+            expectedCandidateQueryDataBundle,
+            expectedSystemProvider,
+            expectedDisplayInfo
         )
 
-        assertThat(option.type).isEqualTo(expectedType)
-        assertThat(equals(option.credentialData, expectedCredentialDataBundle))
+        assertThat(request.type).isEqualTo(expectedType)
+        assertThat(equals(request.credentialData, expectedCredentialDataBundle))
             .isTrue()
         assertThat(
             equals(
-                option.candidateQueryData,
+                request.candidateQueryData,
                 expectedCandidateQueryDataBundle
             )
         ).isTrue()
-        assertThat(option.requireSystemProvider).isEqualTo(expectedSystemProvider)
+        assertThat(request.isSystemProviderRequired).isEqualTo(expectedSystemProvider)
+        assertThat(request.displayInfo).isEqualTo(expectedDisplayInfo)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestJavaTest.java
index a873552..63951b4 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestJavaTest.java
@@ -16,14 +16,20 @@
 
 package androidx.credentials;
 
+import static androidx.credentials.internal.FrameworkImplHelper.getFinalCreateCredentialData;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
+import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -31,6 +37,8 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CreatePasswordRequestJavaTest {
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
+
     @Test
     public void constructor_nullId_throws() {
         assertThrows(
@@ -69,6 +77,8 @@
         assertThat(request.getPassword()).isEqualTo(passwordExpected);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @SuppressWarnings("deprecation") // bundle.get(key)
     @Test
     public void getter_frameworkProperties() {
         String idExpected = "id";
@@ -80,18 +90,41 @@
         CreatePasswordRequest request = new CreatePasswordRequest(idExpected, passwordExpected);
 
         assertThat(request.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
-        assertThat(TestUtilsKt.equals(request.getCredentialData(), expectedData)).isTrue();
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                request.getDisplayInfo$credentials_debug();
+        assertThat(displayInfo.getUserDisplayName()).isNull();
+        assertThat(displayInfo.getUserId()).isEqualTo(idExpected);
         assertThat(TestUtilsKt.equals(request.getCandidateQueryData(), Bundle.EMPTY)).isTrue();
-        assertThat(request.getRequireSystemProvider()).isFalse();
+        assertThat(request.isSystemProviderRequired()).isFalse();
+        Bundle credentialData =
+                getFinalCreateCredentialData(
+                        request, mContext);
+        assertThat(credentialData.keySet())
+                .hasSize(expectedData.size() + /* added request info */ 1);
+        for (String key : expectedData.keySet()) {
+            assertThat(credentialData.get(key)).isEqualTo(credentialData.get(key));
+        }
+        Bundle displayInfoBundle =
+                credentialData.getBundle(
+                        CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO);
+        assertThat(displayInfoBundle.keySet()).hasSize(2);
+        assertThat(displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID)).isEqualTo(idExpected);
+        assertThat(((Icon) (displayInfoBundle.getParcelable(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON))).getResId()
+        ).isEqualTo(R.drawable.ic_password);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     public void frameworkConversion_success() {
-        CreatePasswordRequest request = new CreatePasswordRequest("id", "password");
+        String idExpected = "id";
+        CreatePasswordRequest request = new CreatePasswordRequest(idExpected, "password");
 
         CreateCredentialRequest convertedRequest = CreateCredentialRequest.createFrom(
-                request.getType(), request.getCredentialData(),
-                request.getCandidateQueryData(), request.getRequireSystemProvider()
+                request.getType(), getFinalCreateCredentialData(
+                        request, mContext),
+                request.getCandidateQueryData(), request.isSystemProviderRequired()
         );
 
         assertThat(convertedRequest).isInstanceOf(CreatePasswordRequest.class);
@@ -99,5 +132,11 @@
                 (CreatePasswordRequest) convertedRequest;
         assertThat(convertedCreatePasswordRequest.getPassword()).isEqualTo(request.getPassword());
         assertThat(convertedCreatePasswordRequest.getId()).isEqualTo(request.getId());
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                convertedCreatePasswordRequest.getDisplayInfo$credentials_debug();
+        assertThat(displayInfo.getUserDisplayName()).isNull();
+        assertThat(displayInfo.getUserId()).isEqualTo(idExpected);
+        assertThat(displayInfo.getCredentialTypeIcon().getResId())
+                .isEqualTo(R.drawable.ic_password);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
index 2e3279c..4329126 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePasswordRequestTest.kt
@@ -16,10 +16,14 @@
 
 package androidx.credentials
 
+import android.graphics.drawable.Icon
 import android.os.Bundle
 import androidx.credentials.CreateCredentialRequest.Companion.createFrom
+import androidx.credentials.internal.FrameworkImplHelper.Companion.getFinalCreateCredentialData
 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.assertThrows
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
@@ -28,6 +32,9 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class CreatePasswordRequestTest {
+
+    private val mContext = InstrumentationRegistry.getInstrumentation().context
+
     @Test
     fun constructor_emptyPassword_throws() {
         assertThrows<IllegalArgumentException> {
@@ -49,29 +56,56 @@
         assertThat(request.password).isEqualTo(passwordExpected)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @Suppress("DEPRECATION") // bundle.get(key)
     @Test
     fun getter_frameworkProperties() {
         val idExpected = "id"
         val passwordExpected = "pwd"
-        val expectedData = Bundle()
-        expectedData.putString(CreatePasswordRequest.BUNDLE_KEY_ID, idExpected)
-        expectedData.putString(CreatePasswordRequest.BUNDLE_KEY_PASSWORD, passwordExpected)
+        val expectedCredentialData = Bundle()
+        expectedCredentialData.putString(CreatePasswordRequest.BUNDLE_KEY_ID, idExpected)
+        expectedCredentialData.putString(
+            CreatePasswordRequest.BUNDLE_KEY_PASSWORD,
+            passwordExpected
+        )
 
         val request = CreatePasswordRequest(idExpected, passwordExpected)
 
         assertThat(request.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
-        assertThat(equals(request.credentialData, expectedData)).isTrue()
         assertThat(equals(request.candidateQueryData, Bundle.EMPTY)).isTrue()
-        assertThat(request.requireSystemProvider).isFalse()
+        assertThat(request.isSystemProviderRequired).isFalse()
+        assertThat(request.displayInfo.userDisplayName).isNull()
+        assertThat(request.displayInfo.userId).isEqualTo(idExpected)
+        val credentialData = getFinalCreateCredentialData(
+            request, mContext
+        )
+        assertThat(credentialData.keySet())
+            .hasSize(expectedCredentialData.size() + /* added request info */ 1)
+        for (key in expectedCredentialData.keySet()) {
+            assertThat(expectedCredentialData.get(key)).isEqualTo(credentialData.get(key))
+        }
+        val displayInfoBundle =
+            credentialData.getBundle(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO)!!
+        assertThat(displayInfoBundle.keySet()).hasSize(2)
+        assertThat(displayInfoBundle.getString(
+            CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID)).isEqualTo(idExpected)
+        assertThat((displayInfoBundle.getParcelable(
+            CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON) as Icon?)!!.resId
+        ).isEqualTo(R.drawable.ic_password)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
-        val request = CreatePasswordRequest("id", "password")
+        val idExpected = "id"
+        val request = CreatePasswordRequest(idExpected, "password")
 
         val convertedRequest = createFrom(
-            request.type, request.credentialData,
-            request.candidateQueryData, request.requireSystemProvider
+            request.type, getFinalCreateCredentialData(
+                request, mContext
+            ),
+            request.candidateQueryData, request.isSystemProviderRequired
         )
 
         assertThat(convertedRequest).isInstanceOf(
@@ -80,5 +114,9 @@
         val convertedCreatePasswordRequest = convertedRequest as CreatePasswordRequest
         assertThat(convertedCreatePasswordRequest.password).isEqualTo(request.password)
         assertThat(convertedCreatePasswordRequest.id).isEqualTo(request.id)
+        assertThat(convertedCreatePasswordRequest.displayInfo.userDisplayName).isNull()
+        assertThat(convertedCreatePasswordRequest.displayInfo.userId).isEqualTo(idExpected)
+        assertThat(convertedCreatePasswordRequest.displayInfo.credentialTypeIcon?.resId)
+            .isEqualTo(R.drawable.ic_password)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
index c7abdcb..838217a 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestJavaTest.java
@@ -16,17 +16,22 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_ALLOW_HYBRID;
+import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS;
 import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_REQUEST_JSON;
+import static androidx.credentials.internal.FrameworkImplHelper.getFinalCreateCredentialData;
 
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
 
+import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -34,6 +39,16 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CreatePublicKeyCredentialRequestJavaTest {
+    private static final String TEST_USERNAME = "test-user-name@gmail.com";
+    private static final String TEST_USER_DISPLAYNAME = "Test User";
+    private static final String TEST_REQUEST_JSON = String.format("{\"rp\":{\"name\":true,"
+                    + "\"id\":\"app-id\"},\"user\":{\"name\":\"%s\",\"id\":\"id-value\","
+                    + "\"displayName\":\"%s\",\"icon\":true}, \"challenge\":true,"
+                    + "\"pubKeyCredParams\":true,\"excludeCredentials\":true,"
+                    + "\"attestation\":true}", TEST_USERNAME,
+            TEST_USER_DISPLAYNAME);
+
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
     @Test
     public void constructor_emptyJson_throwsIllegalArgumentException() {
@@ -44,6 +59,14 @@
     }
 
     @Test
+    public void constructor_jsonMissingUserName_throwsIllegalArgumentException() {
+        assertThrows(
+                IllegalArgumentException.class,
+                () -> new CreatePublicKeyCredentialRequest("json")
+        );
+    }
+
+    @Test
     public void constructor_nullJson_throwsNullPointerException() {
         assertThrows("Expected null Json to throw NPE",
                 NullPointerException.class,
@@ -52,33 +75,35 @@
     }
 
     @Test
-    public void constructor_success()  {
+    public void constructor_success() {
         new CreatePublicKeyCredentialRequest(
-                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}");
+                "{\"user\":{\"name\":{\"lol\":\"Value\"}}}");
     }
 
     @Test
-    public void constructor_setsAllowHybridToTrueByDefault()  {
+    public void constructor_setsPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
-                new CreatePublicKeyCredentialRequest(
-                        "JSON");
-        boolean allowHybridActual = createPublicKeyCredentialRequest.allowHybrid();
-        assertThat(allowHybridActual).isTrue();
+                new CreatePublicKeyCredentialRequest(TEST_REQUEST_JSON);
+        boolean preferImmediatelyAvailableCredentialsActual =
+                createPublicKeyCredentialRequest.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse();
     }
 
     @Test
-    public void constructor_setsAllowHybridToFalse()  {
-        boolean allowHybridExpected = false;
+    public void constructor_setPreferImmediatelyAvailableCredentialsToTrue() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         CreatePublicKeyCredentialRequest createPublicKeyCredentialRequest =
-                new CreatePublicKeyCredentialRequest("testJson",
-                        allowHybridExpected);
-        boolean allowHybridActual = createPublicKeyCredentialRequest.allowHybrid();
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
+                new CreatePublicKeyCredentialRequest(TEST_REQUEST_JSON,
+                        preferImmediatelyAvailableCredentialsExpected);
+        boolean preferImmediatelyAvailableCredentialsActual =
+                createPublicKeyCredentialRequest.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
     public void getter_requestJson_success() {
-        String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        String testJsonExpected = "{\"user\":{\"name\":{\"lol\":\"Value\"}}}";
         CreatePublicKeyCredentialRequest createPublicKeyCredentialReq =
                 new CreatePublicKeyCredentialRequest(testJsonExpected);
 
@@ -86,10 +111,12 @@
         assertThat(testJsonActual).isEqualTo(testJsonExpected);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @SuppressWarnings("deprecation") // bundle.get(key)
     @Test
     public void getter_frameworkProperties_success() {
-        String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
-        boolean allowHybridExpected = false;
+        String requestJsonExpected = TEST_REQUEST_JSON;
+        boolean preferImmediatelyAvailableCredentialsExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
@@ -98,31 +125,59 @@
         expectedData.putString(
                 BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
         expectedData.putBoolean(
-                BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentialsExpected);
 
-        CreatePublicKeyCredentialRequest request =
-                new CreatePublicKeyCredentialRequest(requestJsonExpected, allowHybridExpected);
+        CreatePublicKeyCredentialRequest request = new CreatePublicKeyCredentialRequest(
+                requestJsonExpected, preferImmediatelyAvailableCredentialsExpected);
 
         assertThat(request.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
-        assertThat(TestUtilsKt.equals(request.getCredentialData(), expectedData)).isTrue();
         assertThat(TestUtilsKt.equals(request.getCandidateQueryData(), expectedData)).isTrue();
-        assertThat(request.getRequireSystemProvider()).isFalse();
+        assertThat(request.isSystemProviderRequired()).isFalse();
+        Bundle credentialData = getFinalCreateCredentialData(
+                request, mContext);
+        assertThat(credentialData.keySet())
+                .hasSize(expectedData.size() + /* added request info */ 1);
+        for (String key : expectedData.keySet()) {
+            assertThat(credentialData.get(key)).isEqualTo(credentialData.get(key));
+        }
+        Bundle displayInfoBundle =
+                credentialData.getBundle(
+                        CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO);
+        assertThat(displayInfoBundle.keySet()).hasSize(3);
+        assertThat(displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID)).isEqualTo(TEST_USERNAME);
+        assertThat(displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_DISPLAY_NAME
+        )).isEqualTo(TEST_USER_DISPLAYNAME);
+        assertThat(((Icon) (displayInfoBundle.getParcelable(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON))).getResId()
+        ).isEqualTo(R.drawable.ic_passkey);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     public void frameworkConversion_success() {
         CreatePublicKeyCredentialRequest request =
-                new CreatePublicKeyCredentialRequest("json", true);
+                new CreatePublicKeyCredentialRequest(TEST_REQUEST_JSON, true);
 
         CreateCredentialRequest convertedRequest = CreateCredentialRequest.createFrom(
-                request.getType(), request.getCredentialData(),
-                request.getCandidateQueryData(), request.getRequireSystemProvider()
+                request.getType(), getFinalCreateCredentialData(
+                        request, mContext),
+                request.getCandidateQueryData(), request.isSystemProviderRequired()
         );
 
         assertThat(convertedRequest).isInstanceOf(CreatePublicKeyCredentialRequest.class);
         CreatePublicKeyCredentialRequest convertedSubclassRequest =
                 (CreatePublicKeyCredentialRequest) convertedRequest;
         assertThat(convertedSubclassRequest.getRequestJson()).isEqualTo(request.getRequestJson());
-        assertThat(convertedSubclassRequest.allowHybrid()).isEqualTo(request.allowHybrid());
+        assertThat(convertedSubclassRequest.preferImmediatelyAvailableCredentials())
+                .isEqualTo(request.preferImmediatelyAvailableCredentials());
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                convertedRequest.getDisplayInfo$credentials_debug();
+        assertThat(displayInfo.getUserDisplayName()).isEqualTo(TEST_USER_DISPLAYNAME);
+        assertThat(displayInfo.getUserId()).isEqualTo(TEST_USERNAME);
+        assertThat(displayInfo.getCredentialTypeIcon().getResId())
+                .isEqualTo(R.drawable.ic_passkey);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
index 623ae8d..85901a3 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedJavaTest.java
@@ -16,17 +16,22 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_ALLOW_HYBRID;
+import static androidx.credentials.CreatePublicKeyCredentialRequest.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS;
 import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH;
 import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RELYING_PARTY;
 import static androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_REQUEST_JSON;
+import static androidx.credentials.internal.FrameworkImplHelper.getFinalCreateCredentialData;
 
 import static com.google.common.truth.Truth.assertThat;
 
+import android.content.Context;
+import android.graphics.drawable.Icon;
 import android.os.Bundle;
 
 import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
+import androidx.test.platform.app.InstrumentationRegistry;
 
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -38,57 +43,72 @@
 @RunWith(AndroidJUnit4.class)
 @SmallTest
 public class CreatePublicKeyCredentialRequestPrivilegedJavaTest {
+    private static final String TEST_USERNAME = "test-user-name@gmail.com";
+    private static final String TEST_USER_DISPLAYNAME = "Test User";
+    private static final String TEST_REQUEST_JSON = String.format("{\"rp\":{\"name\":true,"
+                    + "\"id\":\"app-id\"},\"user\":{\"name\":\"%s\",\"id\":\"id-value\","
+                    + "\"displayName\":\"%s\",\"icon\":true}, \"challenge\":true,"
+                    + "\"pubKeyCredParams\":true,\"excludeCredentials\":true,"
+                    + "\"attestation\":true}", TEST_USERNAME,
+            TEST_USER_DISPLAYNAME);
+    private Context mContext = InstrumentationRegistry.getInstrumentation().getContext();
 
     @Test
     public void constructor_success() {
         new CreatePublicKeyCredentialRequestPrivileged(
-                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                "{\"user\":{\"name\":{\"lol\":\"Value\"}}}",
                 "relyingParty", "ClientDataHash");
     }
 
     @Test
-    public void constructor_setsAllowHybridToTrueByDefault() {
+    public void constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        "JSON", "relyingParty", "HASH");
-        boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
-        assertThat(allowHybridActual).isTrue();
+                        TEST_REQUEST_JSON, "relyingParty", "HASH");
+        boolean preferImmediatelyAvailableCredentialsActual =
+                createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse();
     }
 
     @Test
-    public void constructor_setsAllowHybridToFalse() {
-        boolean allowHybridExpected = false;
+    public void constructor_setPreferImmediatelyAvailableCredentialsToTrue() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
-                new CreatePublicKeyCredentialRequestPrivileged("JSON",
+                new CreatePublicKeyCredentialRequestPrivileged(TEST_REQUEST_JSON,
                         "relyingParty",
                         "HASH",
-                        allowHybridExpected);
-        boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
+                        preferImmediatelyAvailableCredentialsExpected);
+        boolean preferImmediatelyAvailableCredentialsActual =
+                createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
-    public void builder_build_defaultAllowHybrid_true() {
+    public void builder_build_defaultPreferImmediatelyAvailableCredentials_false() {
         CreatePublicKeyCredentialRequestPrivileged defaultPrivilegedRequest = new
-                CreatePublicKeyCredentialRequestPrivileged.Builder("{\"Data\":5}",
+                CreatePublicKeyCredentialRequestPrivileged.Builder(TEST_REQUEST_JSON,
                 "relyingParty", "HASH").build();
-        assertThat(defaultPrivilegedRequest.allowHybrid()).isTrue();
+        assertThat(defaultPrivilegedRequest.preferImmediatelyAvailableCredentials()).isFalse();
     }
 
     @Test
-    public void builder_build_nonDefaultAllowHybrid_false() {
-        boolean allowHybridExpected = false;
+    public void builder_build_nonDefaultPreferImmediatelyAvailableCredentials_true() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
-                new CreatePublicKeyCredentialRequestPrivileged.Builder("JSON",
+                new CreatePublicKeyCredentialRequestPrivileged.Builder(TEST_REQUEST_JSON,
                         "relyingParty", "HASH")
-                        .setAllowHybrid(allowHybridExpected).build();
-        boolean allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid();
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
+                        .setPreferImmediatelyAvailableCredentials(
+                                preferImmediatelyAvailableCredentialsExpected).build();
+        boolean preferImmediatelyAvailableCredentialsActual =
+                createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
     public void getter_requestJson_success() {
-        String testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        String testJsonExpected = "{\"user\":{\"name\":{\"lol\":\"Value\"}}}";
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialReqPriv =
                 new CreatePublicKeyCredentialRequestPrivileged(testJsonExpected,
                         "relyingParty", "HASH");
@@ -98,14 +118,13 @@
 
     @Test
     public void getter_relyingParty_success() {
-        String testrelyingPartyExpected = "relyingParty";
+        String testRelyingPartyExpected = "relyingParty";
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        testrelyingPartyExpected, "X342%4dfd7&");
-        String testrelyingPartyActual = createPublicKeyCredentialRequestPrivileged
+                        TEST_REQUEST_JSON, testRelyingPartyExpected, "X342%4dfd7&");
+        String testRelyingPartyActual = createPublicKeyCredentialRequestPrivileged
                 .getRelyingParty();
-        assertThat(testrelyingPartyActual).isEqualTo(testrelyingPartyExpected);
+        assertThat(testRelyingPartyActual).isEqualTo(testRelyingPartyExpected);
     }
 
     @Test
@@ -113,49 +132,72 @@
         String clientDataHashExpected = "X342%4dfd7&";
         CreatePublicKeyCredentialRequestPrivileged createPublicKeyCredentialRequestPrivileged =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                         "relyingParty", clientDataHashExpected);
+                        TEST_REQUEST_JSON, "relyingParty", clientDataHashExpected);
         String clientDataHashActual =
                 createPublicKeyCredentialRequestPrivileged.getClientDataHash();
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @SuppressWarnings("deprecation") // bundle.get(key)
     @Test
     public void getter_frameworkProperties_success() {
-        String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
+        String requestJsonExpected = TEST_REQUEST_JSON;
         String relyingPartyExpected = "relyingParty";
         String clientDataHashExpected = "X342%4dfd7&";
-        boolean allowHybridExpected = false;
+        boolean preferImmediatelyAvailableCredentialsExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
                 CreatePublicKeyCredentialRequestPrivileged
-                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED);
+                        .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV);
         expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
         expectedData.putString(BUNDLE_KEY_RELYING_PARTY, relyingPartyExpected);
         expectedData.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHashExpected);
-        expectedData.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
+        expectedData.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentialsExpected);
 
         CreatePublicKeyCredentialRequestPrivileged request =
                 new CreatePublicKeyCredentialRequestPrivileged(
                         requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
-                        allowHybridExpected);
+                        preferImmediatelyAvailableCredentialsExpected);
 
         assertThat(request.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
-        assertThat(TestUtilsKt.equals(request.getCredentialData(), expectedData)).isTrue();
         assertThat(TestUtilsKt.equals(request.getCandidateQueryData(), expectedData)).isTrue();
-        assertThat(request.getRequireSystemProvider()).isFalse();
+        assertThat(request.isSystemProviderRequired()).isFalse();
+        Bundle credentialData = getFinalCreateCredentialData(
+                request, mContext);
+        assertThat(credentialData.keySet())
+                .hasSize(expectedData.size() + /* added request info */ 1);
+        for (String key : expectedData.keySet()) {
+            assertThat(credentialData.get(key)).isEqualTo(credentialData.get(key));
+        }
+        Bundle displayInfoBundle =
+                credentialData.getBundle(
+                        CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO);
+        assertThat(displayInfoBundle.keySet()).hasSize(3);
+        assertThat(displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID)).isEqualTo(TEST_USERNAME);
+        assertThat(displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_DISPLAY_NAME)).isEqualTo(
+                TEST_USER_DISPLAYNAME);
+        assertThat(((Icon) (displayInfoBundle.getParcelable(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON))).getResId()
+        ).isEqualTo(R.drawable.ic_passkey);
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     public void frameworkConversion_success() {
         CreatePublicKeyCredentialRequestPrivileged request =
                 new CreatePublicKeyCredentialRequestPrivileged(
-                        "json", "rp", "clientDataHash", true);
+                        TEST_REQUEST_JSON, "rp", "clientDataHash", true);
 
         CreateCredentialRequest convertedRequest = CreateCredentialRequest.createFrom(
-                request.getType(), request.getCredentialData(),
-                request.getCandidateQueryData(), request.getRequireSystemProvider()
+                request.getType(), getFinalCreateCredentialData(
+                        request, mContext),
+                request.getCandidateQueryData(), request.isSystemProviderRequired()
         );
 
         assertThat(convertedRequest).isInstanceOf(CreatePublicKeyCredentialRequestPrivileged.class);
@@ -165,6 +207,13 @@
         assertThat(convertedSubclassRequest.getRelyingParty()).isEqualTo(request.getRelyingParty());
         assertThat(convertedSubclassRequest.getClientDataHash())
                 .isEqualTo(request.getClientDataHash());
-        assertThat(convertedSubclassRequest.allowHybrid()).isEqualTo(request.allowHybrid());
+        assertThat(convertedSubclassRequest.preferImmediatelyAvailableCredentials()).isEqualTo(
+                request.preferImmediatelyAvailableCredentials());
+        CreateCredentialRequest.DisplayInfo displayInfo =
+                convertedRequest.getDisplayInfo$credentials_debug();
+        assertThat(displayInfo.getUserDisplayName()).isEqualTo(TEST_USER_DISPLAYNAME);
+        assertThat(displayInfo.getUserId()).isEqualTo(TEST_USERNAME);
+        assertThat(displayInfo.getCredentialTypeIcon().getResId())
+                .isEqualTo(R.drawable.ic_passkey);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
index d766191..63bee02 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivilegedTest.kt
@@ -16,10 +16,15 @@
 
 package androidx.credentials
 
+import android.graphics.drawable.Icon
 import android.os.Bundle
+import android.os.Parcelable
 import androidx.credentials.CreateCredentialRequest.Companion.createFrom
+import androidx.credentials.internal.FrameworkImplHelper.Companion.getFinalCreateCredentialData
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -30,62 +35,82 @@
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class CreatePublicKeyCredentialRequestPrivilegedTest {
+    private val mContext = InstrumentationRegistry.getInstrumentation().context
+
+    companion object Constant {
+        private const val TEST_USERNAME = "test-user-name@gmail.com"
+        private const val TEST_USER_DISPLAYNAME = "Test User"
+        private const val TEST_REQUEST_JSON = "{\"rp\":{\"name\":true,\"id\":\"app-id\"}," +
+            "\"user\":{\"name\":\"$TEST_USERNAME\",\"id\":\"id-value\",\"displayName" +
+            "\":\"$TEST_USER_DISPLAYNAME\",\"icon\":true}, \"challenge\":true," +
+            "\"pubKeyCredParams\":true,\"excludeCredentials\":true," + "\"attestation\":true}"
+    }
 
     @Test
     fun constructor_success() {
         CreatePublicKeyCredentialRequestPrivileged(
-            "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-            "RelyingParty", "ClientDataHash"
+            TEST_REQUEST_JSON, "RelyingParty", "ClientDataHash"
         )
     }
 
     @Test
-    fun constructor_setsAllowHybridToTrueByDefault() {
+    fun constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged(
-            "JSON", "RelyingParty", "HASH"
+            TEST_REQUEST_JSON, "RelyingParty", "HASH"
         )
-        val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
-        assertThat(allowHybridActual).isTrue()
+        val preferImmediatelyAvailableCredentialsActual =
+            createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse()
     }
 
     @Test
-    fun constructor_setsAllowHybridToFalse() {
-        val allowHybridExpected = false
+    fun constructor_setPreferImmediatelyAvailableCredentialsToTrue() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged(
-            "testJson",
-            "RelyingParty", "Hash", allowHybridExpected
+            TEST_REQUEST_JSON,
+            "RelyingParty",
+            "Hash",
+            preferImmediatelyAvailableCredentialsExpected
         )
-        val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
+        val preferImmediatelyAvailableCredentialsActual =
+            createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+            preferImmediatelyAvailableCredentialsExpected
+        )
     }
 
     @Test
-    fun builder_build_defaultAllowHybrid_true() {
+    fun builder_build_defaultPreferImmediatelyAvailableCredentials_false() {
         val defaultPrivilegedRequest = CreatePublicKeyCredentialRequestPrivileged.Builder(
-            "{\"Data\":5}",
-            "RelyingParty", "HASH"
+            TEST_REQUEST_JSON, "RelyingParty", "HASH"
         ).build()
-        assertThat(defaultPrivilegedRequest.allowHybrid).isTrue()
+        assertThat(defaultPrivilegedRequest.preferImmediatelyAvailableCredentials).isFalse()
     }
 
     @Test
-    fun builder_build_nonDefaultAllowHybrid_false() {
-        val allowHybridExpected = false
+    fun builder_build_nonDefaultPreferImmediatelyAvailableCredentials_true() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val createPublicKeyCredentialRequestPrivileged = CreatePublicKeyCredentialRequestPrivileged
             .Builder(
-                "testJson",
-                "RelyingParty", "Hash"
-            ).setAllowHybrid(allowHybridExpected).build()
-        val allowHybridActual = createPublicKeyCredentialRequestPrivileged.allowHybrid
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
+                TEST_REQUEST_JSON, "RelyingParty", "Hash"
+            )
+            .setPreferImmediatelyAvailableCredentials(preferImmediatelyAvailableCredentialsExpected)
+            .build()
+        val preferImmediatelyAvailableCredentialsActual =
+            createPublicKeyCredentialRequestPrivileged.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+            preferImmediatelyAvailableCredentialsExpected
+        )
     }
 
     @Test
     fun getter_requestJson_success() {
-        val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
+        val testJsonExpected = "{\"user\":{\"name\":{\"lol\":\"Value\"}}}"
         val createPublicKeyCredentialReqPriv =
-            CreatePublicKeyCredentialRequestPrivileged(testJsonExpected, "RelyingParty",
-                "HASH")
+            CreatePublicKeyCredentialRequestPrivileged(
+                testJsonExpected, "RelyingParty",
+                "HASH"
+            )
         val testJsonActual = createPublicKeyCredentialReqPriv.requestJson
         assertThat(testJsonActual).isEqualTo(testJsonExpected)
     }
@@ -95,8 +120,7 @@
         val testRelyingPartyExpected = "RelyingParty"
         val createPublicKeyCredentialRequestPrivileged =
             CreatePublicKeyCredentialRequestPrivileged(
-                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                testRelyingPartyExpected, "X342%4dfd7&"
+                TEST_REQUEST_JSON, testRelyingPartyExpected, "X342%4dfd7&"
             )
         val testRelyingPartyActual = createPublicKeyCredentialRequestPrivileged.relyingParty
         assertThat(testRelyingPartyActual).isEqualTo(testRelyingPartyExpected)
@@ -107,62 +131,94 @@
         val clientDataHashExpected = "X342%4dfd7&"
         val createPublicKeyCredentialRequestPrivileged =
             CreatePublicKeyCredentialRequestPrivileged(
-                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                "RelyingParty", clientDataHashExpected
+                TEST_REQUEST_JSON, "RelyingParty", clientDataHashExpected
             )
         val clientDataHashActual = createPublicKeyCredentialRequestPrivileged.clientDataHash
         assertThat(clientDataHashActual).isEqualTo(clientDataHashExpected)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @Suppress("DEPRECATION") // bundle.get(key)
     @Test
     fun getter_frameworkProperties_success() {
-        val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
+        val requestJsonExpected = TEST_REQUEST_JSON
         val relyingPartyExpected = "RelyingParty"
         val clientDataHashExpected = "X342%4dfd7&"
-        val allowHybridExpected = false
+        val preferImmediatelyAvailableCredentialsExpected = false
         val expectedData = Bundle()
         expectedData.putString(
             PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
             CreatePublicKeyCredentialRequestPrivileged
-                .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED
+                .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV
         )
         expectedData.putString(
             CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_REQUEST_JSON,
             requestJsonExpected
         )
-        expectedData.putString(CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RELYING_PARTY,
-            relyingPartyExpected)
+        expectedData.putString(
+            CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_RELYING_PARTY,
+            relyingPartyExpected
+        )
         expectedData.putString(
             CreatePublicKeyCredentialRequestPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH,
             clientDataHashExpected
         )
         expectedData.putBoolean(
-            CreatePublicKeyCredentialRequest.BUNDLE_KEY_ALLOW_HYBRID,
-            allowHybridExpected
+            CreatePublicKeyCredentialRequest.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         val request = CreatePublicKeyCredentialRequestPrivileged(
             requestJsonExpected,
             relyingPartyExpected,
             clientDataHashExpected,
-            allowHybridExpected
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         assertThat(request.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
-        assertThat(equals(request.credentialData, expectedData)).isTrue()
         assertThat(equals(request.candidateQueryData, expectedData)).isTrue()
-        assertThat(request.requireSystemProvider).isFalse()
+        assertThat(request.isSystemProviderRequired).isFalse()
+        val credentialData = getFinalCreateCredentialData(
+            request, mContext
+        )
+        assertThat(credentialData.keySet())
+            .hasSize(expectedData.size() + /* added request info */1)
+        for (key in expectedData.keySet()) {
+            assertThat(credentialData[key]).isEqualTo(credentialData[key])
+        }
+        val displayInfoBundle = credentialData.getBundle(
+            CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO
+        )!!
+        assertThat(displayInfoBundle.keySet()).hasSize(3)
+        assertThat(
+            displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID
+            )
+        ).isEqualTo(TEST_USERNAME)
+        assertThat(
+            displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_DISPLAY_NAME
+            )
+        ).isEqualTo(TEST_USER_DISPLAYNAME)
+        assertThat(
+            (displayInfoBundle.getParcelable<Parcelable>(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON
+            ) as Icon?)!!.resId
+        ).isEqualTo(R.drawable.ic_passkey)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
         val request = CreatePublicKeyCredentialRequestPrivileged(
-            "json", "rp", "clientDataHash", true
+            TEST_REQUEST_JSON, "rp", "clientDataHash", true
         )
 
         val convertedRequest = createFrom(
-            request.type, request.credentialData,
-            request.candidateQueryData, request.requireSystemProvider
+            request.type, getFinalCreateCredentialData(
+                request, mContext
+            ),
+            request.candidateQueryData, request.isSystemProviderRequired
         )
 
         assertThat(convertedRequest).isInstanceOf(
@@ -174,6 +230,12 @@
         assertThat(convertedSubclassRequest.relyingParty).isEqualTo(request.relyingParty)
         assertThat(convertedSubclassRequest.clientDataHash)
             .isEqualTo(request.clientDataHash)
-        assertThat(convertedSubclassRequest.allowHybrid).isEqualTo(request.allowHybrid)
+        assertThat(convertedSubclassRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(request.preferImmediatelyAvailableCredentials)
+        val displayInfo = convertedRequest.displayInfo
+        assertThat(displayInfo.userDisplayName).isEqualTo(TEST_USER_DISPLAYNAME)
+        assertThat(displayInfo.userId).isEqualTo(TEST_USERNAME)
+        assertThat(displayInfo.credentialTypeIcon?.resId)
+            .isEqualTo(R.drawable.ic_passkey)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
index d133451..70a4f6b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CreatePublicKeyCredentialRequestTest.kt
@@ -16,68 +16,87 @@
 
 package androidx.credentials
 
+import android.graphics.drawable.Icon
 import android.os.Bundle
+import android.os.Parcelable
 import androidx.credentials.CreateCredentialRequest.Companion.createFrom
-import androidx.credentials.CreatePublicKeyCredentialRequest.Companion.BUNDLE_KEY_ALLOW_HYBRID
+import androidx.credentials.CreatePublicKeyCredentialRequest.Companion.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS
 import androidx.credentials.CreatePublicKeyCredentialRequest.Companion.BUNDLE_KEY_REQUEST_JSON
+import androidx.credentials.internal.FrameworkImplHelper.Companion.getFinalCreateCredentialData
 import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
-import org.junit.Assert
+import org.junit.Assert.assertThrows
 import org.junit.Test
 import org.junit.runner.RunWith
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
 class CreatePublicKeyCredentialRequestTest {
+    private val mContext = InstrumentationRegistry.getInstrumentation().context
+    companion object Constant {
+        private const val TEST_USERNAME = "test-user-name@gmail.com"
+        private const val TEST_USER_DISPLAYNAME = "Test User"
+        private const val TEST_REQUEST_JSON = "{\"rp\":{\"name\":true,\"id\":\"app-id\"}," +
+            "\"user\":{\"name\":\"$TEST_USERNAME\",\"id\":\"id-value\",\"displayName" +
+            "\":\"$TEST_USER_DISPLAYNAME\",\"icon\":true}, \"challenge\":true," +
+            "\"pubKeyCredParams\":true,\"excludeCredentials\":true," + "\"attestation\":true}"
+    }
 
     @Test
     fun constructor_emptyJson_throwsIllegalArgumentException() {
-        Assert.assertThrows(
+        assertThrows(
             "Expected empty Json to throw error",
             IllegalArgumentException::class.java
         ) { CreatePublicKeyCredentialRequest("") }
     }
 
     @Test
-    fun constructor_success() {
-        CreatePublicKeyCredentialRequest(
-            "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
-        )
+    fun constructor_jsonMissingUserName_throwsIllegalArgumentException() {
+        assertThrows(
+            IllegalArgumentException::class.java
+        ) { CreatePublicKeyCredentialRequest("json") }
     }
 
     @Test
-    fun constructor_setsAllowHybridToTrueByDefault() {
+    fun constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
-            "JSON"
+            TEST_REQUEST_JSON
         )
-        val allowHybridActual = createPublicKeyCredentialRequest.allowHybrid
-        assertThat(allowHybridActual).isTrue()
+        val preferImmediatelyAvailableCredentialsActual =
+            createPublicKeyCredentialRequest.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse()
     }
 
     @Test
-    fun constructor_setsAllowHybridToFalse() {
-        val allowHybridExpected = false
+    fun constructor_setPreferImmediatelyAvailableCredentialsToTrue() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val createPublicKeyCredentialRequest = CreatePublicKeyCredentialRequest(
-            "testJson",
-            allowHybridExpected
+            TEST_REQUEST_JSON,
+            preferImmediatelyAvailableCredentialsExpected
         )
-        val allowHybridActual = createPublicKeyCredentialRequest.allowHybrid
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
+        val preferImmediatelyAvailableCredentialsActual =
+            createPublicKeyCredentialRequest.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual)
+            .isEqualTo(preferImmediatelyAvailableCredentialsExpected)
     }
 
     @Test
     fun getter_requestJson_success() {
-        val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
+        val testJsonExpected = "{\"user\":{\"name\":{\"lol\":\"Value\"}}}"
         val createPublicKeyCredentialReq = CreatePublicKeyCredentialRequest(testJsonExpected)
         val testJsonActual = createPublicKeyCredentialReq.requestJson
         assertThat(testJsonActual).isEqualTo(testJsonExpected)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
+    @Suppress("DEPRECATION") // bundle.get(key)
     @Test
     fun getter_frameworkProperties_success() {
-        val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
-        val allowHybridExpected = false
+        val requestJsonExpected = TEST_REQUEST_JSON
+        val preferImmediatelyAvailableCredentialsExpected = false
         val expectedData = Bundle()
         expectedData.putString(
             PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
@@ -88,27 +107,57 @@
             BUNDLE_KEY_REQUEST_JSON, requestJsonExpected
         )
         expectedData.putBoolean(
-            BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected
+            BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         val request = CreatePublicKeyCredentialRequest(
             requestJsonExpected,
-            allowHybridExpected
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         assertThat(request.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
-        assertThat(equals(request.credentialData, expectedData)).isTrue()
         assertThat(equals(request.candidateQueryData, expectedData)).isTrue()
-        assertThat(request.requireSystemProvider).isFalse()
+        assertThat(request.isSystemProviderRequired).isFalse()
+        val credentialData = getFinalCreateCredentialData(
+            request, mContext
+        )
+        assertThat(credentialData.keySet())
+            .hasSize(expectedData.size() + /* added request info */1)
+        for (key in expectedData.keySet()) {
+            assertThat(credentialData[key]).isEqualTo(credentialData[key])
+        }
+        val displayInfoBundle = credentialData.getBundle(
+            CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO
+        )!!
+        assertThat(displayInfoBundle.keySet()).hasSize(3)
+        assertThat(
+            displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_ID
+            )
+        ).isEqualTo(TEST_USERNAME)
+        assertThat(
+            displayInfoBundle.getString(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_USER_DISPLAY_NAME
+            )
+        ).isEqualTo(TEST_USER_DISPLAYNAME)
+        assertThat(
+            (displayInfoBundle.getParcelable<Parcelable>(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON
+            ) as Icon?)!!.resId
+        ).isEqualTo(R.drawable.ic_passkey)
     }
 
+    @SdkSuppress(minSdkVersion = 28)
     @Test
     fun frameworkConversion_success() {
-        val request = CreatePublicKeyCredentialRequest("json", true)
+        val request = CreatePublicKeyCredentialRequest(TEST_REQUEST_JSON, true)
 
         val convertedRequest = createFrom(
-            request.type, request.credentialData,
-            request.candidateQueryData, request.requireSystemProvider
+            request.type, getFinalCreateCredentialData(
+                request, mContext
+            ),
+            request.candidateQueryData, request.isSystemProviderRequired
         )
 
         assertThat(convertedRequest).isInstanceOf(
@@ -116,6 +165,12 @@
         )
         val convertedSubclassRequest = convertedRequest as CreatePublicKeyCredentialRequest
         assertThat(convertedSubclassRequest.requestJson).isEqualTo(request.requestJson)
-        assertThat(convertedSubclassRequest.allowHybrid).isEqualTo(request.allowHybrid)
+        assertThat(convertedSubclassRequest.preferImmediatelyAvailableCredentials)
+            .isEqualTo(request.preferImmediatelyAvailableCredentials)
+        val displayInfo = convertedRequest.displayInfo
+        assertThat(displayInfo.userDisplayName).isEqualTo(TEST_USER_DISPLAYNAME)
+        assertThat(displayInfo.userId).isEqualTo(TEST_USERNAME)
+        assertThat(displayInfo.credentialTypeIcon?.resId)
+            .isEqualTo(R.drawable.ic_passkey)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
index d01bf20..85246a1 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerJavaTest.java
@@ -16,6 +16,8 @@
 
 package androidx.credentials;
 
+import static androidx.credentials.TestUtilsKt.isPostFrameworkApiLevel;
+
 import static com.google.common.truth.Truth.assertThat;
 
 import android.app.Activity;
@@ -29,6 +31,7 @@
 import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException;
 import androidx.credentials.exceptions.GetCredentialException;
 import androidx.credentials.exceptions.GetCredentialProviderConfigurationException;
+import androidx.test.core.app.ActivityScenario;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SmallTest;
 import androidx.test.platform.app.InstrumentationRegistry;
@@ -37,6 +40,8 @@
 import org.junit.Test;
 import org.junit.runner.RunWith;
 
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.TimeUnit;
 import java.util.concurrent.atomic.AtomicReference;
 
 @RunWith(AndroidJUnit4.class)
@@ -53,39 +58,52 @@
     }
 
     @Test
-    public void testCreateCredentialAsyc_successCallbackThrows() {
+    public void testCreateCredentialAsyc_successCallbackThrows() throws InterruptedException {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<CreateCredentialException> loadedResult = new AtomicReference<>();
-        mCredentialManager.executeCreateCredentialAsync(
-                new CreatePasswordRequest("test-user-id", "test-password"),
-                new Activity(),
-                null,
-                Runnable::run,
-                new CredentialManagerCallback<CreateCredentialResponse,
-                        CreateCredentialException>() {
-                    @Override
-                    public void onError(@NonNull CreateCredentialException e) {
-                        loadedResult.set(e);
-                    }
-                    @Override
-                    public void onResult(@NonNull CreateCredentialResponse result) {}
-            });
+        ActivityScenario<TestActivity> activityScenario =
+                ActivityScenario.launch(TestActivity.class);
+        activityScenario.onActivity(activity -> {
+            mCredentialManager.createCredentialAsync(
+                    new CreatePasswordRequest("test-user-id", "test-password"),
+                    activity,
+                    null,
+                    Runnable::run,
+                    new CredentialManagerCallback<CreateCredentialResponse,
+                            CreateCredentialException>() {
+                        @Override
+                        public void onError(@NonNull CreateCredentialException e) {
+                            loadedResult.set(e);
+                            latch.countDown();
+                        }
+
+                        @Override
+                        public void onResult(@NonNull CreateCredentialResponse result) {}
+                    });
+        });
+
+        latch.await(100L, TimeUnit.MILLISECONDS);
         assertThat(loadedResult.get().getClass()).isEqualTo(
                 CreateCredentialProviderConfigurationException.class);
-        // TODO("Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO("Add manifest tests and possibly further separate these tests by API Level
+        //  - maybe a rule perhaps?")
     }
 
+
     @Test
-    public void testGetCredentialAsyc_successCallbackThrows() {
+    public void testGetCredentialAsyc_successCallbackThrows() throws InterruptedException {
         if (Looper.myLooper() == null) {
             Looper.prepare();
         }
+        CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<GetCredentialException> loadedResult = new AtomicReference<>();
-        mCredentialManager.executeGetCredentialAsync(
+
+        mCredentialManager.getCredentialAsync(
                 new GetCredentialRequest.Builder()
-                        .addGetCredentialOption(new GetPasswordOption())
+                        .addCredentialOption(new GetPasswordOption())
                         .build(),
                 new Activity(),
                 null,
@@ -95,21 +113,26 @@
                 @Override
                 public void onError(@NonNull GetCredentialException e) {
                     loadedResult.set(e);
+                    latch.countDown();
                 }
 
                 @Override
                 public void onResult(@NonNull GetCredentialResponse result) {}
             });
+
+        latch.await(100L, TimeUnit.MILLISECONDS);
         assertThat(loadedResult.get().getClass()).isEqualTo(
                 GetCredentialProviderConfigurationException.class);
-        // TODO("Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO("Add manifest tests and possibly further separate these tests - maybe a rule
+        //  perhaps?")
     }
 
     @Test
-    public void testClearCredentialSessionAsync_throws() {
-        if (Looper.myLooper() == null) {
-            Looper.prepare();
+    public void testClearCredentialSessionAsync_throws() throws InterruptedException {
+        if (isPostFrameworkApiLevel()) {
+            return; // TODO(Support!)
         }
+        CountDownLatch latch = new CountDownLatch(1);
         AtomicReference<ClearCredentialException> loadedResult = new AtomicReference<>();
 
         mCredentialManager.clearCredentialStateAsync(
@@ -121,13 +144,16 @@
                     @Override
                     public void onError(@NonNull ClearCredentialException e) {
                         loadedResult.set(e);
+                        latch.countDown();
                     }
 
                     @Override
                     public void onResult(@NonNull Void result) {}
                 });
+
+        latch.await(100L, TimeUnit.MILLISECONDS);
         assertThat(loadedResult.get().getClass()).isEqualTo(
                 ClearCredentialProviderConfigurationException.class);
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO(Add manifest tests and split this once postU is implemented for clearCreds")
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
index ef0cebf..162d6df 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/CredentialManagerTest.kt
@@ -24,12 +24,15 @@
 import androidx.credentials.exceptions.CreateCredentialProviderConfigurationException
 import androidx.credentials.exceptions.GetCredentialException
 import androidx.credentials.exceptions.GetCredentialProviderConfigurationException
+import androidx.test.core.app.ActivityScenario
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.testutils.assertThrows
 import com.google.common.truth.Truth.assertThat
+import java.util.concurrent.CountDownLatch
 import java.util.concurrent.Executor
+import java.util.concurrent.TimeUnit
 import java.util.concurrent.atomic.AtomicReference
 import kotlinx.coroutines.runBlocking
 import org.junit.Before
@@ -53,13 +56,16 @@
         if (Looper.myLooper() == null) {
             Looper.prepare()
         }
-        assertThrows<CreateCredentialProviderConfigurationException> {
-            credentialManager.executeCreateCredential(
-                CreatePasswordRequest("test-user-id", "test-password"),
-                Activity()
-            )
+        if (!isPostFrameworkApiLevel()) {
+            assertThrows<CreateCredentialProviderConfigurationException> {
+                credentialManager.createCredential(
+                    CreatePasswordRequest("test-user-id", "test-password"),
+                    Activity()
+                )
+            }
         }
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO("Add manifest tests and possibly further separate these tests by API Level
+        //  - maybe a rule perhaps?")
     }
 
     @Test
@@ -68,12 +74,16 @@
             Looper.prepare()
         }
         val request = GetCredentialRequest.Builder()
-            .addGetCredentialOption(GetPasswordOption())
+            .addCredentialOption(GetPasswordOption())
             .build()
-        assertThrows<GetCredentialProviderConfigurationException> {
-            credentialManager.executeGetCredential(request, Activity())
+
+        if (!isPostFrameworkApiLevel()) {
+            assertThrows<GetCredentialProviderConfigurationException> {
+                credentialManager.getCredential(request, Activity())
+            }
         }
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO("Add manifest tests and possibly further separate these tests by API Level
+        //  - maybe a rule perhaps?")
     }
 
     @Test
@@ -81,10 +91,14 @@
         if (Looper.myLooper() == null) {
             Looper.prepare()
         }
-        assertThrows<ClearCredentialProviderConfigurationException> {
-            credentialManager.clearCredentialState(ClearCredentialStateRequest())
+
+        if (!isPostFrameworkApiLevel()) {
+            assertThrows<ClearCredentialProviderConfigurationException> {
+                credentialManager.clearCredentialState(ClearCredentialStateRequest())
+            }
         }
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO("Add manifest tests and possibly further separate these tests by API Level
+        //  - maybe a rule perhaps?")
     }
 
     @Test
@@ -92,22 +106,35 @@
         if (Looper.myLooper() == null) {
             Looper.prepare()
         }
+        val latch = CountDownLatch(1)
         val loadedResult: AtomicReference<CreateCredentialException> = AtomicReference()
-        credentialManager.executeCreateCredentialAsync(
-            request = CreatePasswordRequest("test-user-id", "test-password"),
-            activity = Activity(),
-            cancellationSignal = null,
-            executor = Runnable::run,
-            callback = object : CredentialManagerCallback<CreateCredentialResponse,
-                CreateCredentialException> {
-                override fun onResult(result: CreateCredentialResponse) {}
-                override fun onError(e: CreateCredentialException) { loadedResult.set(e) }
-            }
+        val activityScenario = ActivityScenario.launch(
+            TestActivity::class.java
         )
-        assertThat(loadedResult.get().type).isEqualTo(
-            CreateCredentialProviderConfigurationException
-            .TYPE_CREATE_CREDENTIAL_PROVIDER_CONFIGURATION_EXCEPTION)
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+
+        activityScenario.onActivity { activity ->
+            credentialManager.createCredentialAsync(
+                CreatePasswordRequest("test-user-id", "test-password"),
+                activity,
+                null, Executor { obj: Runnable -> obj.run() },
+                object : CredentialManagerCallback<CreateCredentialResponse,
+                    CreateCredentialException> {
+                    override fun onResult(result: CreateCredentialResponse) {}
+                    override fun onError(e: CreateCredentialException) {
+                        loadedResult.set(e)
+                        latch.countDown()
+                    }
+                })
+        }
+
+        latch.await(100L, TimeUnit.MILLISECONDS)
+        if (!isPostFrameworkApiLevel()) {
+            assertThat(loadedResult.get().javaClass).isEqualTo(
+                CreateCredentialProviderConfigurationException::class.java
+            )
+        }
+        // TODO("Add manifest tests and possibly further separate these tests by API Level
+        //  - maybe a rule perhaps?")
     }
 
     @Test
@@ -115,10 +142,12 @@
         if (Looper.myLooper() == null) {
             Looper.prepare()
         }
+        val latch = CountDownLatch(1)
         val loadedResult: AtomicReference<GetCredentialException> = AtomicReference()
-        credentialManager.executeGetCredentialAsync(
+
+        credentialManager.getCredentialAsync(
             request = GetCredentialRequest.Builder()
-                .addGetCredentialOption(GetPasswordOption())
+                .addCredentialOption(GetPasswordOption())
                 .build(),
             activity = Activity(),
             cancellationSignal = null,
@@ -126,13 +155,21 @@
             callback = object : CredentialManagerCallback<GetCredentialResponse,
                 GetCredentialException> {
                 override fun onResult(result: GetCredentialResponse) {}
-                override fun onError(e: GetCredentialException) { loadedResult.set(e) }
+                override fun onError(e: GetCredentialException) {
+                    loadedResult.set(e)
+                    latch.countDown()
+                }
             }
         )
-        assertThat(loadedResult.get().type).isEqualTo(
-            GetCredentialProviderConfigurationException
-            .TYPE_GET_CREDENTIAL_PROVIDER_CONFIGURATION_EXCEPTION)
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+
+        latch.await(100L, TimeUnit.MILLISECONDS)
+        if (!isPostFrameworkApiLevel()) {
+            assertThat(loadedResult.get().javaClass).isEqualTo(
+                GetCredentialProviderConfigurationException::class.java
+            )
+        }
+        // TODO("Add manifest tests and possibly further separate these tests - maybe a rule
+        //  perhaps?")
     }
 
     @Test
@@ -140,19 +177,27 @@
         if (Looper.myLooper() == null) {
             Looper.prepare()
         }
+        if (isPostFrameworkApiLevel()) {
+            return // TODO(Support!)
+        }
+        val latch = CountDownLatch(1)
         val loadedResult: AtomicReference<ClearCredentialException> = AtomicReference()
 
         credentialManager.clearCredentialStateAsync(
             ClearCredentialStateRequest(),
             null, Executor { obj: Runnable -> obj.run() },
             object : CredentialManagerCallback<Void?, ClearCredentialException> {
-                override fun onError(e: ClearCredentialException) { loadedResult.set(e) }
+                override fun onError(e: ClearCredentialException) {
+                    loadedResult.set(e)
+                    latch.countDown()
+                }
                 override fun onResult(result: Void?) {}
             })
 
+        latch.await(100L, TimeUnit.MILLISECONDS)
         assertThat(loadedResult.get().type).isEqualTo(
             ClearCredentialProviderConfigurationException
             .TYPE_CLEAR_CREDENTIAL_PROVIDER_CONFIGURATION_EXCEPTION)
-        // TODO(Add manifest tests and separate tests for pre and post U API Levels")
+        // TODO(Add manifest tests and split this once postU is implemented for clearCreds")
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java
new file mode 100644
index 0000000..58db045
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestJavaTest.java
@@ -0,0 +1,118 @@
+/*
+ * 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.credentials;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.junit.Assert.assertThrows;
+
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+
+@RunWith(AndroidJUnit4.class)
+@SmallTest
+public class GetCredentialRequestJavaTest {
+    @Test
+    public void constructor_emptyCredentialOptions_throws() {
+        assertThrows(IllegalArgumentException.class,
+                () -> new GetCredentialRequest(new ArrayList<>()));
+
+    }
+
+    @Test
+    public void constructor() {
+        boolean expectedIsAutoSelectAllowed = true;
+        ArrayList<CredentialOption> expectedCredentialOptions = new ArrayList<>();
+        expectedCredentialOptions.add(new GetPasswordOption());
+        expectedCredentialOptions.add(new GetPublicKeyCredentialOption("json"));
+
+        GetCredentialRequest request = new GetCredentialRequest(expectedCredentialOptions,
+                expectedIsAutoSelectAllowed);
+
+        assertThat(request.isAutoSelectAllowed()).isEqualTo(expectedIsAutoSelectAllowed);
+        assertThat(request.getCredentialOptions()).hasSize(expectedCredentialOptions.size());
+        for (int i = 0; i < expectedCredentialOptions.size(); i++) {
+            assertThat(request.getCredentialOptions().get(i)).isEqualTo(
+                    expectedCredentialOptions.get(i));
+        }
+    }
+
+    @Test
+    public void constructor_defaultAutoSelect() {
+        ArrayList<CredentialOption> options = new ArrayList<>();
+        options.add(new GetPasswordOption());
+
+        GetCredentialRequest request = new GetCredentialRequest(options);
+
+        assertThat(request.isAutoSelectAllowed()).isFalse();
+    }
+
+    @Test
+    public void builder_addCredentialOption() {
+        boolean expectedIsAutoSelectAllowed = true;
+        ArrayList<CredentialOption> expectedCredentialOptions = new ArrayList<>();
+        expectedCredentialOptions.add(new GetPasswordOption());
+        expectedCredentialOptions.add(new GetPublicKeyCredentialOption("json"));
+
+        GetCredentialRequest request = new GetCredentialRequest.Builder()
+                .addCredentialOption(expectedCredentialOptions.get(0))
+                .addCredentialOption(expectedCredentialOptions.get(1))
+                .setAutoSelectAllowed(expectedIsAutoSelectAllowed)
+                .build();
+
+        assertThat(request.isAutoSelectAllowed()).isEqualTo(expectedIsAutoSelectAllowed);
+        assertThat(request.getCredentialOptions()).hasSize(expectedCredentialOptions.size());
+        for (int i = 0; i < expectedCredentialOptions.size(); i++) {
+            assertThat(request.getCredentialOptions().get(i)).isEqualTo(
+                    expectedCredentialOptions.get(i));
+        }
+    }
+
+    @Test
+    public void builder_setCredentialOptions() {
+        boolean expectedIsAutoSelectAllowed = true;
+        ArrayList<CredentialOption> expectedCredentialOptions = new ArrayList<>();
+        expectedCredentialOptions.add(new GetPasswordOption());
+        expectedCredentialOptions.add(new GetPublicKeyCredentialOption("json"));
+
+        GetCredentialRequest request = new GetCredentialRequest.Builder()
+                .setCredentialOptions(expectedCredentialOptions)
+                .setAutoSelectAllowed(expectedIsAutoSelectAllowed)
+                .build();
+
+        assertThat(request.isAutoSelectAllowed()).isEqualTo(expectedIsAutoSelectAllowed);
+        assertThat(request.getCredentialOptions()).hasSize(expectedCredentialOptions.size());
+        for (int i = 0; i < expectedCredentialOptions.size(); i++) {
+            assertThat(request.getCredentialOptions().get(i)).isEqualTo(
+                    expectedCredentialOptions.get(i));
+        }
+    }
+
+    @Test
+    public void builder_defaultAutoSelect() {
+        GetCredentialRequest request = new GetCredentialRequest.Builder()
+                .addCredentialOption(new GetPasswordOption())
+                .build();
+
+        assertThat(request.isAutoSelectAllowed()).isFalse();
+    }
+}
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt
new file mode 100644
index 0000000..45eed29
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCredentialRequestTest.kt
@@ -0,0 +1,122 @@
+/*
+ * 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.credentials
+
+import com.google.common.truth.Truth.assertThat
+
+import org.junit.Assert.assertThrows
+
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.SmallTest
+
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@RunWith(AndroidJUnit4::class)
+@SmallTest
+class GetCredentialRequestTest {
+
+    @Test
+    fun constructor_emptyCredentialOptions_throws() {
+        assertThrows(
+            IllegalArgumentException::class.java
+        ) { GetCredentialRequest(ArrayList()) }
+    }
+
+    @Test
+    fun constructor() {
+        val expectedIsAutoSelectAllowed = true
+        val expectedCredentialOptions = ArrayList<CredentialOption>()
+        expectedCredentialOptions.add(GetPasswordOption())
+        expectedCredentialOptions.add(GetPublicKeyCredentialOption("json"))
+
+        val request = GetCredentialRequest(
+            expectedCredentialOptions,
+            expectedIsAutoSelectAllowed
+        )
+
+        assertThat(request.isAutoSelectAllowed).isEqualTo(expectedIsAutoSelectAllowed)
+        assertThat(request.credentialOptions).hasSize(expectedCredentialOptions.size)
+        for (i in expectedCredentialOptions.indices) {
+            assertThat(request.credentialOptions[i]).isEqualTo(
+                expectedCredentialOptions[i]
+            )
+        }
+    }
+
+    @Test
+    fun constructor_defaultAutoSelect() {
+        val options = ArrayList<CredentialOption>()
+        options.add(GetPasswordOption())
+
+        val request = GetCredentialRequest(options)
+
+        assertThat(request.isAutoSelectAllowed).isFalse()
+    }
+
+    @Test
+    fun builder_addCredentialOption() {
+        val expectedIsAutoSelectAllowed = true
+        val expectedCredentialOptions = ArrayList<CredentialOption>()
+        expectedCredentialOptions.add(GetPasswordOption())
+        expectedCredentialOptions.add(GetPublicKeyCredentialOption("json"))
+
+        val request = GetCredentialRequest.Builder()
+            .addCredentialOption(expectedCredentialOptions[0])
+            .addCredentialOption(expectedCredentialOptions[1])
+            .setAutoSelectAllowed(expectedIsAutoSelectAllowed)
+            .build()
+
+        assertThat(request.isAutoSelectAllowed).isEqualTo(expectedIsAutoSelectAllowed)
+        assertThat(request.credentialOptions).hasSize(expectedCredentialOptions.size)
+        for (i in expectedCredentialOptions.indices) {
+            assertThat(request.credentialOptions[i]).isEqualTo(
+                expectedCredentialOptions[i]
+            )
+        }
+    }
+
+    @Test
+    fun builder_setCredentialOptions() {
+        val expectedIsAutoSelectAllowed = true
+        val expectedCredentialOptions = ArrayList<CredentialOption>()
+        expectedCredentialOptions.add(GetPasswordOption())
+        expectedCredentialOptions.add(GetPublicKeyCredentialOption("json"))
+
+        val request = GetCredentialRequest.Builder()
+            .setCredentialOptions(expectedCredentialOptions)
+            .setAutoSelectAllowed(expectedIsAutoSelectAllowed)
+            .build()
+
+        assertThat(request.isAutoSelectAllowed).isEqualTo(expectedIsAutoSelectAllowed)
+        assertThat(request.credentialOptions).hasSize(expectedCredentialOptions.size)
+        for (i in expectedCredentialOptions.indices) {
+            assertThat(request.credentialOptions[i]).isEqualTo(
+                expectedCredentialOptions[i]
+            )
+        }
+    }
+
+    @Test
+    fun builder_defaultAutoSelect() {
+        val request = GetCredentialRequest.Builder()
+            .addCredentialOption(GetPasswordOption())
+            .build()
+
+        assertThat(request.isAutoSelectAllowed).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
index 3b02d52..24893d9 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionJavaTest.java
@@ -79,6 +79,6 @@
         assertThat(TestUtilsKt.equals(option.getRequestData(), expectedBundle)).isTrue();
         assertThat(TestUtilsKt.equals(option.getCandidateQueryData(),
                 expectedCandidateQueryDataBundle)).isTrue();
-        assertThat(option.requireSystemProvider()).isEqualTo(expectedSystemProvider);
+        assertThat(option.isSystemProviderRequired()).isEqualTo(expectedSystemProvider);
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
index d6f3ea8..d33cc62 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetCustomCredentialOptionTest.kt
@@ -72,6 +72,6 @@
                 expectedCandidateQueryDataBundle
             )
         ).isTrue()
-        assertThat(option.requireSystemProvider).isEqualTo(expectedSystemProvider)
+        assertThat(option.isSystemProviderRequired).isEqualTo(expectedSystemProvider)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
index 80dfcec..dcfc8dff 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionJavaTest.java
@@ -36,16 +36,16 @@
         assertThat(option.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
         assertThat(TestUtilsKt.equals(option.getRequestData(), Bundle.EMPTY)).isTrue();
         assertThat(TestUtilsKt.equals(option.getRequestData(), Bundle.EMPTY)).isTrue();
-        assertThat(option.getRequireSystemProvider()).isFalse();
+        assertThat(option.isSystemProviderRequired()).isFalse();
     }
 
     @Test
     public void frameworkConversion_success() {
         GetPasswordOption option = new GetPasswordOption();
 
-        GetCredentialOption convertedOption = GetCredentialOption.createFrom(
+        CredentialOption convertedOption = CredentialOption.createFrom(
                 option.getType(), option.getRequestData(), option.getCandidateQueryData(),
-                option.getRequireSystemProvider());
+                option.isSystemProviderRequired());
 
         assertThat(convertedOption).isInstanceOf(GetPasswordOption.class);
     }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
index 7b3201a..e9337ab 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPasswordOptionTest.kt
@@ -17,7 +17,7 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.credentials.GetCredentialOption.Companion.createFrom
+import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -34,7 +34,7 @@
         assertThat(option.type).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL)
         assertThat(equals(option.requestData, Bundle.EMPTY)).isTrue()
         assertThat(equals(option.requestData, Bundle.EMPTY)).isTrue()
-        assertThat(option.requireSystemProvider).isFalse()
+        assertThat(option.isSystemProviderRequired).isFalse()
     }
 
     @Test
@@ -42,7 +42,10 @@
         val option = GetPasswordOption()
 
         val convertedOption = createFrom(
-            option.type, option.requestData, option.candidateQueryData, option.requireSystemProvider
+            option.type,
+            option.requestData,
+            option.candidateQueryData,
+            option.isSystemProviderRequired
         )
 
         assertThat(convertedOption).isInstanceOf(GetPasswordOption::class.java)
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
index dad1fe3..338baf0 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionJavaTest.java
@@ -16,7 +16,7 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_ALLOW_HYBRID;
+import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS;
 import static androidx.credentials.GetPublicKeyCredentialOption.BUNDLE_KEY_REQUEST_JSON;
 
 import static com.google.common.truth.Truth.assertThat;
@@ -53,28 +53,31 @@
     }
 
     @Test
-    public void constructor_success()  {
+    public void constructor_success() {
         new GetPublicKeyCredentialOption(
-                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}");
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}");
     }
 
     @Test
-    public void constructor_setsAllowHybridToTrueByDefault() {
+    public void constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         GetPublicKeyCredentialOption getPublicKeyCredentialOpt =
                 new GetPublicKeyCredentialOption(
                         "JSON");
-        boolean allowHybridActual = getPublicKeyCredentialOpt.allowHybrid();
-        assertThat(allowHybridActual).isTrue();
+        boolean preferImmediatelyAvailableCredentialsActual =
+                getPublicKeyCredentialOpt.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse();
     }
 
     @Test
-    public void constructor_setsAllowHybridToFalse() {
-        boolean allowHybridExpected = false;
+    public void constructor_setPreferImmediatelyAvailableCredentialsToTrue() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         GetPublicKeyCredentialOption getPublicKeyCredentialOpt =
                 new GetPublicKeyCredentialOption(
-                        "JSON", allowHybridExpected);
-        boolean allowHybridActual = getPublicKeyCredentialOpt.allowHybrid();
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected);
+                        "JSON", preferImmediatelyAvailableCredentialsExpected);
+        boolean preferImmediatelyAvailableCredentialsActual =
+                getPublicKeyCredentialOpt.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
@@ -89,21 +92,23 @@
     @Test
     public void getter_frameworkProperties_success() {
         String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
-        boolean allowHybridExpected = false;
+        boolean preferImmediatelyAvailableCredentialsExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
                 GetPublicKeyCredentialOption.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION);
         expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
-        expectedData.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
+        expectedData.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentialsExpected);
 
-        GetPublicKeyCredentialOption option =
-                new GetPublicKeyCredentialOption(requestJsonExpected, allowHybridExpected);
+        GetPublicKeyCredentialOption option = new GetPublicKeyCredentialOption(
+                requestJsonExpected, preferImmediatelyAvailableCredentialsExpected);
 
         assertThat(option.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertThat(TestUtilsKt.equals(option.getRequestData(), expectedData)).isTrue();
         assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedData)).isTrue();
-        assertThat(option.getRequireSystemProvider()).isFalse();
+        assertThat(option.isSystemProviderRequired()).isFalse();
     }
 
     @Test
@@ -111,14 +116,15 @@
         GetPublicKeyCredentialOption option =
                 new GetPublicKeyCredentialOption("json", true);
 
-        GetCredentialOption convertedOption = GetCredentialOption.createFrom(
+        CredentialOption convertedOption = CredentialOption.createFrom(
                 option.getType(), option.getRequestData(),
-                option.getCandidateQueryData(), option.getRequireSystemProvider());
+                option.getCandidateQueryData(), option.isSystemProviderRequired());
 
         assertThat(convertedOption).isInstanceOf(GetPublicKeyCredentialOption.class);
         GetPublicKeyCredentialOption convertedSubclassOption =
                 (GetPublicKeyCredentialOption) convertedOption;
         assertThat(convertedSubclassOption.getRequestJson()).isEqualTo(option.getRequestJson());
-        assertThat(convertedSubclassOption.allowHybrid()).isEqualTo(option.allowHybrid());
+        assertThat(convertedSubclassOption.preferImmediatelyAvailableCredentials()).isEqualTo(
+                option.preferImmediatelyAvailableCredentials());
     }
 }
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
index 760eead..d7e694c 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedJavaTest.java
@@ -16,8 +16,8 @@
 
 package androidx.credentials;
 
-import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_ALLOW_HYBRID;
 import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH;
+import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS;
 import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RELYING_PARTY;
 import static androidx.credentials.GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_REQUEST_JSON;
 
@@ -42,46 +42,56 @@
     @Test
     public void constructor_success() {
         new GetPublicKeyCredentialOptionPrivileged(
-                        "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
-                        "RelyingParty", "ClientDataHash");
+                "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}",
+                "RelyingParty", "ClientDataHash");
     }
 
     @Test
-    public void constructor_setsAllowHybridToTrueByDefault() {
+    public void constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged(
                         "JSON", "RelyingParty", "HASH");
-        boolean allowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
-        assertThat(allowHybridActual).isTrue();
+        boolean preferImmediatelyAvailableCredentialsActual =
+                getPublicKeyCredentialOptionPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse();
     }
 
     @Test
-    public void constructor_setsAllowHybridToFalse() {
-        boolean allowHybridExpected = false;
+    public void constructor_setPreferImmediatelyAvailableCredentialsTrue() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
-                new GetPublicKeyCredentialOptionPrivileged("testJson",
-                        "RelyingParty", "Hash", allowHybridExpected);
-        boolean getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
-        assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected);
+                new GetPublicKeyCredentialOptionPrivileged(
+                        "testJson",
+                        "RelyingParty",
+                        "Hash",
+                        preferImmediatelyAvailableCredentialsExpected
+                );
+        boolean preferImmediatelyAvailableCredentialsActual =
+                getPublicKeyCredentialOptionPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
-    public void builder_build_defaultAllowHybrid_success() {
+    public void builder_build_defaultPreferImmediatelyAvailableCredentials_success() {
         GetPublicKeyCredentialOptionPrivileged defaultPrivilegedRequest = new
                 GetPublicKeyCredentialOptionPrivileged.Builder("{\"Data\":5}",
                 "RelyingParty", "HASH").build();
-        assertThat(defaultPrivilegedRequest.allowHybrid()).isTrue();
+        assertThat(defaultPrivilegedRequest.preferImmediatelyAvailableCredentials()).isFalse();
     }
 
     @Test
-    public void builder_build_nonDefaultAllowHybrid_success() {
-        boolean allowHybridExpected = false;
+    public void builder_build_nonDefaultPreferImmediatelyAvailableCredentials_success() {
+        boolean preferImmediatelyAvailableCredentialsExpected = true;
         GetPublicKeyCredentialOptionPrivileged getPublicKeyCredentialOptionPrivileged =
                 new GetPublicKeyCredentialOptionPrivileged.Builder("testJson",
                         "RelyingParty", "Hash")
-                        .setAllowHybrid(allowHybridExpected).build();
-        boolean getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid();
-        assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected);
+                        .setPreferImmediatelyAvailableCredentials(
+                                preferImmediatelyAvailableCredentialsExpected).build();
+        boolean preferImmediatelyAvailableCredentialsActual =
+                getPublicKeyCredentialOptionPrivileged.preferImmediatelyAvailableCredentials();
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+                preferImmediatelyAvailableCredentialsExpected);
     }
 
     @Test
@@ -121,7 +131,7 @@
         String requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}";
         String relyingPartyExpected = "RelyingParty";
         String clientDataHashExpected = "X342%4dfd7&";
-        boolean allowHybridExpected = false;
+        boolean preferImmediatelyAvailableCredentialsExpected = false;
         Bundle expectedData = new Bundle();
         expectedData.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
@@ -130,17 +140,19 @@
         expectedData.putString(BUNDLE_KEY_REQUEST_JSON, requestJsonExpected);
         expectedData.putString(BUNDLE_KEY_RELYING_PARTY, relyingPartyExpected);
         expectedData.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHashExpected);
-        expectedData.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybridExpected);
+        expectedData.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentialsExpected);
 
         GetPublicKeyCredentialOptionPrivileged option =
                 new GetPublicKeyCredentialOptionPrivileged(
                         requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
-                        allowHybridExpected);
+                        preferImmediatelyAvailableCredentialsExpected);
 
         assertThat(option.getType()).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL);
         assertThat(TestUtilsKt.equals(option.getRequestData(), expectedData)).isTrue();
         assertThat(TestUtilsKt.equals(option.getCandidateQueryData(), expectedData)).isTrue();
-        assertThat(option.getRequireSystemProvider()).isFalse();
+        assertThat(option.isSystemProviderRequired()).isFalse();
     }
 
     @Test
@@ -148,15 +160,16 @@
         GetPublicKeyCredentialOptionPrivileged option =
                 new GetPublicKeyCredentialOptionPrivileged("json", "rp", "clientDataHash", true);
 
-        GetCredentialOption convertedOption = GetCredentialOption.createFrom(
+        CredentialOption convertedOption = CredentialOption.createFrom(
                 option.getType(), option.getRequestData(),
-                option.getCandidateQueryData(), option.getRequireSystemProvider());
+                option.getCandidateQueryData(), option.isSystemProviderRequired());
 
         assertThat(convertedOption).isInstanceOf(GetPublicKeyCredentialOptionPrivileged.class);
         GetPublicKeyCredentialOptionPrivileged convertedSubclassOption =
                 (GetPublicKeyCredentialOptionPrivileged) convertedOption;
         assertThat(convertedSubclassOption.getRequestJson()).isEqualTo(option.getRequestJson());
-        assertThat(convertedSubclassOption.allowHybrid()).isEqualTo(option.allowHybrid());
+        assertThat(convertedSubclassOption.preferImmediatelyAvailableCredentials()).isEqualTo(
+                option.preferImmediatelyAvailableCredentials());
         assertThat(convertedSubclassOption.getClientDataHash())
                 .isEqualTo(option.getClientDataHash());
         assertThat(convertedSubclassOption.getRelyingParty()).isEqualTo(option.getRelyingParty());
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
index dec9de2..7033d90 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionPrivilegedTest.kt
@@ -17,7 +17,7 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.credentials.GetCredentialOption.Companion.createFrom
+import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -40,51 +40,65 @@
     }
 
     @Test
-    fun constructor_setsAllowHybridToTrueByDefault() {
+    fun constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged(
             "JSON", "RelyingParty", "HASH"
         )
-        val allowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid
-        assertThat(allowHybridActual).isTrue()
+        val preferImmediatelyAvailableCredentialsActual =
+            getPublicKeyCredentialOptionPrivileged.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse()
     }
 
     @Test
-    fun constructor_setsAllowHybridFalse() {
-        val allowHybridExpected = false
+    fun constructor_setPreferImmediatelyAvailableCredentialsTrue() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val getPublicKeyCredentialOptPriv = GetPublicKeyCredentialOptionPrivileged(
-            "JSON", "RelyingParty", "HASH", allowHybridExpected
+            "JSON",
+            "RelyingParty",
+            "HASH",
+            preferImmediatelyAvailableCredentialsExpected
         )
-        val getAllowHybridActual = getPublicKeyCredentialOptPriv.allowHybrid
-        assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected)
+        val preferImmediatelyAvailableCredentialsActual =
+            getPublicKeyCredentialOptPriv.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+            preferImmediatelyAvailableCredentialsExpected
+        )
     }
 
     @Test
-    fun builder_build_nonDefaultAllowHybrid_false() {
-        val allowHybridExpected = false
+    fun builder_build_nonDefaultPreferImmediatelyAvailableCredentials_true() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val getPublicKeyCredentialOptionPrivileged = GetPublicKeyCredentialOptionPrivileged
             .Builder(
                 "testJson",
                 "RelyingParty", "Hash",
-            ).setAllowHybrid(allowHybridExpected).build()
-        val getAllowHybridActual = getPublicKeyCredentialOptionPrivileged.allowHybrid
-        assertThat(getAllowHybridActual).isEqualTo(allowHybridExpected)
+            )
+            .setPreferImmediatelyAvailableCredentials(preferImmediatelyAvailableCredentialsExpected)
+            .build()
+        val preferImmediatelyAvailableCredentialsActual =
+            getPublicKeyCredentialOptionPrivileged.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isEqualTo(
+            preferImmediatelyAvailableCredentialsExpected
+        )
     }
 
     @Test
-    fun builder_build_defaultAllowHybrid_true() {
+    fun builder_build_defaultPreferImmediatelyAvailableCredentials_false() {
         val defaultPrivilegedRequest = GetPublicKeyCredentialOptionPrivileged.Builder(
             "{\"Data\":5}",
             "RelyingParty", "HASH"
         ).build()
-        assertThat(defaultPrivilegedRequest.allowHybrid).isTrue()
+        assertThat(defaultPrivilegedRequest.preferImmediatelyAvailableCredentials).isFalse()
     }
 
     @Test
     fun getter_requestJson_success() {
         val testJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val getPublicKeyCredentialOptionPrivileged =
-            GetPublicKeyCredentialOptionPrivileged(testJsonExpected, "RelyingParty",
-                "HASH")
+            GetPublicKeyCredentialOptionPrivileged(
+                testJsonExpected, "RelyingParty",
+                "HASH"
+            )
         val testJsonActual = getPublicKeyCredentialOptionPrivileged.requestJson
         assertThat(testJsonActual).isEqualTo(testJsonExpected)
     }
@@ -116,7 +130,7 @@
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
         val relyingPartyExpected = "RelyingParty"
         val clientDataHashExpected = "X342%4dfd7&"
-        val allowHybridExpected = false
+        val preferImmediatelyAvailableCredentialsExpected = false
         val expectedData = Bundle()
         expectedData.putString(
             PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
@@ -127,26 +141,29 @@
             GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_REQUEST_JSON,
             requestJsonExpected
         )
-        expectedData.putString(GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RELYING_PARTY,
-            relyingPartyExpected)
+        expectedData.putString(
+            GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_RELYING_PARTY,
+            relyingPartyExpected
+        )
         expectedData.putString(
             GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_CLIENT_DATA_HASH,
             clientDataHashExpected
         )
         expectedData.putBoolean(
-            GetPublicKeyCredentialOptionPrivileged.BUNDLE_KEY_ALLOW_HYBRID,
-            allowHybridExpected
+            GetPublicKeyCredentialOptionPrivileged
+                .BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         val option = GetPublicKeyCredentialOptionPrivileged(
             requestJsonExpected, relyingPartyExpected, clientDataHashExpected,
-            allowHybridExpected
+            preferImmediatelyAvailableCredentialsExpected
         )
 
         assertThat(option.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertThat(equals(option.requestData, expectedData)).isTrue()
         assertThat(equals(option.candidateQueryData, expectedData)).isTrue()
-        assertThat(option.requireSystemProvider).isFalse()
+        assertThat(option.isSystemProviderRequired).isFalse()
     }
 
     @Test
@@ -154,7 +171,10 @@
         val option = GetPublicKeyCredentialOptionPrivileged("json", "rp", "clientDataHash", true)
 
         val convertedOption = createFrom(
-            option.type, option.requestData, option.candidateQueryData, option.requireSystemProvider
+            option.type,
+            option.requestData,
+            option.candidateQueryData,
+            option.isSystemProviderRequired
         )
 
         assertThat(convertedOption).isInstanceOf(
@@ -162,7 +182,8 @@
         )
         val convertedSubclassOption = convertedOption as GetPublicKeyCredentialOptionPrivileged
         assertThat(convertedSubclassOption.requestJson).isEqualTo(option.requestJson)
-        assertThat(convertedSubclassOption.allowHybrid).isEqualTo(option.allowHybrid)
+        assertThat(convertedSubclassOption.preferImmediatelyAvailableCredentials)
+            .isEqualTo(option.preferImmediatelyAvailableCredentials)
         assertThat(convertedSubclassOption.clientDataHash)
             .isEqualTo(option.clientDataHash)
         assertThat(convertedSubclassOption.relyingParty).isEqualTo(option.relyingParty)
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
index 6693856b..cc2b192 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/GetPublicKeyCredentialOptionTest.kt
@@ -17,7 +17,7 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.credentials.GetCredentialOption.Companion.createFrom
+import androidx.credentials.CredentialOption.Companion.createFrom
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
@@ -45,22 +45,25 @@
     }
 
     @Test
-    fun constructor_setsAllowHybridToTrueByDefault() {
+    fun constructor_setPreferImmediatelyAvailableCredentialsToFalseByDefault() {
         val getPublicKeyCredentialOpt = GetPublicKeyCredentialOption(
             "JSON"
         )
-        val allowHybridActual = getPublicKeyCredentialOpt.allowHybrid
-        assertThat(allowHybridActual).isTrue()
+        val preferImmediatelyAvailableCredentialsActual =
+            getPublicKeyCredentialOpt.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual).isFalse()
     }
 
     @Test
-    fun constructor_setsAllowHybridFalse() {
-        val allowHybridExpected = false
+    fun constructor_setPreferImmediatelyAvailableCredentialsTrue() {
+        val preferImmediatelyAvailableCredentialsExpected = true
         val getPublicKeyCredentialOpt = GetPublicKeyCredentialOption(
-            "JSON", allowHybridExpected
+            "JSON", preferImmediatelyAvailableCredentialsExpected
         )
-        val allowHybridActual = getPublicKeyCredentialOpt.allowHybrid
-        assertThat(allowHybridActual).isEqualTo(allowHybridExpected)
+        val preferImmediatelyAvailableCredentialsActual =
+            getPublicKeyCredentialOpt.preferImmediatelyAvailableCredentials
+        assertThat(preferImmediatelyAvailableCredentialsActual)
+            .isEqualTo(preferImmediatelyAvailableCredentialsExpected)
     }
 
     @Test
@@ -74,7 +77,7 @@
     @Test
     fun getter_frameworkProperties_success() {
         val requestJsonExpected = "{\"hi\":{\"there\":{\"lol\":\"Value\"}}}"
-        val allowHybridExpected = false
+        val preferImmediatelyAvailableCredentialsExpected = false
         val expectedData = Bundle()
         expectedData.putString(
             PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
@@ -85,16 +88,18 @@
             requestJsonExpected
         )
         expectedData.putBoolean(
-            GetPublicKeyCredentialOption.BUNDLE_KEY_ALLOW_HYBRID,
-            allowHybridExpected
+            GetPublicKeyCredentialOption.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+            preferImmediatelyAvailableCredentialsExpected
         )
 
-        val option = GetPublicKeyCredentialOption(requestJsonExpected, allowHybridExpected)
+        val option = GetPublicKeyCredentialOption(
+            requestJsonExpected, preferImmediatelyAvailableCredentialsExpected
+        )
 
         assertThat(option.type).isEqualTo(PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL)
         assertThat(equals(option.requestData, expectedData)).isTrue()
         assertThat(equals(option.candidateQueryData, expectedData)).isTrue()
-        assertThat(option.requireSystemProvider).isFalse()
+        assertThat(option.isSystemProviderRequired).isFalse()
     }
 
     @Test
@@ -102,7 +107,10 @@
         val option = GetPublicKeyCredentialOption("json", true)
 
         val convertedOption = createFrom(
-            option.type, option.requestData, option.candidateQueryData, option.requireSystemProvider
+            option.type,
+            option.requestData,
+            option.candidateQueryData,
+            option.isSystemProviderRequired
         )
 
         assertThat(convertedOption).isInstanceOf(
@@ -110,6 +118,7 @@
         )
         val convertedSubclassOption = convertedOption as GetPublicKeyCredentialOption
         assertThat(convertedSubclassOption.requestJson).isEqualTo(option.requestJson)
-        assertThat(convertedSubclassOption.allowHybrid).isEqualTo(option.allowHybrid)
+        assertThat(convertedSubclassOption.preferImmediatelyAvailableCredentials)
+            .isEqualTo(option.preferImmediatelyAvailableCredentials)
     }
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialJavaTest.java b/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialJavaTest.java
index 2edf0d3..659dd33 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialJavaTest.java
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/PasswordCredentialJavaTest.java
@@ -77,10 +77,10 @@
         expectedData.putString(PasswordCredential.BUNDLE_KEY_ID, idExpected);
         expectedData.putString(PasswordCredential.BUNDLE_KEY_PASSWORD, passwordExpected);
 
-        CreatePasswordRequest credential = new CreatePasswordRequest(idExpected, passwordExpected);
+        PasswordCredential credential = new PasswordCredential(idExpected, passwordExpected);
 
         assertThat(credential.getType()).isEqualTo(PasswordCredential.TYPE_PASSWORD_CREDENTIAL);
-        assertThat(TestUtilsKt.equals(credential.getCredentialData(), expectedData)).isTrue();
+        assertThat(TestUtilsKt.equals(credential.getData(), expectedData)).isTrue();
     }
 
     @Test
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/TestActivity.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/TestActivity.kt
new file mode 100644
index 0000000..07b5b9a
--- /dev/null
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/TestActivity.kt
@@ -0,0 +1,25 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials
+
+import androidx.core.app.ComponentActivity
+
+/**
+ * This is a test activity used by the Robolectric Activity Scenario tests. It acts
+ * as a calling activity in our test cases.
+ */
+class TestActivity : ComponentActivity()
\ No newline at end of file
diff --git a/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt b/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
index 4567380..6b1ae3b 100644
--- a/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
+++ b/credentials/credentials/src/androidTest/java/androidx/credentials/TestUtils.kt
@@ -16,6 +16,7 @@
 
 package androidx.credentials
 
+import android.os.Build
 import android.os.Bundle
 
 /** True if the two Bundles contain the same elements, and false otherwise. */
@@ -41,4 +42,15 @@
         }
     }
     return true
+}
+
+/** Used to maintain compatibility across API levels. */
+const val MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL = Build.VERSION_CODES.TIRAMISU
+
+/** True if the device running the test is post framework api level,
+ * false if pre framework api level. */
+fun isPostFrameworkApiLevel(): Boolean {
+    return !((Build.VERSION.SDK_INT <= MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL) &&
+        !(Build.VERSION.SDK_INT == MAX_CRED_MAN_PRE_FRAMEWORK_API_LEVEL &&
+            Build.VERSION.PREVIEW_SDK_INT > 0))
 }
\ No newline at end of file
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
index b323a5a..e351e8f 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCredentialRequest.kt
@@ -16,16 +16,20 @@
 
 package androidx.credentials
 
+import android.graphics.drawable.Icon
 import android.os.Bundle
+import android.text.TextUtils
+import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
-import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Companion.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED
+import androidx.annotation.VisibleForTesting
+import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged.Companion.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV
 import androidx.credentials.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
  * Base request class for registering a credential.
  *
- * An application can construct a subtype request and call [CredentialManager.executeCreateCredential] to
+ * An application can construct a subtype request and call [CredentialManager.createCredential] to
  * launch framework UI flows to collect consent and any other metadata needed from the user to
  * register a new user credential.
  */
@@ -41,32 +45,138 @@
     open val candidateQueryData: Bundle,
     /** @hide */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    open val requireSystemProvider: Boolean
+    open val isSystemProviderRequired: Boolean,
+    /** @hide */
+    internal val displayInfo: DisplayInfo,
 ) {
+    /**
+     * Information that may be used for display purposes when rendering UIs to collect the user
+     * consent and choice.
+     *
+     * @property userId the user id of the created credential
+     * @property userDisplayName an optional display name in addition to the [userId] that may be
+     * displayed next to the `userId` during the user consent to help your user better understand
+     * the credential being created.
+     */
+    class DisplayInfo internal /** @hide */ constructor(
+        val userId: String,
+        val userDisplayName: String?,
+        /** @hide */
+        val credentialTypeIcon: Icon?
+    ) {
+
+        /**
+         * Constructs a [DisplayInfo].
+         *
+         * @param userId the user id of the created credential
+         * @param userDisplayName an optional display name in addition to the [userId] that may be
+         * displayed next to the `userId` during the user consent to help your user better
+         * understand the credential being created.
+         * @throws IllegalArgumentException If [userId] is empty
+         */
+        @JvmOverloads constructor(userId: String, userDisplayName: String? = null) : this(
+            userId,
+            userDisplayName,
+            null
+        )
+
+        init {
+            require(userId.isNotEmpty()) { "userId should not be empty" }
+        }
+
+        /** @hide */
+        @RequiresApi(23)
+        fun toBundle(): Bundle {
+            val bundle = Bundle()
+            bundle.putString(BUNDLE_KEY_USER_ID, userId)
+            if (!TextUtils.isEmpty(userDisplayName)) {
+                bundle.putString(BUNDLE_KEY_USER_DISPLAY_NAME, userDisplayName)
+            }
+            // Today the type icon is determined solely within this library right before the
+            // request is passed into the framework. Later if needed a new API can be added for
+            // custom SDKs to supply their own credential type icons.
+            return bundle
+        }
+
+        /** @hide */
+        companion object {
+            /** @hide */
+            const val BUNDLE_KEY_REQUEST_DISPLAY_INFO =
+                "androidx.credentials.BUNDLE_KEY_REQUEST_DISPLAY_INFO"
+
+            @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+            /** @hide */
+            const val BUNDLE_KEY_USER_ID =
+                "androidx.credentials.BUNDLE_KEY_USER_ID"
+
+            @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+            /** @hide */
+            const val BUNDLE_KEY_USER_DISPLAY_NAME =
+                "androidx.credentials.BUNDLE_KEY_USER_DISPLAY_NAME"
+
+            /** @hide */
+            const val BUNDLE_KEY_CREDENTIAL_TYPE_ICON =
+                "androidx.credentials.BUNDLE_KEY_CREDENTIAL_TYPE_ICON"
+
+            /**
+             * Returns a RequestDisplayInfo from a `credentialData` Bundle, or otherwise `null` if
+             * parsing fails.
+             *
+             * @hide
+             */
+            @JvmStatic
+            @RequiresApi(23)
+            @Suppress("DEPRECATION") // bundle.getParcelable(key)
+            fun parseFromCredentialDataBundle(from: Bundle): DisplayInfo? {
+                return try {
+                    val displayInfoBundle = from.getBundle(BUNDLE_KEY_REQUEST_DISPLAY_INFO)!!
+                    val userId = displayInfoBundle.getString(BUNDLE_KEY_USER_ID)
+                    val displayName = displayInfoBundle.getString(BUNDLE_KEY_USER_DISPLAY_NAME)
+                    val icon: Icon? =
+                        displayInfoBundle.getParcelable(BUNDLE_KEY_CREDENTIAL_TYPE_ICON)
+                    DisplayInfo(userId!!, displayName, icon)
+                } catch (e: Exception) {
+                    null
+                }
+            }
+        }
+    }
+
     /** @hide */
     companion object {
-        /** @hide */
+        /**
+         * Attempts to parse the raw data into one of [CreatePasswordRequest],
+         * [CreatePublicKeyCredentialRequest], [CreatePublicKeyCredentialRequestPrivileged], and
+         * [CreateCustomCredentialRequest]. Otherwise returns null.
+         *
+         * @hide
+         */
         @JvmStatic
+        @RequiresApi(23)
         fun createFrom(
             type: String,
             credentialData: Bundle,
             candidateQueryData: Bundle,
             requireSystemProvider: Boolean
-        ): CreateCredentialRequest {
+        ): CreateCredentialRequest? {
             return try {
                 when (type) {
                     PasswordCredential.TYPE_PASSWORD_CREDENTIAL ->
                         CreatePasswordRequest.createFrom(credentialData)
+
                     PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL ->
                         when (credentialData.getString(BUNDLE_KEY_SUBTYPE)) {
                             CreatePublicKeyCredentialRequest
                                 .BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST ->
                                 CreatePublicKeyCredentialRequest.createFrom(credentialData)
-                            BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED ->
+
+                            BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV ->
                                 CreatePublicKeyCredentialRequestPrivileged
                                     .createFrom(credentialData)
+
                             else -> throw FrameworkClassParsingException()
                         }
+
                     else -> throw FrameworkClassParsingException()
                 }
             } catch (e: FrameworkClassParsingException) {
@@ -76,7 +186,10 @@
                     type,
                     credentialData,
                     candidateQueryData,
-                    requireSystemProvider
+                    requireSystemProvider,
+                    DisplayInfo.parseFromCredentialDataBundle(
+                        credentialData
+                    ) ?: return null
                 )
             }
         }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialRequest.kt
index 9ad1dd8..507c0aaa 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialRequest.kt
@@ -22,28 +22,44 @@
  * Base custom create request class for registering a credential.
  *
  * An application can construct a subtype custom request and call
- * [CredentialManager.executeCreateCredential] to launch framework UI flows to collect consent and
+ * [CredentialManager.createCredential] to launch framework UI flows to collect consent and
  * any other metadata needed from the user to register a new user credential.
  *
- * @property type the credential type determined by the credential-type-specific subclass for custom
- * use cases
- * @property credentialData the full credential creation request data in the [Bundle] format for
+ * If you get a [CreateCustomCredentialRequest] instead of a type-safe request class such as
+ * [CreatePasswordRequest], [CreatePublicKeyCredentialRequest], etc., then you should check if you
+ * have any other library at interest that supports this custom [type] of credential request,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ * Note: The Bundle keys for [credentialData] and [candidateQueryData] should not be in the form
+ * of androidx.credentials.*` as they are reserved for internal use by this androidx library.
+ *
+ * @property type the credential type determined by the credential-type-specific subclass for
  * custom use cases
- * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
- * the provider during the initial candidate query stage, which should not contain sensitive user
- * credential information
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
- * otherwise
+ * @property credentialData the data of this [CreateCustomCredentialRequest] in the [Bundle]
+ * format (note: bundle keys in the form of `androidx.credentials.*` are reserved for internal
+ * library use)
+ * @property candidateQueryData the partial request data in the [Bundle] format that will be sent
+ * to the provider during the initial candidate query stage, which should not contain sensitive
+ * user credential information (note: bundle keys in the form of `androidx.credentials.*` are
+ * reserved for internal library use)
+ * @property isSystemProviderRequired true if must only be fulfilled by a system provider and
+ * false otherwise
  * @throws IllegalArgumentException If [type] is empty
- * @throws NullPointerException If [type] or [credentialData] are null
+ * @throws NullPointerException If [type], [credentialData], or [candidateQueryData] is null
  */
 open class CreateCustomCredentialRequest(
     final override val type: String,
     final override val credentialData: Bundle,
     final override val candidateQueryData: Bundle,
-    @get:JvmName("requireSystemProvider")
-    final override val requireSystemProvider: Boolean
-) : CreateCredentialRequest(type, credentialData, candidateQueryData, requireSystemProvider) {
+    final override val isSystemProviderRequired: Boolean,
+    displayInfo: DisplayInfo,
+) : CreateCredentialRequest(
+    type,
+    credentialData,
+    candidateQueryData,
+    isSystemProviderRequired,
+    displayInfo
+) {
     init {
         require(type.isNotEmpty()) { "type should not be empty" }
     }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialResponse.kt b/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialResponse.kt
index 3cca4ac..caf0dac 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialResponse.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreateCustomCredentialResponse.kt
@@ -22,6 +22,14 @@
  * Base custom create response class for the credential creation operation made with the
  * [CreateCustomCredentialRequest].
  *
+ * If you get a [CreateCustomCredentialResponse] instead of a type-safe response class such as
+ * [CreatePasswordResponse], [CreatePublicKeyCredentialResponse], etc., then you should check if
+ * you have any other library at interest that supports this custom [type] of credential response,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ * Note: The Bundle keys for [data] should not be in the form of androidx.credentials.*` as they
+ * are reserved for internal use by this androidx library.
+ *
  * @property type the credential type determined by the credential-type-specific subclass for custom
  * use cases
  * @property data the response data in the [Bundle] format for custom use cases
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
index 682731e..530eb69 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePasswordRequest.kt
@@ -17,6 +17,7 @@
 package androidx.credentials
 
 import android.os.Bundle
+import androidx.annotation.RequiresApi
 import androidx.annotation.VisibleForTesting
 import androidx.credentials.internal.FrameworkClassParsingException
 
@@ -25,21 +26,31 @@
  *
  * @property id the user id associated with the password
  * @property password the password
- * @throws NullPointerException If [id] is null
- * @throws NullPointerException If [password] is null
- * @throws IllegalArgumentException If [password] is empty
  */
-class CreatePasswordRequest constructor(
+class CreatePasswordRequest private constructor(
     val id: String,
     val password: String,
+    displayInfo: DisplayInfo,
 ) : CreateCredentialRequest(
     type = PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
     credentialData = toCredentialDataBundle(id, password),
-    // No credential data should be sent during the query phase.
-    candidateQueryData = Bundle(),
-    requireSystemProvider = false,
+    candidateQueryData = toCandidateDataBundle(),
+    isSystemProviderRequired = false,
+    displayInfo
 ) {
 
+    /**
+     * Constructs a [CreatePasswordRequest] to save the user password credential with their
+     * password provider.
+     *
+     * @param id the user id associated with the password
+     * @param password the password
+     * @throws NullPointerException If [id] is null
+     * @throws NullPointerException If [password] is null
+     * @throws IllegalArgumentException If [password] is empty
+     */
+    constructor(id: String, password: String) : this(id, password, DisplayInfo(id, null))
+
     init {
         require(password.isNotEmpty()) { "password should not be empty" }
     }
@@ -47,6 +58,7 @@
     /** @hide */
     companion object {
         internal const val BUNDLE_KEY_ID = "androidx.credentials.BUNDLE_KEY_ID"
+
         @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
         const val BUNDLE_KEY_PASSWORD = "androidx.credentials.BUNDLE_KEY_PASSWORD"
 
@@ -58,12 +70,23 @@
             return bundle
         }
 
+        // No credential data should be sent during the query phase.
         @JvmStatic
+        internal fun toCandidateDataBundle(): Bundle {
+            return Bundle()
+        }
+
+        @JvmStatic
+        @RequiresApi(23)
         internal fun createFrom(data: Bundle): CreatePasswordRequest {
             try {
                 val id = data.getString(BUNDLE_KEY_ID)
                 val password = data.getString(BUNDLE_KEY_PASSWORD)
-                return CreatePasswordRequest(id!!, password!!)
+                val displayInfo = DisplayInfo.parseFromCredentialDataBundle(data)
+                return if (displayInfo == null) CreatePasswordRequest(
+                    id!!,
+                    password!!
+                ) else CreatePasswordRequest(id!!, password!!, displayInfo)
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
             }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
index c78e8b2..1e80938 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequest.kt
@@ -17,64 +17,129 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.annotation.VisibleForTesting
+import androidx.annotation.RequiresApi
 import androidx.credentials.PublicKeyCredential.Companion.BUNDLE_KEY_SUBTYPE
 import androidx.credentials.internal.FrameworkClassParsingException
+import org.json.JSONObject
 
 /**
  * A request to register a passkey from the user's public key credential provider.
  *
- * @property requestJson the privileged request in JSON format in the standard webauthn web json
- * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default, with hybrid credentials defined
- * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
- * @throws NullPointerException If [requestJson] is null
- * @throws IllegalArgumentException If [requestJson] is empty
+ * @property requestJson the privileged request in JSON format in the [standard webauthn web json](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available passkey registration offering instead of falling back to
+ * discovering remote options, and false (default) otherwise
  */
-class CreatePublicKeyCredentialRequest @JvmOverloads constructor(
+class CreatePublicKeyCredentialRequest private constructor(
     val requestJson: String,
-    @get:JvmName("allowHybrid")
-    val allowHybrid: Boolean = true
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean,
+    displayInfo: DisplayInfo,
 ) : CreateCredentialRequest(
     type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    credentialData = toCredentialDataBundle(requestJson, allowHybrid),
+    credentialData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
     // The whole request data should be passed during the query phase.
-    candidateQueryData = toCredentialDataBundle(requestJson, allowHybrid),
-    requireSystemProvider = false,
+    candidateQueryData = toCredentialDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    isSystemProviderRequired = false,
+    displayInfo,
 ) {
 
+    /**
+     * Constructs a [CreatePublicKeyCredentialRequest] to register a passkey from the user's public key credential provider.
+     *
+     * @param requestJson the privileged request in JSON format in the [standard webauthn web json](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+     * @param preferImmediatelyAvailableCredentials true if you prefer the operation to return
+     * immediately when there is no available passkey registration offering instead of falling back to
+     * discovering remote options, and false (default) otherwise
+     * @throws NullPointerException If [requestJson] is null
+     * @throws IllegalArgumentException If [requestJson] is empty, or if it doesn't have a valid
+     * `user.name` defined according to the [webauthn spec](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson)
+     */
+    @JvmOverloads constructor(
+        requestJson: String,
+        preferImmediatelyAvailableCredentials: Boolean = false
+    ) : this(requestJson, preferImmediatelyAvailableCredentials, getRequestDisplayInfo(requestJson))
+
     init {
         require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
     }
 
     /** @hide */
     companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
         internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
+        internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST =
             "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST"
 
         @JvmStatic
-        internal fun toCredentialDataBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+        internal fun getRequestDisplayInfo(requestJson: String): DisplayInfo {
+            return try {
+                val json = JSONObject(requestJson)
+                val user = json.getJSONObject("user")
+                val userName = user.getString("name")
+                val displayName: String? =
+                    if (user.isNull("displayName")) null else user.getString("displayName")
+                DisplayInfo(userName, displayName)
+            } catch (e: Exception) {
+                throw IllegalArgumentException("user.name must be defined in requestJson")
+            }
+        }
+
+        @JvmStatic
+        internal fun toCredentialDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
             val bundle = Bundle()
-            bundle.putString(BUNDLE_KEY_SUBTYPE,
-                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST)
+            bundle.putString(
+                BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST
+            )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
+            return bundle
+        }
+
+        @JvmStatic
+        internal fun toCandidateDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
+            val bundle = Bundle()
+            bundle.putString(
+                BUNDLE_KEY_SUBTYPE,
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST
+            )
+            bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
             return bundle
         }
 
         @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
-                                         // boolean value from being returned.
+        // boolean value from being returned.
         @JvmStatic
+        @RequiresApi(23)
         internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequest {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
-                return CreatePublicKeyCredentialRequest(requestJson!!, (allowHybrid!!) as Boolean)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                val displayInfo = DisplayInfo.parseFromCredentialDataBundle(data)
+                return if (displayInfo == null) CreatePublicKeyCredentialRequest(
+                    requestJson!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean
+                ) else CreatePublicKeyCredentialRequest(
+                    requestJson!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
+                    displayInfo
+                )
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
             }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
index 1600894..9c888b5 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CreatePublicKeyCredentialRequestPrivileged.kt
@@ -17,42 +17,77 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.annotation.VisibleForTesting
+import androidx.annotation.RequiresApi
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
  * A privileged request to register a passkey from the user’s public key credential provider, where
  * the caller can modify the rp. Only callers with privileged permission, e.g. user’s default
- * brower, caBLE, can use this. These permissions will be introduced in an upcoming release.
- * TODO("Add specific permission info/annotation")
+ * browser, caBLE, can use this. These permissions will be introduced in an upcoming release.
  *
- * @property requestJson the privileged request in JSON format in the standard webauthn web json
- * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default, with hybrid credentials defined
- * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
- * @property relyingParty the expected true RP ID which will override the one in the [requestJson], where
- * rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
- * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
- * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is
- * null
- * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is empty
+ * @property requestJson the privileged request in JSON format in the [standard webauthn web json](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available passkey registration offering instead of falling back to
+ * discovering remote options, and false (default) otherwise
+ * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
+ * where rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
  */
-class CreatePublicKeyCredentialRequestPrivileged @JvmOverloads constructor(
+// TODO("Add specific permission info/annotation")
+class CreatePublicKeyCredentialRequestPrivileged private constructor(
     val requestJson: String,
     val relyingParty: String,
     val clientDataHash: String,
-    @get:JvmName("allowHybrid")
-    val allowHybrid: Boolean = true
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean,
+    displayInfo: DisplayInfo,
 ) : CreateCredentialRequest(
     type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    credentialData = toCredentialDataBundle(requestJson, relyingParty, clientDataHash, allowHybrid),
+    credentialData = toCredentialDataBundle(
+        requestJson,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
     // The whole request data should be passed during the query phase.
     candidateQueryData = toCredentialDataBundle(
-        requestJson, relyingParty, clientDataHash, allowHybrid),
-    requireSystemProvider = false,
+        requestJson, relyingParty, clientDataHash, preferImmediatelyAvailableCredentials
+    ),
+    isSystemProviderRequired = false,
+    displayInfo,
 ) {
 
+    /**
+     * Constructs a privileged request to register a passkey from the user’s public key credential
+     * provider, where the caller can modify the rp. Only callers with privileged permission, e.g.
+     * user’s default browser, caBLE, can use this. These permissions will be introduced in an
+     * upcoming release.
+     *
+     * @param requestJson the privileged request in JSON format in the [standard webauthn web json](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson).
+     * @param preferImmediatelyAvailableCredentials true if you prefer the operation to return
+     * immediately when there is no available passkey registration offering instead of falling
+     * back to discovering remote options, and false (default) otherwise
+     * @param relyingParty the expected true RP ID which will override the one in the
+     * [requestJson], where rp is defined [here](https://w3c.github.io/webauthn/#rp-id)
+     * @param clientDataHash a hash that is used to verify the [relyingParty] Identity
+     * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash] is
+     * null
+     * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or
+     * [clientDataHash] is empty, or if [requestJson] doesn't have a valid user.name` defined
+     * according to the [webauthn spec](https://w3c.github.io/webauthn/#dictdef-publickeycredentialcreationoptionsjson)
+     */
+    @JvmOverloads constructor(
+        requestJson: String,
+        relyingParty: String,
+        clientDataHash: String,
+        preferImmediatelyAvailableCredentials: Boolean = false
+    ) : this(
+        requestJson,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials,
+        CreatePublicKeyCredentialRequest.getRequestDisplayInfo(requestJson),
+    )
+
     init {
         require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
         require(relyingParty.isNotEmpty()) { "rp must not be empty" }
@@ -64,9 +99,9 @@
         private var requestJson: String,
         private var relyingParty: String,
         private var clientDataHash: String
-        ) {
+    ) {
 
-        private var allowHybrid: Boolean = true
+        private var preferImmediatelyAvailableCredentials: Boolean = false
 
         /**
          * Sets the privileged request in JSON format.
@@ -77,11 +112,17 @@
         }
 
         /**
-         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         * Sets to true if you prefer the operation to return immediately when there is no available
+         * passkey registration offering instead of falling back to discovering remote options, and
+         * false otherwise.
+         *
+         * The default value is false.
          */
         @Suppress("MissingGetterMatchingBuilder")
-        fun setAllowHybrid(allowHybrid: Boolean): Builder {
-            this.allowHybrid = allowHybrid
+        fun setPreferImmediatelyAvailableCredentials(
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Builder {
+            this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
             return this
         }
 
@@ -103,24 +144,25 @@
 
         /** Builds a [CreatePublicKeyCredentialRequestPrivileged]. */
         fun build(): CreatePublicKeyCredentialRequestPrivileged {
-            return CreatePublicKeyCredentialRequestPrivileged(this.requestJson,
-                this.relyingParty, this.clientDataHash, this.allowHybrid)
+            return CreatePublicKeyCredentialRequestPrivileged(
+                this.requestJson,
+                this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
+            )
         }
     }
 
     /** @hide */
     companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_RELYING_PARTY = "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_CLIENT_DATA_HASH =
+        internal const val BUNDLE_KEY_RELYING_PARTY =
+            "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
+        internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
             "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-        const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED =
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+
+        internal const val BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV =
             "androidx.credentials.BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_" +
                 "PRIVILEGED"
 
@@ -129,34 +171,46 @@
             requestJson: String,
             relyingParty: String,
             clientDataHash: String,
-            allowHybrid: Boolean
+            preferImmediatelyAvailableCredentials: Boolean
         ): Bundle {
             val bundle = Bundle()
             bundle.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
-                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIVILEGED
+                BUNDLE_VALUE_SUBTYPE_CREATE_PUBLIC_KEY_CREDENTIAL_REQUEST_PRIV
             )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
             bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
             return bundle
         }
 
         @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
-                                         // boolean value from being returned.
+        // boolean value from being returned.
         @JvmStatic
+        @RequiresApi(23)
         internal fun createFrom(data: Bundle): CreatePublicKeyCredentialRequestPrivileged {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
                 val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
                 val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
-                return CreatePublicKeyCredentialRequestPrivileged(
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                val displayInfo = DisplayInfo.parseFromCredentialDataBundle(data)
+                return if (displayInfo == null) CreatePublicKeyCredentialRequestPrivileged(
                     requestJson!!,
                     rp!!,
                     clientDataHash!!,
-                    (allowHybrid!!) as Boolean,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
+                ) else CreatePublicKeyCredentialRequestPrivileged(
+                    requestJson!!,
+                    rp!!,
+                    clientDataHash!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
+                    displayInfo,
                 )
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
index 89a8ac6..53396d9 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialManager.kt
@@ -100,10 +100,9 @@
      *
      * @param request the request for getting the credential
      * @param activity the activity used to potentially launch any UI needed
-     * @throws UnsupportedOperationException Since the api is unimplemented
+     * @throws GetCredentialException If the request fails
      */
-    // TODO(helenqin): support failure flow.
-    suspend fun executeGetCredential(
+    suspend fun getCredential(
         request: GetCredentialRequest,
         activity: Activity,
     ): GetCredentialResponse = suspendCancellableCoroutine { continuation ->
@@ -123,7 +122,7 @@
             }
         }
 
-        executeGetCredentialAsync(
+        getCredentialAsync(
             request,
             activity,
             canceller,
@@ -142,9 +141,9 @@
      *
      * @param request the request for creating the credential
      * @param activity the activity used to potentially launch any UI needed
-     * @throws UnsupportedOperationException Since the api is unimplemented
+     * @throws CreateCredentialException If the request fails
      */
-    suspend fun executeCreateCredential(
+    suspend fun createCredential(
         request: CreateCredentialRequest,
         activity: Activity,
     ): CreateCredentialResponse = suspendCancellableCoroutine { continuation ->
@@ -164,7 +163,7 @@
             }
         }
 
-        executeCreateCredentialAsync(
+        createCredentialAsync(
             request,
             activity,
             canceller,
@@ -187,6 +186,7 @@
      * to let the provider clear any stored credential session.
      *
      * @param request the request for clearing the app user's credential state
+     * @throws ClearCredentialException If the request fails
      */
     suspend fun clearCredentialState(
         request: ClearCredentialStateRequest
@@ -226,9 +226,8 @@
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this executor
      * @param callback the callback invoked when the request succeeds or fails
-     * @throws UnsupportedOperationException Since the api is unimplemented
      */
-    fun executeGetCredentialAsync(
+    fun getCredentialAsync(
         request: GetCredentialRequest,
         activity: Activity,
         cancellationSignal: CancellationSignal?,
@@ -241,7 +240,7 @@
             // TODO (Update with the right error code when ready)
             callback.onError(
                 GetCredentialProviderConfigurationException(
-                    "executeGetCredentialAsync no provider dependencies found - please ensure " +
+                    "getCredentialAsync no provider dependencies found - please ensure " +
                         "the desired provider dependencies are added")
             )
             return
@@ -261,9 +260,8 @@
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this executor
      * @param callback the callback invoked when the request succeeds or fails
-     * @throws UnsupportedOperationException Since the api is unimplemented
      */
-    fun executeCreateCredentialAsync(
+    fun createCredentialAsync(
         request: CreateCredentialRequest,
         activity: Activity,
         cancellationSignal: CancellationSignal?,
@@ -275,7 +273,7 @@
         if (provider == null) {
             // TODO (Update with the right error code when ready)
             callback.onError(CreateCredentialProviderConfigurationException(
-                "executeCreateCredentialAsync no provider dependencies found - please ensure the " +
+                "createCredentialAsync no provider dependencies found - please ensure the " +
                     "desired provider dependencies are added"))
             return
         }
@@ -298,7 +296,6 @@
      * @param cancellationSignal an optional signal that allows for cancelling this call
      * @param executor the callback will take place on this executor
      * @param callback the callback invoked when the request succeeds or fails
-     * @throws UnsupportedOperationException Since the api is unimplemented
      */
     fun clearCredentialStateAsync(
         request: ClearCredentialStateRequest,
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
similarity index 92%
rename from credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt
rename to credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
index e109b67..555cd00 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialOption.kt
@@ -23,10 +23,10 @@
 /**
  * Base class for getting a specific type of credentials.
  *
- * [GetCredentialRequest] will be composed of a list of [GetCredentialOption] subclasses to indicate
+ * [GetCredentialRequest] will be composed of a list of [CredentialOption] subclasses to indicate
  * the specific credential types and configurations that your app accepts.
  */
-abstract class GetCredentialOption internal constructor(
+abstract class CredentialOption internal constructor(
     /** @hide */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     open val type: String,
@@ -38,7 +38,7 @@
     open val candidateQueryData: Bundle,
     /** @hide */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    open val requireSystemProvider: Boolean,
+    open val isSystemProviderRequired: Boolean,
 ) {
     /** @hide */
     companion object {
@@ -49,7 +49,7 @@
             requestData: Bundle,
             candidateQueryData: Bundle,
             requireSystemProvider: Boolean
-        ): GetCredentialOption {
+        ): CredentialOption {
             return try {
                 when (type) {
                     PasswordCredential.TYPE_PASSWORD_CREDENTIAL ->
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
index 2896622..6dcd0f2 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CredentialProviderFactory.kt
@@ -84,8 +84,10 @@
         @Suppress("deprecation")
         private fun getAllowedProvidersFromManifest(context: Context): List<String> {
             val packageInfo = context.packageManager
-                .getPackageInfo(context.packageName, PackageManager.GET_META_DATA or
-                        PackageManager.GET_SERVICES)
+                .getPackageInfo(
+                    context.packageName, PackageManager.GET_META_DATA or
+                        PackageManager.GET_SERVICES
+                )
 
             val classNames = mutableListOf<String>()
             if (packageInfo.services != null) {
@@ -101,4 +103,4 @@
             return classNames.toList()
         }
     }
-}
\ No newline at end of file
+}
diff --git a/credentials/credentials/src/main/java/androidx/credentials/CustomCredential.kt b/credentials/credentials/src/main/java/androidx/credentials/CustomCredential.kt
index f4138ef..7550543 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/CustomCredential.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/CustomCredential.kt
@@ -21,6 +21,15 @@
 /**
  * Base class for a custom credential with which the user consented to authenticate to the app.
  *
+ * If you get a [CustomCredential] instead of a type-safe credential class such as
+ * [PasswordCredential], [PublicKeyCredential], etc., then you should check if
+ * you have any other library at interest that supports this custom [type] of credential,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ *
+ * Note: The Bundle keys for [data] should not be in the form of `androidx.credentials.*` as they
+ * are reserved for internal use by this androidx library.
+ *
  * @property type the credential type determined by the credential-type-specific subclass for custom
  * use cases
  * @property data the credential data in the [Bundle] format for custom use cases
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
index 3db5108..5dabedd 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCredentialRequest.kt
@@ -19,39 +19,39 @@
 /**
  * Encapsulates a request to get a user credential.
  *
- * An application can construct such a request by adding one or more types of [GetCredentialOption],
- * and then call [CredentialManager.executeGetCredential] to launch framework UI flows to allow the user
+ * An application can construct such a request by adding one or more types of [CredentialOption],
+ * and then call [CredentialManager.getCredential] to launch framework UI flows to allow the user
  * to consent to using a previously saved credential for the given application.
  *
- * @property getCredentialOptions the list of [GetCredentialOption] from which the user can choose
+ * @property credentialOptions the list of [CredentialOption] from which the user can choose
  * one to authenticate to the app
  * @property isAutoSelectAllowed defines if a credential entry will be automatically chosen if it is
  * the only one, false by default
- * @throws IllegalArgumentException If [getCredentialOptions] is empty
+ * @throws IllegalArgumentException If [credentialOptions] is empty
  */
 class GetCredentialRequest @JvmOverloads constructor(
-    val getCredentialOptions: List<GetCredentialOption>,
+    val credentialOptions: List<CredentialOption>,
     val isAutoSelectAllowed: Boolean = false,
 ) {
 
     init {
-        require(getCredentialOptions.isNotEmpty()) { "credentialRequests should not be empty" }
+        require(credentialOptions.isNotEmpty()) { "credentialOptions should not be empty" }
     }
 
     /** A builder for [GetCredentialRequest]. */
     class Builder {
-        private var getCredentialOptions: MutableList<GetCredentialOption> = mutableListOf()
+        private var credentialOptions: MutableList<CredentialOption> = mutableListOf()
         private var autoSelectAllowed: Boolean = false
 
-        /** Adds a specific type of [GetCredentialOption]. */
-        fun addGetCredentialOption(getCredentialOption: GetCredentialOption): Builder {
-            getCredentialOptions.add(getCredentialOption)
+        /** Adds a specific type of [CredentialOption]. */
+        fun addCredentialOption(credentialOption: CredentialOption): Builder {
+            credentialOptions.add(credentialOption)
             return this
         }
 
-        /** Sets the list of [GetCredentialOption]. */
-        fun setGetCredentialOptions(getCredentialOptions: List<GetCredentialOption>): Builder {
-            this.getCredentialOptions = getCredentialOptions.toMutableList()
+        /** Sets the list of [CredentialOption]. */
+        fun setCredentialOptions(credentialOptions: List<CredentialOption>): Builder {
+            this.credentialOptions = credentialOptions.toMutableList()
             return this
         }
 
@@ -67,10 +67,10 @@
         /**
          * Builds a [GetCredentialRequest].
          *
-         * @throws IllegalArgumentException If [getCredentialOptions] is empty
+         * @throws IllegalArgumentException If [credentialOptions] is empty
          */
         fun build(): GetCredentialRequest {
-            return GetCredentialRequest(getCredentialOptions.toList(),
+            return GetCredentialRequest(credentialOptions.toList(),
                 autoSelectAllowed)
         }
     }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
index 2227eb7..546dae8 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetCustomCredentialOption.kt
@@ -21,13 +21,21 @@
 /**
  * Allows extending custom versions of GetCredentialOptions for unique use cases.
  *
+ * If you get a [GetCustomCredentialOption] instead of a type-safe option class such as
+ * [GetPasswordOption], [GetPublicKeyCredentialOption], etc., then you should check if
+ * you have any other library at interest that supports this custom [type] of credential option,
+ * and if so use its parsing utilities to resolve to a type-safe class within that library.
+ *
+ * Note: The Bundle keys for [requestData] and [candidateQueryData] should not be in the form of
+ * `androidx.credentials.*` as they are reserved for internal use by this androidx library.
+ *
  * @property type the credential type determined by the credential-type-specific subclass
  * generated for custom use cases
  * @property requestData the request data in the [Bundle] format, generated for custom use cases
  * @property candidateQueryData the partial request data in the [Bundle] format that will be sent to
  * the provider during the initial candidate query stage, which should not contain sensitive user
  * information
- * @property requireSystemProvider true if must only be fulfilled by a system provider and false
+ * @property isSystemProviderRequired true if must only be fulfilled by a system provider and false
  * otherwise
  * @throws IllegalArgumentException If [type] is empty
  * @throws NullPointerException If [requestData] or [type] is null
@@ -36,13 +44,12 @@
     final override val type: String,
     final override val requestData: Bundle,
     final override val candidateQueryData: Bundle,
-    @get:JvmName("requireSystemProvider")
-    final override val requireSystemProvider: Boolean
-) : GetCredentialOption(
+    final override val isSystemProviderRequired: Boolean
+) : CredentialOption(
     type,
     requestData,
     candidateQueryData,
-    requireSystemProvider
+    isSystemProviderRequired
 ) {
     init {
         require(type.isNotEmpty()) { "type should not be empty" }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
index c49f6b8..2c182ea 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPasswordOption.kt
@@ -19,11 +19,11 @@
 import android.os.Bundle
 
 /** A request to retrieve the user's saved application password from their password provider. */
-class GetPasswordOption : GetCredentialOption(
+class GetPasswordOption : CredentialOption(
     type = PasswordCredential.TYPE_PASSWORD_CREDENTIAL,
     requestData = Bundle(),
     candidateQueryData = Bundle(),
-    requireSystemProvider = false,
+    isSystemProviderRequired = false,
 ) {
     /** @hide */
     companion object {
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
index 8a038d6..c8ae8f0 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOption.kt
@@ -17,7 +17,6 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.annotation.VisibleForTesting
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
@@ -25,21 +24,21 @@
  *
  * @property requestJson the privileged request in JSON format in the standard webauthn web json
  * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default, with hybrid credentials defined
- * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available credential instead of falling back to discovering remote
+ * credentials, and false (default) otherwise
  * @throws NullPointerException If [requestJson] is null
  * @throws IllegalArgumentException If [requestJson] is empty
  */
 class GetPublicKeyCredentialOption @JvmOverloads constructor(
     val requestJson: String,
-    @get:JvmName("allowHybrid")
-    val allowHybrid: Boolean = true,
-) : GetCredentialOption(
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false,
+) : CredentialOption(
     type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    requestData = toRequestDataBundle(requestJson, allowHybrid),
-    candidateQueryData = toRequestDataBundle(requestJson, allowHybrid),
-    requireSystemProvider = false
+    requestData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    candidateQueryData = toRequestDataBundle(requestJson, preferImmediatelyAvailableCredentials),
+    isSystemProviderRequired = false
 ) {
     init {
         require(requestJson.isNotEmpty()) { "requestJson must not be empty" }
@@ -47,23 +46,25 @@
 
     /** @hide */
     companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION =
             "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION"
 
         @JvmStatic
-        internal fun toRequestDataBundle(requestJson: String, allowHybrid: Boolean): Bundle {
+        internal fun toRequestDataBundle(
+            requestJson: String,
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Bundle {
             val bundle = Bundle()
             bundle.putString(
                 PublicKeyCredential.BUNDLE_KEY_SUBTYPE,
                 BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION
             )
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials)
             return bundle
         }
 
@@ -73,8 +74,10 @@
         internal fun createFrom(data: Bundle): GetPublicKeyCredentialOption {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
-                return GetPublicKeyCredentialOption(requestJson!!, (allowHybrid!!) as Boolean)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
+                return GetPublicKeyCredentialOption(requestJson!!,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean)
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
             }
diff --git a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
index 033cd65..4c30a64 100644
--- a/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
+++ b/credentials/credentials/src/main/java/androidx/credentials/GetPublicKeyCredentialOptionPrivileged.kt
@@ -17,7 +17,6 @@
 package androidx.credentials
 
 import android.os.Bundle
-import androidx.annotation.VisibleForTesting
 import androidx.credentials.internal.FrameworkClassParsingException
 
 /**
@@ -28,27 +27,38 @@
  *
  * @property requestJson the privileged request in JSON format in the standard webauthn web json
  * shown [here](https://w3c.github.io/webauthn/#dictdef-publickeycredentialrequestoptionsjson).
- * @property allowHybrid defines whether hybrid credentials are allowed to fulfill this request,
- * true by default, with hybrid credentials defined
- * [here](https://w3c.github.io/webauthn/#dom-authenticatortransport-hybrid)
+ * @property preferImmediatelyAvailableCredentials true if you prefer the operation to return
+ * immediately when there is no available credential instead of falling back to discovering remote
+ * credentials, and false (default) otherwise
  * @property relyingParty the expected true RP ID which will override the one in the [requestJson],
  * where relyingParty is defined [here](https://w3c.github.io/webauthn/#rp-id) in more detail
  * @property clientDataHash a hash that is used to verify the [relyingParty] Identity
  * @throws NullPointerException If any of [requestJson], [relyingParty], or [clientDataHash]
  * is null
- * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is empty
+ * @throws IllegalArgumentException If any of [requestJson], [relyingParty], or [clientDataHash] is
+ * empty
  */
 class GetPublicKeyCredentialOptionPrivileged @JvmOverloads constructor(
     val requestJson: String,
     val relyingParty: String,
     val clientDataHash: String,
-    @get:JvmName("allowHybrid")
-    val allowHybrid: Boolean = true
-) : GetCredentialOption(
+    @get:JvmName("preferImmediatelyAvailableCredentials")
+    val preferImmediatelyAvailableCredentials: Boolean = false
+) : CredentialOption(
     type = PublicKeyCredential.TYPE_PUBLIC_KEY_CREDENTIAL,
-    requestData = toBundle(requestJson, relyingParty, clientDataHash, allowHybrid),
-    candidateQueryData = toBundle(requestJson, relyingParty, clientDataHash, allowHybrid),
-    requireSystemProvider = false,
+    requestData = toBundle(
+        requestJson,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
+    candidateQueryData = toBundle(
+        requestJson,
+        relyingParty,
+        clientDataHash,
+        preferImmediatelyAvailableCredentials
+    ),
+    isSystemProviderRequired = false,
 ) {
 
     init {
@@ -62,9 +72,9 @@
         private var requestJson: String,
         private var relyingParty: String,
         private var clientDataHash: String
-        ) {
+    ) {
 
-        private var allowHybrid: Boolean = true
+        private var preferImmediatelyAvailableCredentials: Boolean = false
 
         /**
          * Sets the privileged request in JSON format.
@@ -75,11 +85,17 @@
         }
 
         /**
-         * Sets whether hybrid credentials are allowed to fulfill this request, true by default.
+         * Sets to true if you prefer the operation to return immediately when there is no available
+         * credential instead of falling back to discovering remote credentials, and false
+         * otherwise.
+         *
+         * The default value is false.
          */
         @Suppress("MissingGetterMatchingBuilder")
-        fun setAllowHybrid(allowHybrid: Boolean): Builder {
-            this.allowHybrid = allowHybrid
+        fun setPreferImmediatelyAvailableCredentials(
+            preferImmediatelyAvailableCredentials: Boolean
+        ): Builder {
+            this.preferImmediatelyAvailableCredentials = preferImmediatelyAvailableCredentials
             return this
         }
 
@@ -101,24 +117,23 @@
 
         /** Builds a [GetPublicKeyCredentialOptionPrivileged]. */
         fun build(): GetPublicKeyCredentialOptionPrivileged {
-            return GetPublicKeyCredentialOptionPrivileged(this.requestJson,
-                this.relyingParty, this.clientDataHash, this.allowHybrid)
+            return GetPublicKeyCredentialOptionPrivileged(
+                this.requestJson,
+                this.relyingParty, this.clientDataHash, this.preferImmediatelyAvailableCredentials
+            )
         }
     }
 
     /** @hide */
     companion object {
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_RELYING_PARTY = "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_CLIENT_DATA_HASH =
+        internal const val BUNDLE_KEY_RELYING_PARTY =
+            "androidx.credentials.BUNDLE_KEY_RELYING_PARTY"
+        internal const val BUNDLE_KEY_CLIENT_DATA_HASH =
             "androidx.credentials.BUNDLE_KEY_CLIENT_DATA_HASH"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_ALLOW_HYBRID = "androidx.credentials.BUNDLE_KEY_ALLOW_HYBRID"
-        @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
-        const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
-        @VisibleForTesting(otherwise = VisibleForTesting.PACKAGE_PRIVATE)
-        const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
+        internal const val BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS =
+            "androidx.credentials.BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS"
+        internal const val BUNDLE_KEY_REQUEST_JSON = "androidx.credentials.BUNDLE_KEY_REQUEST_JSON"
+        internal const val BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION_PRIVILEGED =
             "androidx.credentials.BUNDLE_VALUE_SUBTYPE_GET_PUBLIC_KEY_CREDENTIAL_OPTION" +
                 "_PRIVILEGED"
 
@@ -127,7 +142,7 @@
             requestJson: String,
             relyingParty: String,
             clientDataHash: String,
-            allowHybrid: Boolean
+            preferImmediatelyAvailableCredentials: Boolean
         ): Bundle {
             val bundle = Bundle()
             bundle.putString(
@@ -137,24 +152,28 @@
             bundle.putString(BUNDLE_KEY_REQUEST_JSON, requestJson)
             bundle.putString(BUNDLE_KEY_RELYING_PARTY, relyingParty)
             bundle.putString(BUNDLE_KEY_CLIENT_DATA_HASH, clientDataHash)
-            bundle.putBoolean(BUNDLE_KEY_ALLOW_HYBRID, allowHybrid)
+            bundle.putBoolean(
+                BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS,
+                preferImmediatelyAvailableCredentials
+            )
             return bundle
         }
 
         @Suppress("deprecation") // bundle.get() used for boolean value to prevent default
-                                         // boolean value from being returned.
+        // boolean value from being returned.
         @JvmStatic
         internal fun createFrom(data: Bundle): GetPublicKeyCredentialOptionPrivileged {
             try {
                 val requestJson = data.getString(BUNDLE_KEY_REQUEST_JSON)
                 val rp = data.getString(BUNDLE_KEY_RELYING_PARTY)
                 val clientDataHash = data.getString(BUNDLE_KEY_CLIENT_DATA_HASH)
-                val allowHybrid = data.get(BUNDLE_KEY_ALLOW_HYBRID)
+                val preferImmediatelyAvailableCredentials =
+                    data.get(BUNDLE_KEY_PREFER_IMMEDIATELY_AVAILABLE_CREDENTIALS)
                 return GetPublicKeyCredentialOptionPrivileged(
                     requestJson!!,
                     rp!!,
                     clientDataHash!!,
-                    (allowHybrid!!) as Boolean,
+                    (preferImmediatelyAvailableCredentials!!) as Boolean,
                 )
             } catch (e: Exception) {
                 throw FrameworkClassParsingException()
diff --git a/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt b/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt
new file mode 100644
index 0000000..ccea9d2
--- /dev/null
+++ b/credentials/credentials/src/main/java/androidx/credentials/internal/FrameworkImplHelper.kt
@@ -0,0 +1,65 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.credentials.internal
+
+import android.content.Context
+import android.graphics.drawable.Icon
+import android.os.Bundle
+import androidx.annotation.RequiresApi
+import androidx.credentials.CreateCredentialRequest
+import androidx.credentials.CreatePasswordRequest
+import androidx.credentials.CreatePublicKeyCredentialRequest
+import androidx.credentials.CreatePublicKeyCredentialRequestPrivileged
+import androidx.credentials.R
+
+/** @hide */
+@RequiresApi(23)
+class FrameworkImplHelper {
+    companion object {
+        /**
+         * Take the create request's `credentialData` and add SDK specific values to it.
+         *
+         * @hide
+         */
+        @JvmStatic
+        @RequiresApi(23)
+        fun getFinalCreateCredentialData(
+            request: CreateCredentialRequest,
+            activity: Context,
+        ): Bundle {
+            val createCredentialData = request.credentialData
+            val displayInfoBundle = request.displayInfo.toBundle()
+            displayInfoBundle.putParcelable(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_CREDENTIAL_TYPE_ICON,
+                Icon.createWithResource(
+                    activity,
+                    when (request) {
+                        is CreatePasswordRequest -> R.drawable.ic_password
+                        is CreatePublicKeyCredentialRequest -> R.drawable.ic_passkey
+                        is CreatePublicKeyCredentialRequestPrivileged -> R.drawable.ic_passkey
+                        else -> R.drawable.ic_other_sign_in
+                    }
+                )
+            )
+            createCredentialData.putBundle(
+                CreateCredentialRequest.DisplayInfo.BUNDLE_KEY_REQUEST_DISPLAY_INFO,
+                displayInfoBundle
+            )
+            return createCredentialData
+        }
+    }
+}
\ No newline at end of file
diff --git a/credentials/credentials/src/main/res/drawable/ic_other_sign_in.xml b/credentials/credentials/src/main/res/drawable/ic_other_sign_in.xml
new file mode 100644
index 0000000..d1bb37e
--- /dev/null
+++ b/credentials/credentials/src/main/res/drawable/ic_other_sign_in.xml
@@ -0,0 +1,36 @@
+<!--
+  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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 20 19 L 12 19 L 12 21 L 20 21 C 21.1 21 22 20.1 22 19 L 22 5 C 22 3.9 21.1 3 20 3 L 12 3 L 12 5 L 20 5 L 20 19 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+    <path
+        android:name="path_1"
+        android:pathData="M 12 7 L 10.6 8.4 L 13.2 11 L 8.85 11 C 8.42 9.55 7.09 8.5 5.5 8.5 C 3.57 8.5 2 10.07 2 12 C 2 13.93 3.57 15.5 5.5 15.5 C 7.09 15.5 8.42 14.45 8.85 13 L 13.2 13 L 10.6 15.6 L 12 17 L 17 12 L 12 7 Z M 5.5 13.5 C 4.67 13.5 4 12.83 4 12 C 4 11.17 4.67 10.5 5.5 10.5 C 6.33 10.5 7 11.17 7 12 C 7 12.83 6.33 13.5 5.5 13.5 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/credentials/credentials/src/main/res/drawable/ic_passkey.xml b/credentials/credentials/src/main/res/drawable/ic_passkey.xml
new file mode 100644
index 0000000..9c4304e
--- /dev/null
+++ b/credentials/credentials/src/main/res/drawable/ic_passkey.xml
@@ -0,0 +1,32 @@
+<!--
+  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.
+  -->
+
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="28dp"
+    android:height="24dp"
+    android:viewportWidth="28"
+    android:viewportHeight="24">
+    <path
+        android:pathData="M27.453,13.253C27.453,14.952 26.424,16.411 24.955,17.041L26.21,18.295L24.839,19.666L26.21,21.037L23.305,23.942L22.012,22.65L22.012,17.156C20.385,16.605 19.213,15.066 19.213,13.253C19.213,10.977 21.058,9.133 23.333,9.133C25.609,9.133 27.453,10.977 27.453,13.253ZM25.47,13.254C25.47,14.434 24.514,15.39 23.334,15.39C22.154,15.39 21.197,14.434 21.197,13.254C21.197,12.074 22.154,11.118 23.334,11.118C24.514,11.118 25.47,12.074 25.47,13.254Z"
+        android:fillColor="#00639B"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="M17.85,5.768C17.85,8.953 15.268,11.536 12.083,11.536C8.897,11.536 6.315,8.953 6.315,5.768C6.315,2.582 8.897,0 12.083,0C15.268,0 17.85,2.582 17.85,5.768Z"
+        android:fillColor="#00639B"/>
+    <path
+        android:pathData="M0.547,20.15C0.547,16.32 8.23,14.382 12.083,14.382C13.59,14.382 15.684,14.679 17.674,15.269C18.116,16.454 18.952,17.447 20.022,18.089V23.071H0.547V20.15Z"
+        android:fillColor="#00639B"/>
+</vector>
\ No newline at end of file
diff --git a/credentials/credentials/src/main/res/drawable/ic_password.xml b/credentials/credentials/src/main/res/drawable/ic_password.xml
new file mode 100644
index 0000000..1fb71cf
--- /dev/null
+++ b/credentials/credentials/src/main/res/drawable/ic_password.xml
@@ -0,0 +1,31 @@
+<!--
+  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.
+  -->
+
+<vector
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    tools:ignore="VectorPath"
+    android:name="vector"
+    android:width="24dp"
+    android:height="24dp"
+    android:viewportWidth="24"
+    android:viewportHeight="24">
+    <path
+        android:name="path"
+        android:pathData="M 8.71 10.29 C 8.52 10.1 8.28 10 8 10 L 7.75 10 L 7.75 8.75 C 7.75 7.98 7.48 7.33 6.95 6.8 C 6.42 6.27 5.77 6 5 6 C 4.23 6 3.58 6.27 3.05 6.8 C 2.52 7.33 2.25 7.98 2.25 8.75 L 2.25 10 L 2 10 C 1.72 10 1.48 10.1 1.29 10.29 C 1.1 10.48 1 10.72 1 11 L 1 16 C 1 16.28 1.1 16.52 1.29 16.71 C 1.48 16.9 1.72 17 2 17 L 8 17 C 8.28 17 8.52 16.9 8.71 16.71 C 8.9 16.52 9 16.28 9 16 L 9 11 C 9 10.72 8.9 10.48 8.71 10.29 Z M 6.25 10 L 3.75 10 L 3.75 8.75 C 3.75 8.4 3.87 8.1 4.11 7.86 C 4.35 7.62 4.65 7.5 5 7.5 C 5.35 7.5 5.65 7.62 5.89 7.86 C 6.13 8.1 6.25 8.4 6.25 8.75 L 6.25 10 Z M 10 14 L 23 14 L 23 16 L 10 16 Z M 21.5 9 C 21.102 9 20.721 9.158 20.439 9.439 C 20.158 9.721 20 10.102 20 10.5 C 20 10.898 20.158 11.279 20.439 11.561 C 20.721 11.842 21.102 12 21.5 12 C 21.898 12 22.279 11.842 22.561 11.561 C 22.842 11.279 23 10.898 23 10.5 C 23 10.102 22.842 9.721 22.561 9.439 C 22.279 9.158 21.898 9 21.5 9 Z M 16.5 9 C 16.102 9 15.721 9.158 15.439 9.439 C 15.158 9.721 15 10.102 15 10.5 C 15 10.898 15.158 11.279 15.439 11.561 C 15.721 11.842 16.102 12 16.5 12 C 16.898 12 17.279 11.842 17.561 11.561 C 17.842 11.279 18 10.898 18 10.5 C 18 10.102 17.842 9.721 17.561 9.439 C 17.279 9.158 16.898 9 16.5 9 Z M 11.5 9 C 11.102 9 10.721 9.158 10.439 9.439 C 10.158 9.721 10 10.102 10 10.5 C 10 10.898 10.158 11.279 10.439 11.561 C 10.721 11.842 11.102 12 11.5 12 C 11.898 12 12.279 11.842 12.561 11.561 C 12.842 11.279 13 10.898 13 10.5 C 13 10.102 12.842 9.721 12.561 9.439 C 12.279 9.158 11.898 9 11.5 9 Z"
+        android:fillColor="#000"
+        android:strokeWidth="1"/>
+</vector>
\ No newline at end of file
diff --git a/credentials/credentials/src/main/res/values-af/strings.xml b/credentials/credentials/src/main/res/values-af/strings.xml
new file mode 100644
index 0000000..324d169
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-af/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Wagwoordsleutel"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Wagwoord"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-am/strings.xml b/credentials/credentials/src/main/res/values-am/strings.xml
new file mode 100644
index 0000000..becd4e28
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-am/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"የይለፍ ቁልፍ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"የይለፍ ቃል"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ar/strings.xml b/credentials/credentials/src/main/res/values-ar/strings.xml
new file mode 100644
index 0000000..0af8c23
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ar/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"مفتاح المرور"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"كلمة المرور"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-as/strings.xml b/credentials/credentials/src/main/res/values-as/strings.xml
new file mode 100644
index 0000000..54f51cb
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-as/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"পাছকী"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"পাছৱৰ্ড"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-az/strings.xml b/credentials/credentials/src/main/res/values-az/strings.xml
new file mode 100644
index 0000000..8c1756f
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-az/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Giriş açarı"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Parol"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-b+sr+Latn/strings.xml b/credentials/credentials/src/main/res/values-b+sr+Latn/strings.xml
new file mode 100644
index 0000000..9cf1ec4
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-b+sr+Latn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Pristupni kôd"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Lozinka"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-be/strings.xml b/credentials/credentials/src/main/res/values-be/strings.xml
new file mode 100644
index 0000000..4bd1021
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-be/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ключ доступу"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Пароль"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-bg/strings.xml b/credentials/credentials/src/main/res/values-bg/strings.xml
new file mode 100644
index 0000000..262756f
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-bg/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Код за достъп"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Парола"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-bn/strings.xml b/credentials/credentials/src/main/res/values-bn/strings.xml
new file mode 100644
index 0000000..ba552e9
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-bn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"পাসকী"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"পাসওয়ার্ড"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-bs/strings.xml b/credentials/credentials/src/main/res/values-bs/strings.xml
new file mode 100644
index 0000000..d1bd317
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-bs/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Pristupni ključ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Lozinka"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ca/strings.xml b/credentials/credentials/src/main/res/values-ca/strings.xml
new file mode 100644
index 0000000..ee5660e
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ca/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Clau d\'accés"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Contrasenya"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-cs/strings.xml b/credentials/credentials/src/main/res/values-cs/strings.xml
new file mode 100644
index 0000000..6d618e8
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-cs/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Přístupový klíč"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Heslo"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-da/strings.xml b/credentials/credentials/src/main/res/values-da/strings.xml
new file mode 100644
index 0000000..f8c9e29
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-da/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Adgangsnøgle"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Adgangskode"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-de/strings.xml b/credentials/credentials/src/main/res/values-de/strings.xml
new file mode 100644
index 0000000..49d54d2
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-de/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Passwort"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-el/strings.xml b/credentials/credentials/src/main/res/values-el/strings.xml
new file mode 100644
index 0000000..5c2d73e
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-el/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Κλειδί πρόσβασης"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Κωδικός πρόσβασης"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-en-rAU/strings.xml b/credentials/credentials/src/main/res/values-en-rAU/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-en-rAU/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-en-rCA/strings.xml b/credentials/credentials/src/main/res/values-en-rCA/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-en-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-en-rGB/strings.xml b/credentials/credentials/src/main/res/values-en-rGB/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-en-rGB/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-en-rIN/strings.xml b/credentials/credentials/src/main/res/values-en-rIN/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-en-rIN/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-en-rXC/strings.xml b/credentials/credentials/src/main/res/values-en-rXC/strings.xml
new file mode 100644
index 0000000..ee94f40
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-en-rXC/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‎‏‏‎‏‏‎‏‎‎‎‎‏‏‎‏‎‏‎‏‎‏‎‎‏‏‎‎‏‏‏‎‎‏‎‏‏‏‎‎‎‎‏‏‏‎‎‏‎‏‎‏‎‏‏‏‏‏‏‎‏‏‎‎Passkey‎‏‎‎‏‎"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"‎‏‎‎‎‎‎‏‎‏‏‏‎‎‎‎‎‎‏‎‎‏‎‎‎‎‏‏‏‏‏‏‏‏‏‏‎‏‎‎‏‎‎‎‏‎‎‎‎‎‏‏‎‎‎‏‎‎‏‎‎‏‎‏‏‎‏‏‏‎‎‏‏‎‏‎‏‎‏‏‎‏‏‏‏‏‏‏‏‎‏‎‎‎‏‏‎Password‎‏‎‎‏‎"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-es-rUS/strings.xml b/credentials/credentials/src/main/res/values-es-rUS/strings.xml
new file mode 100644
index 0000000..898ed7d9
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-es-rUS/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Llave de acceso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Contraseña"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-es/strings.xml b/credentials/credentials/src/main/res/values-es/strings.xml
new file mode 100644
index 0000000..898ed7d9
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-es/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Llave de acceso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Contraseña"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-et/strings.xml b/credentials/credentials/src/main/res/values-et/strings.xml
new file mode 100644
index 0000000..294c693
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-et/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Pääsuvõti"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Parool"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-eu/strings.xml b/credentials/credentials/src/main/res/values-eu/strings.xml
new file mode 100644
index 0000000..b1db172
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-eu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Sarbide-gakoa"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Pasahitza"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-fa/strings.xml b/credentials/credentials/src/main/res/values-fa/strings.xml
new file mode 100644
index 0000000..f1b8f5f
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-fa/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"گذرکلید"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"گذرواژه"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-fi/strings.xml b/credentials/credentials/src/main/res/values-fi/strings.xml
new file mode 100644
index 0000000..8adce03
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-fi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Avainkoodi"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Salasana"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-fr-rCA/strings.xml b/credentials/credentials/src/main/res/values-fr-rCA/strings.xml
new file mode 100644
index 0000000..555784b
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-fr-rCA/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Clé d\'accès"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Mot de passe"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-fr/strings.xml b/credentials/credentials/src/main/res/values-fr/strings.xml
new file mode 100644
index 0000000..555784b
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-fr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Clé d\'accès"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Mot de passe"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-gl/strings.xml b/credentials/credentials/src/main/res/values-gl/strings.xml
new file mode 100644
index 0000000..6bf8e92
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-gl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Clave de acceso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Contrasinal"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-gu/strings.xml b/credentials/credentials/src/main/res/values-gu/strings.xml
new file mode 100644
index 0000000..d63c6d9
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-gu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"પાસકી"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"પાસવર્ડ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-hi/strings.xml b/credentials/credentials/src/main/res/values-hi/strings.xml
new file mode 100644
index 0000000..bbcc98c
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-hi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"पासकी"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"पासवर्ड"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-hr/strings.xml b/credentials/credentials/src/main/res/values-hr/strings.xml
new file mode 100644
index 0000000..6edcb91
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-hr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Pristupni ključ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Zaporka"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-hu/strings.xml b/credentials/credentials/src/main/res/values-hu/strings.xml
new file mode 100644
index 0000000..c0e0cb3
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-hu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Azonosítókulcs"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Jelszó"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-hy/strings.xml b/credentials/credentials/src/main/res/values-hy/strings.xml
new file mode 100644
index 0000000..617300a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-hy/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Անցաբառ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Գաղտնաբառ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-in/strings.xml b/credentials/credentials/src/main/res/values-in/strings.xml
new file mode 100644
index 0000000..1683197
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-in/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Kunci sandi"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Sandi"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-is/strings.xml b/credentials/credentials/src/main/res/values-is/strings.xml
new file mode 100644
index 0000000..e98a644
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-is/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Aðgangslykill"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Aðgangsorð"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-it/strings.xml b/credentials/credentials/src/main/res/values-it/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-it/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-iw/strings.xml b/credentials/credentials/src/main/res/values-iw/strings.xml
new file mode 100644
index 0000000..3ddedd6
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-iw/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"מפתח גישה"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"סיסמה"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ja/strings.xml b/credentials/credentials/src/main/res/values-ja/strings.xml
new file mode 100644
index 0000000..2e926be
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ja/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"パスキー"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"パスワード"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ka/strings.xml b/credentials/credentials/src/main/res/values-ka/strings.xml
new file mode 100644
index 0000000..40c308b
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ka/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"წვდომის გასაღები"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"პაროლი"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-kk/strings.xml b/credentials/credentials/src/main/res/values-kk/strings.xml
new file mode 100644
index 0000000..592eff2
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-kk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Кіру кілті"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Құпия сөз"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-km/strings.xml b/credentials/credentials/src/main/res/values-km/strings.xml
new file mode 100644
index 0000000..0aa413a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-km/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"កូដសម្ងាត់"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"ពាក្យសម្ងាត់"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-kn/strings.xml b/credentials/credentials/src/main/res/values-kn/strings.xml
new file mode 100644
index 0000000..c57f6209
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-kn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"ಪಾಸ್‌ಕೀ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"ಪಾಸ್‌ವರ್ಡ್"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ko/strings.xml b/credentials/credentials/src/main/res/values-ko/strings.xml
new file mode 100644
index 0000000..dc494ec
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ko/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"패스키"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"비밀번호"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ky/strings.xml b/credentials/credentials/src/main/res/values-ky/strings.xml
new file mode 100644
index 0000000..3366129
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ky/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Мүмкүндүк алуу ачкычы"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Сырсөз"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-lo/strings.xml b/credentials/credentials/src/main/res/values-lo/strings.xml
new file mode 100644
index 0000000..d642614
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-lo/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"ກະແຈຜ່ານ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"ລະຫັດຜ່ານ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-lt/strings.xml b/credentials/credentials/src/main/res/values-lt/strings.xml
new file mode 100644
index 0000000..ee87b96
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-lt/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Slaptažodis"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-lv/strings.xml b/credentials/credentials/src/main/res/values-lv/strings.xml
new file mode 100644
index 0000000..9d79e3a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-lv/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Piekļuves atslēga"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Parole"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-mk/strings.xml b/credentials/credentials/src/main/res/values-mk/strings.xml
new file mode 100644
index 0000000..96f6efe
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-mk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Криптографски клуч"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Лозинка"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ml/strings.xml b/credentials/credentials/src/main/res/values-ml/strings.xml
new file mode 100644
index 0000000..e272342
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ml/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"പാസ്‌കീ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"പാസ്‌വേഡ്"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-mn/strings.xml b/credentials/credentials/src/main/res/values-mn/strings.xml
new file mode 100644
index 0000000..59e9ded
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-mn/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Нууц үг"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-mr/strings.xml b/credentials/credentials/src/main/res/values-mr/strings.xml
new file mode 100644
index 0000000..bbcc98c
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-mr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"पासकी"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"पासवर्ड"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ms/strings.xml b/credentials/credentials/src/main/res/values-ms/strings.xml
new file mode 100644
index 0000000..aacb31b
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ms/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Kunci laluan"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Kata Laluan"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-my/strings.xml b/credentials/credentials/src/main/res/values-my/strings.xml
new file mode 100644
index 0000000..3ea63a4
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-my/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"လျှို့ဝှက်ကီး"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"စကားဝှက်"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-nb/strings.xml b/credentials/credentials/src/main/res/values-nb/strings.xml
new file mode 100644
index 0000000..a72318a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-nb/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Tilgangsnøkkel"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Passord"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ne/strings.xml b/credentials/credentials/src/main/res/values-ne/strings.xml
new file mode 100644
index 0000000..bbcc98c
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ne/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"पासकी"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"पासवर्ड"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-nl/strings.xml b/credentials/credentials/src/main/res/values-nl/strings.xml
new file mode 100644
index 0000000..db3ba80
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-nl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Toegangssleutel"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Wachtwoord"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-or/strings.xml b/credentials/credentials/src/main/res/values-or/strings.xml
new file mode 100644
index 0000000..6f31314
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-or/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"ପାସକୀ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"ପାସୱାର୍ଡ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-pa/strings.xml b/credentials/credentials/src/main/res/values-pa/strings.xml
new file mode 100644
index 0000000..ca4c10a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-pa/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"ਪਾਸਕੀ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"ਪਾਸਵਰਡ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-pl/strings.xml b/credentials/credentials/src/main/res/values-pl/strings.xml
new file mode 100644
index 0000000..7c4d4c1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-pl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Klucz"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Hasło"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-pt-rBR/strings.xml b/credentials/credentials/src/main/res/values-pt-rBR/strings.xml
new file mode 100644
index 0000000..554e9b8
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-pt-rBR/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Chave de acesso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Senha"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-pt-rPT/strings.xml b/credentials/credentials/src/main/res/values-pt-rPT/strings.xml
new file mode 100644
index 0000000..f405d93
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-pt-rPT/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Chave de acesso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Palavra-passe"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-pt/strings.xml b/credentials/credentials/src/main/res/values-pt/strings.xml
new file mode 100644
index 0000000..554e9b8
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-pt/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Chave de acesso"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Senha"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ro/strings.xml b/credentials/credentials/src/main/res/values-ro/strings.xml
new file mode 100644
index 0000000..9748df0
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ro/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Cheie de acces"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Parolă"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ru/strings.xml b/credentials/credentials/src/main/res/values-ru/strings.xml
new file mode 100644
index 0000000..d3e3ce4
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ru/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ключ доступа"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Пароль"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-si/strings.xml b/credentials/credentials/src/main/res/values-si/strings.xml
new file mode 100644
index 0000000..4eee3ad
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-si/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"මුරයතුර"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"මුරපදය"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sk/strings.xml b/credentials/credentials/src/main/res/values-sk/strings.xml
new file mode 100644
index 0000000..f92dc9a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Prístupový kľúč"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Heslo"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sl/strings.xml b/credentials/credentials/src/main/res/values-sl/strings.xml
new file mode 100644
index 0000000..38907ef
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ključ za dostop"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Geslo"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sq/strings.xml b/credentials/credentials/src/main/res/values-sq/strings.xml
new file mode 100644
index 0000000..00f2ae3
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sq/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Çelësi i kalimit"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Fjalëkalimi"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sr/strings.xml b/credentials/credentials/src/main/res/values-sr/strings.xml
new file mode 100644
index 0000000..0e0be5e
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Приступни кôд"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Лозинка"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sv/strings.xml b/credentials/credentials/src/main/res/values-sv/strings.xml
new file mode 100644
index 0000000..808fd8c
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sv/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Nyckel"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Lösenord"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-sw/strings.xml b/credentials/credentials/src/main/res/values-sw/strings.xml
new file mode 100644
index 0000000..a129b58
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-sw/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ufunguo wa siri"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Nenosiri"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ta/strings.xml b/credentials/credentials/src/main/res/values-ta/strings.xml
new file mode 100644
index 0000000..458bcb4
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ta/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"கடவுக்குறியீடு"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"கடவுச்சொல்"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-te/strings.xml b/credentials/credentials/src/main/res/values-te/strings.xml
new file mode 100644
index 0000000..67ad9ab
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-te/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"పాస్-కీ"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"పాస్‌వర్డ్"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-th/strings.xml b/credentials/credentials/src/main/res/values-th/strings.xml
new file mode 100644
index 0000000..e2b685f
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-th/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"พาสคีย์"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"รหัสผ่าน"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-tl/strings.xml b/credentials/credentials/src/main/res/values-tl/strings.xml
new file mode 100644
index 0000000..b9f9ba1
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-tl/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Passkey"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Password"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-tr/strings.xml b/credentials/credentials/src/main/res/values-tr/strings.xml
new file mode 100644
index 0000000..f00b298
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-tr/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Şifre anahtarı"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Şifre"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-uk/strings.xml b/credentials/credentials/src/main/res/values-uk/strings.xml
new file mode 100644
index 0000000..4bd1021
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-uk/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ключ доступу"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Пароль"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-ur/strings.xml b/credentials/credentials/src/main/res/values-ur/strings.xml
new file mode 100644
index 0000000..3183ec3
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-ur/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"پاس کی"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"پاس ورڈ"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-uz/strings.xml b/credentials/credentials/src/main/res/values-uz/strings.xml
new file mode 100644
index 0000000..7f1bb8c
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-uz/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Kod"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Parol"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-vi/strings.xml b/credentials/credentials/src/main/res/values-vi/strings.xml
new file mode 100644
index 0000000..28a4e5a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-vi/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Mã xác thực"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Mật khẩu"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-zh-rCN/strings.xml b/credentials/credentials/src/main/res/values-zh-rCN/strings.xml
new file mode 100644
index 0000000..8f3d028
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-zh-rCN/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"通行密钥"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"密码"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-zh-rHK/strings.xml b/credentials/credentials/src/main/res/values-zh-rHK/strings.xml
new file mode 100644
index 0000000..6111c53
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-zh-rHK/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"密鑰"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"密碼"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-zh-rTW/strings.xml b/credentials/credentials/src/main/res/values-zh-rTW/strings.xml
new file mode 100644
index 0000000..884239a
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-zh-rTW/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"密碼金鑰"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"密碼"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values-zu/strings.xml b/credentials/credentials/src/main/res/values-zu/strings.xml
new file mode 100644
index 0000000..f14c8e8
--- /dev/null
+++ b/credentials/credentials/src/main/res/values-zu/strings.xml
@@ -0,0 +1,22 @@
+<?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.
+   -->
+
+<resources xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL" msgid="3929015085059320822">"Ukhiye wokudlula"</string>
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL" msgid="8397015543330865059">"Iphasiwedi"</string>
+</resources>
diff --git a/credentials/credentials/src/main/res/values/strings.xml b/credentials/credentials/src/main/res/values/strings.xml
new file mode 100644
index 0000000..2fef094
--- /dev/null
+++ b/credentials/credentials/src/main/res/values/strings.xml
@@ -0,0 +1,23 @@
+<?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.
+  -->
+
+<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
+    <!-- Credential type label for passkey -->
+    <string name="androidx.credentials.TYPE_PUBLIC_KEY_CREDENTIAL">Passkey</string>
+    <!-- Credential type label for password -->
+    <string name="android.credentials.TYPE_PASSWORD_CREDENTIAL">Password</string>
+</resources>
\ No newline at end of file
diff --git a/development/importMaven/build.gradle.kts b/development/importMaven/build.gradle.kts
index 38f9bc1..86205a6 100644
--- a/development/importMaven/build.gradle.kts
+++ b/development/importMaven/build.gradle.kts
@@ -64,10 +64,6 @@
     testImplementation(importMavenLibs.okioFakeFilesystem)
 }
 
-tasks.withType<KotlinCompile> {
-    kotlinOptions.jvmTarget = "11"
-}
-
 // b/250726951 Gradle ProjectBuilder needs reflection access to java.lang.
 val jvmAddOpensArgs = listOf("--add-opens=java.base/java.lang=ALL-UNNAMED")
 tasks.withType<Test>() {
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 3fad297..99ec90b 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -23,9 +23,9 @@
     docs("androidx.appsearch:appsearch-ktx:1.1.0-alpha02")
     docs("androidx.appsearch:appsearch-local-storage:1.1.0-alpha02")
     docs("androidx.appsearch:appsearch-platform-storage:1.1.0-alpha02")
-    docs("androidx.arch.core:core-common:2.2.0-beta01")
-    docs("androidx.arch.core:core-runtime:2.2.0-beta01")
-    docs("androidx.arch.core:core-testing:2.2.0-beta01")
+    docs("androidx.arch.core:core-common:2.2.0-rc01")
+    docs("androidx.arch.core:core-runtime:2.2.0-rc01")
+    docs("androidx.arch.core:core-testing:2.2.0-rc01")
     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")
diff --git a/drawerlayout/OWNERS b/drawerlayout/OWNERS
deleted file mode 100644
index 0d15e98..0000000
--- a/drawerlayout/OWNERS
+++ /dev/null
@@ -1 +0,0 @@
-sjgilbert@google.com
diff --git a/external/paparazzi/paparazzi/build.gradle b/external/paparazzi/paparazzi/build.gradle
index 3fbe6a3..8e87582 100644
--- a/external/paparazzi/paparazzi/build.gradle
+++ b/external/paparazzi/paparazzi/build.gradle
@@ -39,7 +39,7 @@
 
     compileOnlyAarAsJar("androidx.compose.runtime:runtime:1.2.1")
     compileOnlyAarAsJar("androidx.compose.ui:ui:1.2.1")
-    compileOnly("androidx.lifecycle:lifecycle-common:2.5.0")
+    compileOnly(project(":lifecycle:lifecycle-common"))
     compileOnlyAarAsJar(project(":lifecycle:lifecycle-runtime"))
     compileOnlyAarAsJar("androidx.savedstate:savedstate:1.2.0")
 
diff --git a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
index da62b21..0a96bcf 100644
--- a/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
+++ b/external/paparazzi/paparazzi/src/main/java/app/cash/paparazzi/Paparazzi.kt
@@ -602,7 +602,8 @@
     private val lifecycleRegistry: LifecycleRegistry = LifecycleRegistry(this)
     private val savedStateRegistryController = SavedStateRegistryController.create(this)
 
-    override fun getLifecycle(): Lifecycle = lifecycleRegistry
+    override val lifecycle: Lifecycle
+      get() = lifecycleRegistry
     override val savedStateRegistry: SavedStateRegistry =
       savedStateRegistryController.savedStateRegistry
 
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.kt
index 18af166..319226a 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/FragmentArchLifecycleTest.kt
@@ -294,9 +294,8 @@
 
     private val lifecycleRegistry = LifecycleRegistry(this)
 
-    override fun getLifecycle(): Lifecycle {
-        return lifecycleRegistry
-    }
+    override val lifecycle: Lifecycle
+        get() = lifecycleRegistry
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
index fa0e117..46cfa21 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/AppWidgetSession.kt
@@ -31,6 +31,7 @@
 import androidx.compose.runtime.neverEqualPolicy
 import androidx.compose.runtime.produceState
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.snapshots.Snapshot
 import androidx.compose.ui.unit.DpSize
 import androidx.datastore.preferences.core.emptyPreferences
 import androidx.glance.EmittableWithChildren
@@ -124,9 +125,9 @@
     override suspend fun processEmittableTree(
         context: Context,
         root: EmittableWithChildren
-    ) {
+    ): Boolean {
+        if (root.shouldIgnoreResult()) return false
         root as RemoteViewsRoot
-        if (root.shouldIgnoreResult()) return
         val layoutConfig = LayoutConfiguration.load(context, id.appWidgetId)
         val appWidgetManager = context.appWidgetManager
         try {
@@ -160,14 +161,18 @@
             layoutConfig.save()
             Tracing.endGlanceAppWidgetUpdate()
         }
+        return true
     }
 
     override suspend fun processEvent(context: Context, event: Any) {
         when (event) {
             is UpdateGlanceState -> {
                 if (DEBUG) Log.i(TAG, "Received UpdateGlanceState event for session($key)")
-                glanceState.value =
+                val newGlanceState =
                     configManager.getValue(context, PreferencesGlanceStateDefinition, key)
+                Snapshot.withMutableSnapshot {
+                    glanceState.value = newGlanceState
+                }
             }
             is UpdateAppWidgetOptions -> {
                 if (DEBUG) {
@@ -177,15 +182,15 @@
                             "for session($key)"
                     )
                 }
-                options.value = event.newOptions
+                Snapshot.withMutableSnapshot {
+                    options.value = event.newOptions
+                }
             }
             is RunLambda -> {
-                Log.i(TAG, "Received RunLambda(${event.key}) action for session($key)")
-                lambdas[event.key]?.map { it.block() }
-                    ?: Log.w(
-                        TAG,
-                        "Triggering Action(${event.key}) for session($key) failed"
-                    )
+                if (DEBUG) Log.i(TAG, "Received RunLambda(${event.key}) action for session($key)")
+                Snapshot.withMutableSnapshot {
+                    lambdas[event.key]?.forEach { it.block() }
+                } ?: Log.w(TAG, "Triggering Action(${event.key}) for session($key) failed")
             }
             else -> {
                 throw IllegalArgumentException(
@@ -207,7 +212,7 @@
         sendEvent(RunLambda(key))
     }
 
-    // Action types that this session supports.
+    // Event types that this session supports.
     @VisibleForTesting
     internal object UpdateGlanceState
     @VisibleForTesting
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutSelection.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutSelection.kt
index f87bb8e..933c70a 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutSelection.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/LayoutSelection.kt
@@ -17,6 +17,7 @@
 
 import android.content.Context
 import android.os.Build
+import android.util.Log
 import android.view.View
 import android.view.ViewGroup
 import android.widget.RemoteViews
@@ -370,10 +371,18 @@
     horizontalAlignment: Alignment.Horizontal?,
     verticalAlignment: Alignment.Vertical?,
 ): InsertedViewInfo {
+    if (numChildren > 10) {
+        Log.e(
+            GlanceAppWidgetTag,
+            "Truncated $type container from $numChildren to 10 elements",
+            IllegalArgumentException("$type container cannot have more than 10 elements")
+        )
+    }
+    val children = numChildren.coerceAtMost(10)
     val childLayout = selectLayout33(type, modifier)
         ?: generatedContainers[ContainerSelector(
             type,
-            numChildren,
+            children,
             horizontalAlignment,
             verticalAlignment
         )]?.layoutId
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
index de2c0e4..405ae563 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/RemoteViewsTranslator.kt
@@ -434,9 +434,9 @@
 internal fun RemoteViews.setChildren(
     translationContext: TranslationContext,
     parentDef: InsertedViewInfo,
-    children: Iterable<Emittable>
+    children: List<Emittable>
 ) {
-    children.forEachIndexed { index, child ->
+    children.take(10).forEachIndexed { index, child ->
         translateChild(
             translationContext.forChild(parent = parentDef, pos = index),
             child,
diff --git a/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_off.xml b/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_off.xml
new file mode 100644
index 0000000..06c358f
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_off.xml
@@ -0,0 +1,25 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="26dp"
+    android:viewportWidth="40"
+    android:viewportHeight="26"
+>
+  <group android:name="thumb" android:translateX="0">
+    <path
+        android:pathData="M13,22.5C18.7989,22.5 23.5,17.7989 23.5,12 23.5,6.201 18.7989,1.5 13,1.5 7.201,1.5 2.5,6.201 2.5,12 2.5,17.7989 7.201,22.5 13,22.5Z"
+        android:strokeWidth="1.05"
+        android:fillColor="@color/glance_switch_off_ambient_shadow"
+        android:fillType="evenOdd"/>
+    <path
+        android:pathData="m12.9681,24.2511c5.7734,0 10.4537,-5.084 10.4537,-11.3552 0,-6.2713 -4.6803,-11.3552 -10.4537,-11.3552 -5.7734,0 -10.4537,5.0839 -10.4537,11.3552 0,6.2713 4.6803,11.3552 10.4537,11.3552z"
+        android:strokeWidth="1.08951"
+        android:fillColor="@color/glance_switch_off_key_shadow"
+        android:fillType="evenOdd"/>
+    <path
+        android:name="thumb_icon"
+        android:pathData="M13,22C18.5228,22 23,17.5228 23,12 23,6.4771 18.5228,2 13,2 7.4771,2 3,6.4771 3,12c0,5.5228 4.4771,10 10,10z"
+        android:strokeWidth="1"
+        android:fillColor="#fff"
+        android:fillType="evenOdd"/>
+  </group>
+</vector>
diff --git a/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_on.xml b/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_on.xml
new file mode 100644
index 0000000..0e0a21b
--- /dev/null
+++ b/glance/glance-appwidget/src/androidMain/res/drawable-v24/glance_switch_thumb_on.xml
@@ -0,0 +1,26 @@
+<vector xmlns:android="http://schemas.android.com/apk/res/android"
+    android:width="40dp"
+    android:height="26dp"
+    android:viewportWidth="40"
+    android:viewportHeight="26"
+    >
+    <group
+        android:name="thumb"
+        android:translateX="3">
+        <path
+            android:fillColor="@color/glance_switch_on_key_shadow"
+            android:fillType="evenOdd"
+            android:pathData="m23.9373,24.5329c5.7734,0 10.4537,-5.084 10.4537,-11.3552 0,-6.2713 -4.6803,-11.3552 -10.4537,-11.3552 -5.7734,0 -10.4537,5.0839 -10.4537,11.3552 0,6.2713 4.6803,11.3552 10.4537,11.3552z"
+            android:strokeWidth="1.08951" />
+        <path
+            android:fillColor="@color/glance_switch_on_ambient_shadow"
+            android:fillType="evenOdd"
+            android:pathData="M24,22.5C29.7989,22.5 34.5,17.7989 34.5,12 34.5,6.201 29.7989,1.5 24,1.5 18.201,1.5 13.5,6.201 13.5,12c0,5.7989 4.701,10.5 10.5,10.5z"
+            android:strokeWidth="1.05" />
+        <path
+            android:name="thumb_icon"
+            android:fillColor="#fff"
+            android:fillType="evenOdd"
+            android:pathData="M24,22C29.5228,22 34,17.5228 34,12C34,6.4771 29.5228,2 24,2C18.4772,2 14,6.4771 14,12C14,17.5228 18.4772,22 24,22Z" />
+    </group>
+</vector>
diff --git a/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_off.xml b/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_off.xml
index 06c358f..4400089 100644
--- a/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_off.xml
+++ b/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_off.xml
@@ -6,20 +6,9 @@
 >
   <group android:name="thumb" android:translateX="0">
     <path
-        android:pathData="M13,22.5C18.7989,22.5 23.5,17.7989 23.5,12 23.5,6.201 18.7989,1.5 13,1.5 7.201,1.5 2.5,6.201 2.5,12 2.5,17.7989 7.201,22.5 13,22.5Z"
-        android:strokeWidth="1.05"
-        android:fillColor="@color/glance_switch_off_ambient_shadow"
-        android:fillType="evenOdd"/>
-    <path
-        android:pathData="m12.9681,24.2511c5.7734,0 10.4537,-5.084 10.4537,-11.3552 0,-6.2713 -4.6803,-11.3552 -10.4537,-11.3552 -5.7734,0 -10.4537,5.0839 -10.4537,11.3552 0,6.2713 4.6803,11.3552 10.4537,11.3552z"
-        android:strokeWidth="1.08951"
-        android:fillColor="@color/glance_switch_off_key_shadow"
-        android:fillType="evenOdd"/>
-    <path
         android:name="thumb_icon"
         android:pathData="M13,22C18.5228,22 23,17.5228 23,12 23,6.4771 18.5228,2 13,2 7.4771,2 3,6.4771 3,12c0,5.5228 4.4771,10 10,10z"
         android:strokeWidth="1"
-        android:fillColor="#fff"
-        android:fillType="evenOdd"/>
+        android:fillColor="#fff"/>
   </group>
 </vector>
diff --git a/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_on.xml b/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_on.xml
index 0e0a21b..dd64c38 100644
--- a/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_on.xml
+++ b/glance/glance-appwidget/src/androidMain/res/drawable/glance_switch_thumb_on.xml
@@ -8,19 +8,8 @@
         android:name="thumb"
         android:translateX="3">
         <path
-            android:fillColor="@color/glance_switch_on_key_shadow"
-            android:fillType="evenOdd"
-            android:pathData="m23.9373,24.5329c5.7734,0 10.4537,-5.084 10.4537,-11.3552 0,-6.2713 -4.6803,-11.3552 -10.4537,-11.3552 -5.7734,0 -10.4537,5.0839 -10.4537,11.3552 0,6.2713 4.6803,11.3552 10.4537,11.3552z"
-            android:strokeWidth="1.08951" />
-        <path
-            android:fillColor="@color/glance_switch_on_ambient_shadow"
-            android:fillType="evenOdd"
-            android:pathData="M24,22.5C29.7989,22.5 34.5,17.7989 34.5,12 34.5,6.201 29.7989,1.5 24,1.5 18.201,1.5 13.5,6.201 13.5,12c0,5.7989 4.701,10.5 10.5,10.5z"
-            android:strokeWidth="1.05" />
-        <path
             android:name="thumb_icon"
             android:fillColor="#fff"
-            android:fillType="evenOdd"
             android:pathData="M24,22C29.5228,22 34,17.5228 34,12C34,6.4771 29.5228,2 24,2C18.4772,2 14,6.4771 14,12C14,17.5228 18.4772,22 24,22Z" />
     </group>
 </vector>
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 dd6e6fc..5104c54 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
@@ -46,6 +46,7 @@
 import org.junit.Assert.assertThrows
 import org.junit.Before
 import org.junit.Test
+import org.junit.Ignore
 import org.junit.runner.RunWith
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.Shadows
@@ -81,6 +82,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-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
index 66cce3c..a8cec419 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/RemoteViewsTranslatorKtTest.kt
@@ -1000,6 +1000,31 @@
         }
     }
 
+    @Test
+    fun containersHaveAtMostTenChildren() = runTest {
+        val rv = context.runAndTranslate {
+            Box {
+                Box {
+                    repeat(15) { Text("") }
+                }
+                Column {
+                    repeat(15) { Text("") }
+                }
+                Row {
+                    repeat(15) { Text("") }
+                }
+            }
+        }
+
+        val children = assertIs<FrameLayout>(context.applyRemoteViews(rv)).nonGoneChildren.toList()
+        val box = assertIs<FrameLayout>(children[0])
+        assertThat(box.nonGoneChildCount).isEqualTo(10)
+        val column = assertIs<LinearLayout>(children[1])
+        assertThat(column.nonGoneChildCount).isEqualTo(10)
+        val row = assertIs<LinearLayout>(children[2])
+        assertThat(row.nonGoneChildCount).isEqualTo(10)
+    }
+
     private fun expectGlanceLog(type: Int, message: String) {
         ShadowLog.getLogsForTag(GlanceAppWidgetTag).forEach { logItem ->
             if (logItem.type == type && logItem.msg == message)
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxBackportTranslatorTest.kt
similarity index 96%
rename from glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt
rename to glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxBackportTranslatorTest.kt
index e84a322..1923a54 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/CheckBoxBackportTranslatorTest.kt
@@ -47,9 +47,10 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 
+@Config(minSdk = 23, maxSdk = 30)
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
-class CheckBoxTranslatorTest {
+class CheckBoxBackportTranslatorTest {
 
     private lateinit var fakeCoroutineScope: TestScope
     private val context = ApplicationProvider.getApplicationContext<Context>()
@@ -61,7 +62,6 @@
         fakeCoroutineScope = TestScope()
     }
 
-    @Config(sdk = [21, 23])
     @Test
     fun canTranslateCheckBox_resolved_unchecked() = fakeCoroutineScope.runTest {
         val rv = context.runAndTranslate {
@@ -78,7 +78,6 @@
         assertThat(icon).hasColorFilter(Color.Blue)
     }
 
-    @Config(sdk = [21, 23])
     @Test
     fun canTranslateCheckBox_resolved_checked() = fakeCoroutineScope.runTest {
         val rv = context.runAndTranslate {
@@ -95,7 +94,6 @@
         assertThat(icon).hasColorFilter(Color.Red)
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_dayNight_unchecked_day() = fakeCoroutineScope.runTest {
         val rv = lightContext.runAndTranslate {
@@ -115,7 +113,6 @@
         assertThat(icon).hasColorFilter(Color.Yellow)
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_dayNight_unchecked_night() = fakeCoroutineScope.runTest {
         val rv = darkContext.runAndTranslate {
@@ -135,7 +132,6 @@
         assertThat(icon).hasColorFilter(Color.Green)
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_dayNight_checked_day() = fakeCoroutineScope.runTest {
         val rv = lightContext.runAndTranslate {
@@ -155,7 +151,6 @@
         assertThat(icon).hasColorFilter(Color.Red)
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_dayNight_checked_night() = fakeCoroutineScope.runTest {
         val rv = darkContext.runAndTranslate {
@@ -175,7 +170,6 @@
         assertThat(icon).hasColorFilter(Color.Blue)
     }
 
-    @Config(sdk = [21, 23])
     @Test
     fun canTranslateCheckBox_resource_checked() = fakeCoroutineScope.runTest {
         val rv = context.runAndTranslate {
@@ -195,7 +189,6 @@
         assertThat(icon).hasColorFilter(Color.Red)
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_onCheckedChange_null() = fakeCoroutineScope.runTest {
         val rv = context.runAndTranslate {
@@ -210,7 +203,6 @@
         assertThat(checkboxRoot.hasOnClickListeners()).isFalse()
     }
 
-    @Config(sdk = [29])
     @Test
     fun canTranslateCheckBox_onCheckedChange_withAction() = fakeCoroutineScope.runTest {
         val rv = context.runAndTranslate {
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonBackportTranslatorTest.kt
similarity index 99%
rename from glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt
rename to glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonBackportTranslatorTest.kt
index 892b5ed..a0a6683 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/RadioButtonBackportTranslatorTest.kt
@@ -50,10 +50,10 @@
 import org.robolectric.annotation.Config
 import kotlin.test.assertIs
 
-@Config(sdk = [29])
+@Config(minSdk = 23, maxSdk = 30)
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
-class RadioButtonTranslatorTest {
+class RadioButtonBackportTranslatorTest {
 
     private lateinit var fakeCoroutineScope: TestScope
     private val context = ApplicationProvider.getApplicationContext<Context>()
diff --git a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchBackportTranslatorTest.kt
similarity index 99%
rename from glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt
rename to glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchBackportTranslatorTest.kt
index 9dce352..d4ba832 100644
--- a/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchTranslatorTest.kt
+++ b/glance/glance-appwidget/src/test/kotlin/androidx/glance/appwidget/translators/SwitchBackportTranslatorTest.kt
@@ -48,10 +48,10 @@
 import org.robolectric.Shadows.shadowOf
 import org.robolectric.annotation.Config
 
-@Config(sdk = [29])
+@Config(minSdk = 23, maxSdk = 30)
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(RobolectricTestRunner::class)
-class SwitchTranslatorTest {
+class SwitchBackportTranslatorTest {
 
     private lateinit var fakeCoroutineScope: TestScope
     private val context = ApplicationProvider.getApplicationContext<Context>()
diff --git a/glance/glance-wear-tiles/build.gradle b/glance/glance-wear-tiles/build.gradle
index f1ac6fa..a4b6f3b 100644
--- a/glance/glance-wear-tiles/build.gradle
+++ b/glance/glance-wear-tiles/build.gradle
@@ -37,9 +37,9 @@
 
     implementation(libs.kotlinStdlib)
     implementation(libs.kotlinCoroutinesGuava)
-    implementation "androidx.lifecycle:lifecycle-runtime:2.3.1"
-    implementation "androidx.lifecycle:lifecycle-runtime-ktx:2.3.1"
-    implementation "androidx.lifecycle:lifecycle-service:2.3.1"
+    implementation(projectOrArtifact(":lifecycle:lifecycle-runtime"))
+    implementation(projectOrArtifact(":lifecycle:lifecycle-runtime-ktx"))
+    implementation(projectOrArtifact(":lifecycle:lifecycle-service"))
 
     testImplementation(libs.testCore)
     testImplementation(libs.testRules)
diff --git a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/GlanceTileService.kt b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/GlanceTileService.kt
index bc9c2a8..1b0a310 100644
--- a/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/GlanceTileService.kt
+++ b/glance/glance-wear-tiles/src/androidMain/kotlin/androidx/glance/wear/tiles/GlanceTileService.kt
@@ -29,7 +29,6 @@
 import androidx.glance.state.GlanceState
 import androidx.glance.state.GlanceStateDefinition
 import androidx.glance.wear.tiles.action.RunCallbackAction
-import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.LifecycleRegistry
 import androidx.lifecycle.ServiceLifecycleDispatcher
@@ -78,9 +77,8 @@
 ) : TileService() {
     private val lifecycleOwner = object : LifecycleOwner {
         val localLifecycle = LifecycleRegistry(this)
-        override fun getLifecycle(): Lifecycle = localLifecycle
+        override val lifecycle: LifecycleRegistry = localLifecycle
     }
-
     private val lifecycleDispatcher = ServiceLifecycleDispatcher(lifecycleOwner)
     private val coroutineScope =
         CoroutineScope(
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceComposable.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceComposable.kt
index ea07394..b3b877c 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceComposable.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/GlanceComposable.kt
@@ -16,7 +16,7 @@
 package androidx.glance
 import androidx.compose.runtime.ComposableTargetMarker
 /**
- * An annotation that can be used to mark an composable function as being expected to be use in a
+ * An annotation that can be used to mark a composable function as being expected to be use in a
  * composable function that is also marked or inferred to be marked as a [GlanceComposable].
  *
  * Using this annotation explicitly is rarely necessary as the Compose compiler plugin will infer
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Box.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Box.kt
index 66a85b1..22328de 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Box.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Box.kt
@@ -51,6 +51,9 @@
  * subject to the [contentAlignment]. When the [content] has more than one layout child, all of
  * the children will be stacked on top of each other in the composition order.
  *
+ * Note for App Widgets: [Box] supports up to 10 child elements. Any additional elements will be
+ * truncated from the output.
+ *
  * @param modifier The modifier to be applied to the layout.
  * @param contentAlignment The alignment of children within the [Box].
  * @param content The content inside the [Box].
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Column.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Column.kt
index ba2441a..17fa31d 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Column.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Column.kt
@@ -69,6 +69,9 @@
  * been provided. When children are smaller than the size of the [Column], they will be placed
  * within the available space subject to [horizontalAlignment] and [verticalAlignment].
  *
+ * Note for App Widgets: [Column] supports up to 10 child elements. Any additional elements will be
+ * truncated from the output.
+ *
  * @param modifier The modifier to be applied to the layout.
  * @param verticalAlignment The vertical alignment to apply to the set of children, when they do not
  *   consume the full height of the [Column] (i.e. whether to push the children towards the top,
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Row.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Row.kt
index 0486cec..0259c9a9 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Row.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/layout/Row.kt
@@ -70,6 +70,9 @@
  * been provided. When children are smaller than the size of the [Row], they will be placed
  * within the available space subject to [verticalAlignment] and [horizontalAlignment].
  *
+ * Note for App Widgets: [Row] supports up to 10 child elements. Any additional elements will be
+ * truncated from the output.
+ *
  * @param modifier The modifier to be applied to the layout.
  * @param horizontalAlignment The horizontal alignment to apply to the set of children, when they do
  *   not consume the full width of the [Row] (i.e. whether to push the children towards the start,
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/InteractiveFrameClock.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/InteractiveFrameClock.kt
index 795030c..806d974 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/InteractiveFrameClock.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/InteractiveFrameClock.kt
@@ -26,6 +26,7 @@
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlinx.coroutines.withTimeoutOrNull
+import kotlinx.coroutines.yield
 
 /**
  * A frame clock implementation that supports interactive mode.
@@ -100,10 +101,21 @@
             period = now - lastFrame
             minPeriod = NANOSECONDS_PER_SECOND / currentHz
         }
-        if (period >= minPeriod) {
-            sendFrame(now)
-        } else {
-            scope.launch {
+        scope.launch {
+            if (period >= minPeriod) {
+                // Our SessionWorker updates the Session whenever Recomposer.currentState is Idle.
+                // When a new frame is awaiting, it usually means that the currentState is
+                // PendingWork. Once the frame is run, the currentState will return to Idle.
+                // Sometimes, the currentState can transition from Idle to PendingWork and back to
+                // Idle without suspending, which means that the SessionWorker cannot collect the
+                // intermediate PendingWork state. Because currentState is a StateFlow,
+                // SessionWorker will not be notified of the second Idle because it is the same
+                // as the state that was collected last.
+                // Yielding here gives the SessionWorker an opportunity to collect the PendingWork
+                // state and the following Idle state as distinct states.
+                yield()
+                sendFrame(now)
+            } else {
                 delay((minPeriod - period) / NANOSECONDS_PER_MILLISECOND)
                 sendFrame(nanoTime())
             }
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
index cedec4c..28c7ad1 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/Session.kt
@@ -51,8 +51,12 @@
      * [provideGlance].
      *
      * This will also be called for the results of future recompositions.
+     * @return true if the tree has been processed and the session is ready to handle events.
      */
-    abstract suspend fun processEmittableTree(context: Context, root: EmittableWithChildren)
+    abstract suspend fun processEmittableTree(
+        context: Context,
+        root: EmittableWithChildren
+    ): Boolean
 
     /**
      * Process an event that was sent to this session.
@@ -79,7 +83,8 @@
                 block(event)
                 processEvent(context, event)
             }
-        } catch (_: ClosedReceiveChannelException) {}
+        } catch (_: ClosedReceiveChannelException) {
+        }
     }
 
     suspend fun close() {
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 586e6fa..5a5728e 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/session/SessionWorker.kt
@@ -82,11 +82,11 @@
                         // Also update the session if we have not sent an initial tree yet.
                         if (recomposer.changeCount > lastRecomposeCount || !uiReady.value) {
                             if (DEBUG) Log.d(TAG, "UI tree updated (${session.key})")
-                            session.processEmittableTree(
+                            val processed = session.processEmittableTree(
                                 applicationContext,
                                 root.copy() as EmittableWithChildren
                             )
-                            if (!uiReady.value) uiReady.emit(true)
+                            if (!uiReady.value && processed) uiReady.emit(true)
                         }
                         lastRecomposeCount = recomposer.changeCount
                     }
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/InteractiveFrameClockTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/InteractiveFrameClockTest.kt
index 2c55ac0..640af9f 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/InteractiveFrameClockTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/InteractiveFrameClockTest.kt
@@ -23,6 +23,7 @@
 import kotlinx.coroutines.test.advanceTimeBy
 import kotlinx.coroutines.test.advanceUntilIdle
 import kotlinx.coroutines.test.currentTime
+import kotlinx.coroutines.test.runCurrent
 import kotlinx.coroutines.test.runTest
 import kotlinx.coroutines.yield
 import org.junit.Test
@@ -49,7 +50,9 @@
         // awaiter1 will be sent immediately, awaiter2 & awaiter3 will be sent together at least
         // 1/5th of a second later.
         val awaiter1 = async { clock.withFrameNanos { it } }
+        runCurrent()
         val awaiter2 = async { clock.withFrameNanos { it } }
+        runCurrent()
         val awaiter3 = async { clock.withFrameNanos { it } }
         advanceUntilIdle()
         val frame1 = awaiter1.await()
@@ -69,7 +72,9 @@
         // awaiter1 will be sent immediately, awaiter2 & awaiter3 will be sent together at least
         // 1/20th of a second later.
         val awaiter1 = async { clock.withFrameNanos { it } }
+        runCurrent()
         val awaiter2 = async { clock.withFrameNanos { it } }
+        runCurrent()
         val awaiter3 = async { clock.withFrameNanos { it } }
         advanceUntilIdle()
         val frame1 = awaiter1.await()
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
index 9ceb156..09d5058 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionManagerImplTest.kt
@@ -52,7 +52,10 @@
             TODO("Not yet implemented")
         }
 
-        override suspend fun processEmittableTree(context: Context, root: EmittableWithChildren) {
+        override suspend fun processEmittableTree(
+            context: Context,
+            root: EmittableWithChildren
+        ): Boolean {
             TODO("Not yet implemented")
         }
 
diff --git a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
index 24a428a..208c6f1 100644
--- a/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
+++ b/glance/glance/src/test/kotlin/androidx/glance/session/SessionWorkerTest.kt
@@ -202,8 +202,12 @@
         return content
     }
 
-    override suspend fun processEmittableTree(context: Context, root: EmittableWithChildren) {
+    override suspend fun processEmittableTree(
+        context: Context,
+        root: EmittableWithChildren
+    ): Boolean {
         onUiFlow?.emit(root)
+        return true
     }
 
     override suspend fun processEvent(context: Context, event: Any) {
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index 2d02b1b..d0edcbe 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -95,7 +95,7 @@
 checkerframework = { module = "org.checkerframework:checker-qual", version = "2.5.3" }
 checkmark = { module = "net.saff.checkmark:checkmark", version = "0.1.6" }
 constraintLayout = { module = "androidx.constraintlayout:constraintlayout", version = "2.0.1"}
-dackka = { module = "com.google.devsite:dackka", version = "1.1.0" }
+dackka = { module = "com.google.devsite:dackka", version = "1.2.0" }
 dagger = { module = "com.google.dagger:dagger", version.ref = "dagger" }
 daggerCompiler = { module = "com.google.dagger:dagger-compiler", version.ref = "dagger" }
 dexmakerMockito = { module = "com.linkedin.dexmaker:dexmaker-mockito", version.ref = "dexmaker" }
diff --git a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/egl/EGLManagerTest.kt b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/egl/EGLManagerTest.kt
index d2a9139..f7e5a2b 100644
--- a/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/egl/EGLManagerTest.kt
+++ b/graphics/graphics-core/src/androidTest/java/androidx/graphics/opengl/egl/EGLManagerTest.kt
@@ -58,6 +58,7 @@
 import org.junit.Assert.assertNotNull
 import org.junit.Assert.assertTrue
 import org.junit.Assert.fail
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 
@@ -220,6 +221,7 @@
         }
     }
 
+    @Ignore // b/266736718
     @Test
     fun testCreatePBufferSurface() {
         testEGLManager {
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 5a3840e..b0febf5 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -9,12 +9,12 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public androidx.health.connect.client.PermissionController getPermissionController();
     method public suspend Object? insertRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.InsertRecordsResponse>);
     method public default static boolean isApiSupported();
-    method public default static boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public default static boolean isProviderAvailable(android.content.Context context);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String recordId, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecords(androidx.health.connect.client.request.ReadRecordsRequest<T> request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordsResponse<T>>);
@@ -24,10 +24,10 @@
   }
 
   public static final class HealthConnectClient.Companion {
-    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public boolean isApiSupported();
-    method public boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public boolean isProviderAvailable(android.content.Context context);
   }
 
diff --git a/health/connect/connect-client/api/public_plus_experimental_current.txt b/health/connect/connect-client/api/public_plus_experimental_current.txt
index 5a3840e..b0febf5 100644
--- a/health/connect/connect-client/api/public_plus_experimental_current.txt
+++ b/health/connect/connect-client/api/public_plus_experimental_current.txt
@@ -9,12 +9,12 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public androidx.health.connect.client.PermissionController getPermissionController();
     method public suspend Object? insertRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.InsertRecordsResponse>);
     method public default static boolean isApiSupported();
-    method public default static boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public default static boolean isProviderAvailable(android.content.Context context);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String recordId, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecords(androidx.health.connect.client.request.ReadRecordsRequest<T> request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordsResponse<T>>);
@@ -24,10 +24,10 @@
   }
 
   public static final class HealthConnectClient.Companion {
-    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public boolean isApiSupported();
-    method public boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public boolean isProviderAvailable(android.content.Context context);
   }
 
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 519dbf0..3df3d9d 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -9,12 +9,12 @@
     method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, androidx.health.connect.client.time.TimeRangeFilter timeRangeFilter, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     method public suspend Object? getChanges(String changesToken, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ChangesResponse>);
     method public suspend Object? getChangesToken(androidx.health.connect.client.request.ChangesTokenRequest request, kotlin.coroutines.Continuation<? super java.lang.String>);
-    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public default static androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public androidx.health.connect.client.PermissionController getPermissionController();
     method public suspend Object? insertRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.InsertRecordsResponse>);
     method public default static boolean isApiSupported();
-    method public default static boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public default static boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public default static boolean isProviderAvailable(android.content.Context context);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String recordId, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
     method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecords(androidx.health.connect.client.request.ReadRecordsRequest<T> request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordsResponse<T>>);
@@ -24,10 +24,10 @@
   }
 
   public static final class HealthConnectClient.Companion {
-    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context, optional String providerPackageName);
     method public androidx.health.connect.client.HealthConnectClient getOrCreate(android.content.Context context);
     method public boolean isApiSupported();
-    method public boolean isProviderAvailable(android.content.Context context, optional java.util.List<java.lang.String> providerPackageNames);
+    method public boolean isProviderAvailable(android.content.Context context, optional String providerPackageName);
     method public boolean isProviderAvailable(android.content.Context context);
   }
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
index 54e26a1..09ad6fa 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/HealthConnectClient.kt
@@ -359,6 +359,9 @@
         @RestrictTo(RestrictTo.Scope.LIBRARY)
         internal const val DEFAULT_PROVIDER_PACKAGE_NAME = "com.google.android.apps.healthdata"
 
+        @RestrictTo(RestrictTo.Scope.LIBRARY)
+        internal const val DEFAULT_PROVIDER_MIN_VERSION_CODE = 35000
+
         @RestrictTo(RestrictTo.Scope.LIBRARY) // To be released after testing
         const val HEALTH_CONNECT_SETTING_INTENT_ACTION =
             "androidx.health.ACTION_HEALTH_CONNECT_SETTINGS"
@@ -382,27 +385,29 @@
          *
          * @sample androidx.health.connect.client.samples.AvailabilityCheckSamples
          *
-         * @param providerPackageNames optional package provider to choose implementation from
+         * @param context the context
+         * @param providerPackageName optional package provider to choose for backend implementation
          * @return whether the api is available
          */
         @JvmOverloads
         @JvmStatic
         public fun isProviderAvailable(
             context: Context,
-            providerPackageNames: List<String> = listOf(DEFAULT_PROVIDER_PACKAGE_NAME),
+            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
         ): Boolean {
             if (!isApiSupported()) {
                 return false
             }
-            return providerPackageNames.any { isPackageInstalled(context.packageManager, it) }
+            return isPackageInstalled(context.packageManager, providerPackageName)
         }
 
         /**
          * Retrieves an IPC-backed [HealthConnectClient] instance binding to an available
          * implementation.
          *
-         * @param providerPackageNames optional alternative package provider to choose
-         * implementation from
+         * @param context the context
+         * @param providerPackageName optional alternative package provider to choose for backend
+         * implementation
          * @return instance of [HealthConnectClient] ready for issuing requests
          * @throws UnsupportedOperationException if service not available due to SDK version too low
          * @throws IllegalStateException if service not available due to not installed
@@ -413,19 +418,16 @@
         @JvmStatic
         public fun getOrCreate(
             context: Context,
-            providerPackageNames: List<String> = listOf(DEFAULT_PROVIDER_PACKAGE_NAME),
+            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME,
         ): HealthConnectClient {
             if (!isApiSupported()) {
                 throw UnsupportedOperationException("SDK version too low")
             }
-            if (!isProviderAvailable(context, providerPackageNames)) {
+            if (!isProviderAvailable(context, providerPackageName)) {
                 throw IllegalStateException("Service not available")
             }
-            val enabledPackage =
-                providerPackageNames.first { isPackageInstalled(context.packageManager, it) }
             return HealthConnectClientImpl(
-                enabledPackage,
-                HealthDataService.getClient(context, enabledPackage)
+                HealthDataService.getClient(context, providerPackageName)
             )
         }
 
@@ -444,7 +446,9 @@
                     return false
                 }
             return packageInfo.applicationInfo.enabled &&
-                PackageInfoCompat.getLongVersionCode(packageInfo) > 35000 &&
+                (packageName != DEFAULT_PROVIDER_PACKAGE_NAME ||
+                    PackageInfoCompat.getLongVersionCode(packageInfo) >=
+                    DEFAULT_PROVIDER_MIN_VERSION_CODE) &&
                 hasBindableService(packageManager, packageName)
         }
 
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
index 4994ea5..6006964 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/HealthConnectClientImpl.kt
@@ -64,7 +64,6 @@
  */
 class HealthConnectClientImpl
 internal constructor(
-    private val providerPackageName: String,
     private val delegate: HealthDataAsyncClient,
     private val allPermissions: List<String> =
         HealthPermission.RECORD_TYPE_TO_PERMISSION.flatMap {
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
index 828d890..1ea349b 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/HealthConnectClientTest.kt
@@ -51,10 +51,10 @@
         val packageManager = context.packageManager
         Shadows.shadowOf(packageManager).removePackage(PROVIDER_PACKAGE_NAME)
         assertThat(HealthConnectClient.isApiSupported()).isTrue()
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
+        assertThat(HealthConnectClient.isProviderAvailable(context, PROVIDER_PACKAGE_NAME))
             .isFalse()
         assertThrows(IllegalStateException::class.java) {
-            HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+            HealthConnectClient.getOrCreate(context, PROVIDER_PACKAGE_NAME)
         }
     }
 
@@ -62,10 +62,10 @@
     @Config(sdk = [Build.VERSION_CODES.P])
     fun backingImplementation_notEnabled_unavailable() {
         installPackage(context, PROVIDER_PACKAGE_NAME, versionCode = 35001, enabled = false)
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
+        assertThat(HealthConnectClient.isProviderAvailable(context, PROVIDER_PACKAGE_NAME))
             .isFalse()
         assertThrows(IllegalStateException::class.java) {
-            HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+            HealthConnectClient.getOrCreate(context, PROVIDER_PACKAGE_NAME)
         }
     }
 
@@ -73,42 +73,51 @@
     @Config(sdk = [Build.VERSION_CODES.P])
     fun backingImplementation_enabledNoService_unavailable() {
         installPackage(context, PROVIDER_PACKAGE_NAME, versionCode = 35001, enabled = true)
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
+        assertThat(HealthConnectClient.isProviderAvailable(context, PROVIDER_PACKAGE_NAME))
             .isFalse()
         assertThrows(IllegalStateException::class.java) {
-            HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+            HealthConnectClient.getOrCreate(context, PROVIDER_PACKAGE_NAME)
         }
     }
 
     @Test
     @Config(sdk = [Build.VERSION_CODES.P])
     fun backingImplementation_enabledUnsupportedVersion_unavailable() {
-        installPackage(context, PROVIDER_PACKAGE_NAME, versionCode = 35000, enabled = true)
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
-            .isFalse()
+        installPackage(
+            context,
+            HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+            versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE - 1,
+            enabled = true)
+        installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+
+        assertThat(HealthConnectClient.isProviderAvailable(context)).isFalse()
         assertThrows(IllegalStateException::class.java) {
-            HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+            HealthConnectClient.getOrCreate(context)
         }
     }
 
     @Test
     @Config(sdk = [Build.VERSION_CODES.P])
     fun backingImplementation_enabledSupportedVersion_isAvailable() {
-        installPackage(context, PROVIDER_PACKAGE_NAME, versionCode = 35001, enabled = true)
-        installService(context, PROVIDER_PACKAGE_NAME)
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
-            .isTrue()
-        HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+        installPackage(
+            context,
+            HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME,
+            versionCode = HealthConnectClient.DEFAULT_PROVIDER_MIN_VERSION_CODE,
+            enabled = true)
+        installService(context, HealthConnectClient.DEFAULT_PROVIDER_PACKAGE_NAME)
+
+        assertThat(HealthConnectClient.isProviderAvailable(context)).isTrue()
+        HealthConnectClient.getOrCreate(context)
     }
 
     @Test
     @Config(sdk = [Build.VERSION_CODES.O_MR1])
     fun sdkVersionTooOld_unavailable() {
         assertThat(HealthConnectClient.isApiSupported()).isFalse()
-        assertThat(HealthConnectClient.isProviderAvailable(context, listOf(PROVIDER_PACKAGE_NAME)))
+        assertThat(HealthConnectClient.isProviderAvailable(context, PROVIDER_PACKAGE_NAME))
             .isFalse()
         assertThrows(UnsupportedOperationException::class.java) {
-            HealthConnectClient.getOrCreate(context, listOf(PROVIDER_PACKAGE_NAME))
+            HealthConnectClient.getOrCreate(context, PROVIDER_PACKAGE_NAME)
         }
     }
 
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
index 61f0915..2fc02d2 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/HealthConnectClientImplTest.kt
@@ -143,7 +143,6 @@
 
         healthConnectClient =
             HealthConnectClientImpl(
-                PROVIDER_PACKAGE_NAME,
                 ServiceBackedHealthDataClient(
                     ApplicationProvider.getApplicationContext(),
                     clientConfig,
diff --git a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
index 470ddf4..77a5b38 100644
--- a/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
+++ b/leanback/leanback/src/androidTest/java/androidx/leanback/widget/GridWidgetTest.java
@@ -65,6 +65,7 @@
 import androidx.testutils.AnimationTest;
 
 import org.junit.After;
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TestName;
@@ -931,6 +932,7 @@
         assertEquals(29, mGridView.getSelectedPosition());
     }
 
+    @Ignore // b/266757643
     @Test
     public void testThreeColumnVerticalBasic() throws Throwable {
         Intent intent = new Intent();
@@ -2343,6 +2345,7 @@
         assertEquals(mGridView.getWidth() / 2, (view.getLeft() + view.getRight()) / 2);
     }
 
+    @Ignore // b/266757643
     @Test
     public void testItemAddRemoveHorizontal() throws Throwable {
 
@@ -2926,7 +2929,7 @@
         testRemoveVisibleItemsInSmoothScrollingBackward(/*focusOnGridView=*/ false);
     }
 
-    @FlakyTest(bugId = 186848347)
+    @Ignore // b/266757643
     @Test
     public void testPendingSmoothScrollAndRemove() throws Throwable {
         Intent intent = new Intent();
diff --git a/libraryversions.toml b/libraryversions.toml
index c22b486..49c571e 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -98,7 +98,7 @@
 PRIVACYSANDBOX_SDKRUNTIME = "1.0.0-alpha01"
 PRIVACYSANDBOX_TOOLS = "1.0.0-alpha03"
 PRIVACYSANDBOX_UI = "1.0.0-alpha01"
-PROFILEINSTALLER = "1.3.0-alpha04"
+PROFILEINSTALLER = "1.3.0-beta01"
 RECOMMENDATION = "1.1.0-alpha01"
 RECYCLERVIEW = "1.4.0-alpha01"
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
@@ -168,7 +168,7 @@
 BIOMETRIC = { group = "androidx.biometric", atomicGroupVersion = "versions.BIOMETRIC" }
 BLUETOOTH = { group = "androidx.bluetooth", atomicGroupVersion = "versions.BLUETOOTH" }
 BROWSER = { group = "androidx.browser", atomicGroupVersion = "versions.BROWSER" }
-BUILDSRC_TESTS = { group = "androidx.buildSrc-tests", atomicGroupVersion = "versions.BUILDSRC_TESTS", overrideInclude = [ ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-dep", ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-main",] }
+BUILDSRC_TESTS = { group = "androidx.buildSrc-tests", atomicGroupVersion = "versions.BUILDSRC_TESTS", overrideInclude = [ ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-dep", ":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-main"] }
 CAMERA = { group = "androidx.camera", atomicGroupVersion = "versions.CAMERA" }
 CARDVIEW = { group = "androidx.cardview", atomicGroupVersion = "versions.CARDVIEW" }
 CAR_APP = { group = "androidx.car.app", atomicGroupVersion = "versions.CAR_APP" }
@@ -211,7 +211,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-common/api/current.txt b/lifecycle/lifecycle-common/api/current.txt
index a339f50..c802eec 100644
--- a/lifecycle/lifecycle-common/api/current.txt
+++ b/lifecycle/lifecycle-common/api/current.txt
@@ -64,6 +64,7 @@
 
   public interface LifecycleOwner {
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public abstract androidx.lifecycle.Lifecycle lifecycle;
   }
 
   @Deprecated @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface OnLifecycleEvent {
diff --git a/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
index a339f50..c802eec 100644
--- a/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-common/api/public_plus_experimental_current.txt
@@ -64,6 +64,7 @@
 
   public interface LifecycleOwner {
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public abstract androidx.lifecycle.Lifecycle lifecycle;
   }
 
   @Deprecated @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.RUNTIME) @java.lang.annotation.Target(java.lang.annotation.ElementType.METHOD) public @interface OnLifecycleEvent {
diff --git a/lifecycle/lifecycle-common/api/restricted_current.txt b/lifecycle/lifecycle-common/api/restricted_current.txt
index 04b9c90..2c8958d 100644
--- a/lifecycle/lifecycle-common/api/restricted_current.txt
+++ b/lifecycle/lifecycle-common/api/restricted_current.txt
@@ -71,6 +71,7 @@
 
   public interface LifecycleOwner {
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public abstract androidx.lifecycle.Lifecycle lifecycle;
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class Lifecycling {
diff --git a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.java b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.kt
similarity index 86%
rename from lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.java
rename to lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.kt
index 02cc1eb..cdc18d4 100644
--- a/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.java
+++ b/lifecycle/lifecycle-common/src/main/java/androidx/lifecycle/LifecycleOwner.kt
@@ -13,10 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-package androidx.lifecycle;
-
-import androidx.annotation.NonNull;
+package androidx.lifecycle
 
 /**
  * A class that has an Android lifecycle. These events can be used by custom components to
@@ -25,13 +22,11 @@
  * @see Lifecycle
  * @see ViewTreeLifecycleOwner
  */
-@SuppressWarnings({"WeakerAccess", "unused"})
 public interface LifecycleOwner {
     /**
      * Returns the Lifecycle of the provider.
      *
      * @return The lifecycle of the provider.
      */
-    @NonNull
-    Lifecycle getLifecycle();
-}
+    public val lifecycle: Lifecycle
+}
\ No newline at end of file
diff --git a/lifecycle/lifecycle-process/api/current.txt b/lifecycle/lifecycle-process/api/current.txt
index 35d5ac4..891c9c6 100644
--- a/lifecycle/lifecycle-process/api/current.txt
+++ b/lifecycle/lifecycle-process/api/current.txt
@@ -10,6 +10,7 @@
   public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public androidx.lifecycle.Lifecycle lifecycle;
     field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
   }
 
diff --git a/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
index 35d5ac4..891c9c6 100644
--- a/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-process/api/public_plus_experimental_current.txt
@@ -10,6 +10,7 @@
   public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public androidx.lifecycle.Lifecycle lifecycle;
     field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
   }
 
diff --git a/lifecycle/lifecycle-process/api/restricted_current.txt b/lifecycle/lifecycle-process/api/restricted_current.txt
index 35d5ac4..891c9c6 100644
--- a/lifecycle/lifecycle-process/api/restricted_current.txt
+++ b/lifecycle/lifecycle-process/api/restricted_current.txt
@@ -10,6 +10,7 @@
   public final class ProcessLifecycleOwner implements androidx.lifecycle.LifecycleOwner {
     method public static androidx.lifecycle.LifecycleOwner get();
     method public androidx.lifecycle.Lifecycle getLifecycle();
+    property public androidx.lifecycle.Lifecycle lifecycle;
     field public static final androidx.lifecycle.ProcessLifecycleOwner.Companion Companion;
   }
 
diff --git a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
index 9bd8d89..f29e9e4 100644
--- a/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
+++ b/lifecycle/lifecycle-process/src/main/java/androidx/lifecycle/ProcessLifecycleOwner.kt
@@ -185,9 +185,8 @@
         })
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return registry
-    }
+    override val lifecycle: Lifecycle
+        get() = registry
 
     @RequiresApi(29)
     internal object Api29Impl {
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
index bbf9774..10d9fd9 100644
--- a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/FakeLifecycleOwner.kt
@@ -29,7 +29,8 @@
         }
     }
 
-    override fun getLifecycle(): Lifecycle = registry
+    override val lifecycle: Lifecycle
+        get() = registry
 
     fun setState(state: Lifecycle.State) {
         registry.currentState = state
diff --git a/camera/camera-viewfinder/api/res-1.1.0-beta03.txt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
similarity index 100%
rename from camera/camera-viewfinder/api/res-1.1.0-beta03.txt
rename to lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/ViewTreeLifecycleOwnerTest.kt
diff --git a/lifecycle/lifecycle-runtime-testing/api/current.txt b/lifecycle/lifecycle-runtime-testing/api/current.txt
index dc2274c..47a819e 100644
--- a/lifecycle/lifecycle-runtime-testing/api/current.txt
+++ b/lifecycle/lifecycle-runtime-testing/api/current.txt
@@ -11,6 +11,7 @@
     method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
     method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
     property public final androidx.lifecycle.Lifecycle.State currentState;
+    property public androidx.lifecycle.LifecycleRegistry lifecycle;
     property public final int observerCount;
   }
 
diff --git a/lifecycle/lifecycle-runtime-testing/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-runtime-testing/api/public_plus_experimental_current.txt
index dc2274c..47a819e 100644
--- a/lifecycle/lifecycle-runtime-testing/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-runtime-testing/api/public_plus_experimental_current.txt
@@ -11,6 +11,7 @@
     method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
     method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
     property public final androidx.lifecycle.Lifecycle.State currentState;
+    property public androidx.lifecycle.LifecycleRegistry lifecycle;
     property public final int observerCount;
   }
 
diff --git a/lifecycle/lifecycle-runtime-testing/api/restricted_current.txt b/lifecycle/lifecycle-runtime-testing/api/restricted_current.txt
index dc2274c..47a819e 100644
--- a/lifecycle/lifecycle-runtime-testing/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-testing/api/restricted_current.txt
@@ -11,6 +11,7 @@
     method public void handleLifecycleEvent(androidx.lifecycle.Lifecycle.Event event);
     method public void setCurrentState(androidx.lifecycle.Lifecycle.State);
     property public final androidx.lifecycle.Lifecycle.State currentState;
+    property public androidx.lifecycle.LifecycleRegistry lifecycle;
     property public final int observerCount;
   }
 
diff --git a/lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt b/lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt
index ddb193b..7e0f455 100644
--- a/lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt
+++ b/lifecycle/lifecycle-runtime-testing/src/main/java/androidx/lifecycle/testing/TestLifecycleOwner.kt
@@ -45,7 +45,9 @@
     private val lifecycleRegistry = LifecycleRegistry.createUnsafe(this).apply {
         currentState = initialState
     }
-    override fun getLifecycle(): LifecycleRegistry = lifecycleRegistry
+
+    override val lifecycle: LifecycleRegistry
+        get() = lifecycleRegistry
 
     /**
      * Update the [currentState] by moving it to the state directly after the given [event].
diff --git a/lifecycle/lifecycle-service/api/current.txt b/lifecycle/lifecycle-service/api/current.txt
index a27283e..bebcd93 100644
--- a/lifecycle/lifecycle-service/api/current.txt
+++ b/lifecycle/lifecycle-service/api/current.txt
@@ -5,6 +5,7 @@
     ctor public LifecycleService();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method @CallSuper public android.os.IBinder? onBind(android.content.Intent intent);
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
   public class ServiceLifecycleDispatcher {
diff --git a/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
index a27283e..bebcd93 100644
--- a/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-service/api/public_plus_experimental_current.txt
@@ -5,6 +5,7 @@
     ctor public LifecycleService();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method @CallSuper public android.os.IBinder? onBind(android.content.Intent intent);
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
   public class ServiceLifecycleDispatcher {
diff --git a/lifecycle/lifecycle-service/api/restricted_current.txt b/lifecycle/lifecycle-service/api/restricted_current.txt
index a27283e..bebcd93 100644
--- a/lifecycle/lifecycle-service/api/restricted_current.txt
+++ b/lifecycle/lifecycle-service/api/restricted_current.txt
@@ -5,6 +5,7 @@
     ctor public LifecycleService();
     method public androidx.lifecycle.Lifecycle getLifecycle();
     method @CallSuper public android.os.IBinder? onBind(android.content.Intent intent);
+    property public androidx.lifecycle.Lifecycle lifecycle;
   }
 
   public class ServiceLifecycleDispatcher {
diff --git a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
index 7754cc7..17006dc 100644
--- a/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
+++ b/lifecycle/lifecycle-service/src/main/java/androidx/lifecycle/LifecycleService.kt
@@ -62,7 +62,6 @@
         super.onDestroy()
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return dispatcher.lifecycle
-    }
+    override val lifecycle: Lifecycle
+        get() = dispatcher.lifecycle
 }
\ No newline at end of file
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
index efdb532..a0c00510 100644
--- a/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
+++ b/lifecycle/lifecycle-viewmodel-savedstate/src/androidTest/java/androidx/lifecycle/viewmodel/savedstate/TestComponent.kt
@@ -45,7 +45,8 @@
         savedStateController.performRestore(bundle)
     }
 
-    override fun getLifecycle(): Lifecycle = lifecycleRegistry
+    override val lifecycle: Lifecycle
+        get() = lifecycleRegistry
     override val savedStateRegistry: SavedStateRegistry
         get() = savedStateController.savedStateRegistry
 
diff --git a/media/media/api/api_lint.ignore b/media/media/api/api_lint.ignore
index d098ab4..2a61822 100644
--- a/media/media/api/api_lint.ignore
+++ b/media/media/api/api_lint.ignore
@@ -73,6 +73,24 @@
     Intent action constant name must be ACTION_FOO: BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
 IntentName: androidx.media.utils.MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST:
     Intent action constant name must be ACTION_FOO: DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM
+IntentName: androidx.media.utils.MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM:
+    Intent action constant name must be ACTION_FOO: EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM
 
 
 InterfaceConstant: androidx.media.MediaBrowserServiceCompat#SERVICE_INTERFACE:
diff --git a/media/media/api/current.txt b/media/media/api/current.txt
index 0f1bb9d..3e65d18 100644
--- a/media/media/api/current.txt
+++ b/media/media/api/current.txt
@@ -712,12 +712,12 @@
 package androidx.media.utils {
 
   public final class MediaConstants {
-    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.MediaBrowserCompat.CUSTOM_BROWSER_ACTION_LIMIT";
+    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.utils.MediaBrowserCompat.extras.CUSTOM_BROWSER_ACTION_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS = "android.media.extras.MEDIA_ART_SIZE_HINT_PIXELS";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_SUPPORTED_FLAGS";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT = "androidx.media.BrowserRoot.Extras.APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT";
-    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ROOT_LIST";
+    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ROOT_LIST";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_FAVORITES_MEDIA_ITEM = "androidx.media.BrowserRoot.Extras.FAVORITES_MEDIA_ITEM";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED";
     field public static final String DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE = "androidx.media.MediaItem.Extras.COMPLETION_PERCENTAGE";
@@ -726,7 +726,7 @@
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM = "android.media.browse.CONTENT_STYLE_SINGLE_ITEM_HINT";
-    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ID_LIST";
+    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ID_LIST";
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED = 0; // 0x0
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED = 1; // 0x1
@@ -734,15 +734,15 @@
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM = 3; // 0x3
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM = 1; // 0x1
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_LABEL";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_LABEL";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
     field public static final String METADATA_KEY_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_CONTENT_ID";
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
diff --git a/media/media/api/public_plus_experimental_current.txt b/media/media/api/public_plus_experimental_current.txt
index 0f1bb9d..3e65d18 100644
--- a/media/media/api/public_plus_experimental_current.txt
+++ b/media/media/api/public_plus_experimental_current.txt
@@ -712,12 +712,12 @@
 package androidx.media.utils {
 
   public final class MediaConstants {
-    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.MediaBrowserCompat.CUSTOM_BROWSER_ACTION_LIMIT";
+    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.utils.MediaBrowserCompat.extras.CUSTOM_BROWSER_ACTION_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS = "android.media.extras.MEDIA_ART_SIZE_HINT_PIXELS";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_SUPPORTED_FLAGS";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT = "androidx.media.BrowserRoot.Extras.APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT";
-    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ROOT_LIST";
+    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ROOT_LIST";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_FAVORITES_MEDIA_ITEM = "androidx.media.BrowserRoot.Extras.FAVORITES_MEDIA_ITEM";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED";
     field public static final String DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE = "androidx.media.MediaItem.Extras.COMPLETION_PERCENTAGE";
@@ -726,7 +726,7 @@
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM = "android.media.browse.CONTENT_STYLE_SINGLE_ITEM_HINT";
-    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ID_LIST";
+    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ID_LIST";
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED = 0; // 0x0
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED = 1; // 0x1
@@ -734,15 +734,15 @@
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM = 3; // 0x3
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM = 1; // 0x1
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_LABEL";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_LABEL";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
     field public static final String METADATA_KEY_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_CONTENT_ID";
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
diff --git a/media/media/api/restricted_current.txt b/media/media/api/restricted_current.txt
index fe59f23..35c24b8 100644
--- a/media/media/api/restricted_current.txt
+++ b/media/media/api/restricted_current.txt
@@ -750,12 +750,12 @@
 package androidx.media.utils {
 
   public final class MediaConstants {
-    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.MediaBrowserCompat.CUSTOM_BROWSER_ACTION_LIMIT";
+    field public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT = "androidx.media.utils.MediaBrowserCompat.extras.CUSTOM_BROWSER_ACTION_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_MEDIA_ART_SIZE_PIXELS = "android.media.extras.MEDIA_ART_SIZE_HINT_PIXELS";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_LIMIT = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_LIMIT";
     field public static final String BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS = "androidx.media.MediaBrowserCompat.Extras.KEY_ROOT_CHILDREN_SUPPORTED_FLAGS";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT = "androidx.media.BrowserRoot.Extras.APPLICATION_PREFERENCES_USING_CAR_APP_LIBRARY_INTENT";
-    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ROOT_LIST";
+    field public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ROOT_LIST";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_FAVORITES_MEDIA_ITEM = "androidx.media.BrowserRoot.Extras.FAVORITES_MEDIA_ITEM";
     field public static final String BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED = "android.media.browse.SEARCH_SUPPORTED";
     field public static final String DESCRIPTION_EXTRAS_KEY_COMPLETION_PERCENTAGE = "androidx.media.MediaItem.Extras.COMPLETION_PERCENTAGE";
@@ -764,7 +764,7 @@
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_GROUP_TITLE = "android.media.browse.CONTENT_STYLE_GROUP_TITLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_PLAYABLE = "android.media.browse.CONTENT_STYLE_PLAYABLE_HINT";
     field public static final String DESCRIPTION_EXTRAS_KEY_CONTENT_STYLE_SINGLE_ITEM = "android.media.browse.CONTENT_STYLE_SINGLE_ITEM_HINT";
-    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "android.media.extra.CUSTOM_BROWSER_ACTION_ID_LIST";
+    field public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST = "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ID_LIST";
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_FULLY_PLAYED = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_NOT_PLAYED = 0; // 0x0
     field public static final int DESCRIPTION_EXTRAS_VALUE_COMPLETION_STATUS_PARTIALLY_PLAYED = 1; // 0x1
@@ -772,15 +772,15 @@
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_CATEGORY_LIST_ITEM = 3; // 0x3
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM = 2; // 0x2
     field public static final int DESCRIPTION_EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM = 1; // 0x1
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_LABEL";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
-    field public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_LABEL";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
+    field public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM = "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
     field public static final String METADATA_KEY_CONTENT_ID = "androidx.media.MediaMetadatCompat.METADATA_KEY_CONTENT_ID";
     field public static final String METADATA_KEY_IS_ADVERTISEMENT = "android.media.metadata.ADVERTISEMENT";
     field public static final String METADATA_KEY_IS_EXPLICIT = "android.media.IS_EXPLICIT";
diff --git a/media/media/src/main/java/androidx/media/utils/MediaConstants.java b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
index ebbc9e4..123be21 100644
--- a/media/media/src/main/java/androidx/media/utils/MediaConstants.java
+++ b/media/media/src/main/java/androidx/media/utils/MediaConstants.java
@@ -552,42 +552,42 @@
      * custom action.
      *
      * <p>A custom browser action is defined by an
-     * {@linkplain MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID action ID}, an
-     * {@linkplain MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL action label},
-     * an {@linkplain MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI action icon
+     * {@linkplain MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID action ID}, an
+     * {@linkplain MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL action label},
+     * an {@linkplain MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI action icon
      * URI}, and optionally an
-     * {@linkplain MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS action extras
+     * {@linkplain MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS action extras
      * bundle}.
      *
      * <p>Custom browser action example:
      * <ul>
      *   <li>Action ID: "com.example.audioapp.download"
      *     <ul>
-     *       <li>Key: {@link MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID}
+     *       <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID}
      *     </ul>
      *   </li>
      *     <li>Action label: "Download Song"
      *     <ul>
-     *       <li>Key: {@link MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL}
+     *       <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL}
      *       <li>Localized String label for action
      *     </ul>
      *   </li>
      *     <li>Action Icon URI: "content://com.example.public/download"
      *     <ul>
-     *       <li>Key: {@link MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI}
+     *       <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI}
      *       <li>Tintable vector drawable
      *     </ul>
      *   </li>
      *     <li>Action extras: {bundle}
      *     <ul>
-     *       <li>Key: {@link MediaConstants#EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS}
+     *       <li>Key: {@link MediaConstants#EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS}
      *       <li>Bundle extras
      *     </ul>
      *   </li>
      * </ul>
      */
     public static final String BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST =
-            "android.media.extra.CUSTOM_BROWSER_ACTION_ROOT_LIST";
+            "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ROOT_LIST";
 
     /**
      * {@link Bundle} key used to define a string list of custom browser actions for a
@@ -616,7 +616,7 @@
      * @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
      */
     public static final String DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST =
-            "android.media.extra.CUSTOM_BROWSER_ACTION_ID_LIST";
+            "androidx.media.utils.extras.CUSTOM_BROWSER_ACTION_ID_LIST";
 
     /**
      * {@link Bundle} key used to define the ID for a custom browser action.
@@ -626,8 +626,8 @@
      * @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
      * @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ID =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ID";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ID";
 
     /**
      * {@link Bundle} key used to define the label for a custom browser action. Label is a localized
@@ -638,8 +638,8 @@
      * @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
      * @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_LABEL =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_LABEL";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_LABEL =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_LABEL";
     /**
      * {@link Bundle} key used to define the icon URI for a custom browser action.
      *
@@ -648,8 +648,8 @@
      * @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
      * @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_ICON_URI =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ICON_URI =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_ICON_URI";
     /**
      * {@link Bundle} key used to define an extras bundle for a custom browser action.
      *
@@ -661,14 +661,14 @@
      * @see MediaConstants#BROWSER_SERVICE_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ROOT_LIST
      * @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_EXTRAS =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_EXTRAS =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_EXTRAS";
     /**
      * {@link Bundle} key used to define the total number of actions allowed per item. Passed to
      * {@link MediaBrowserServiceCompat} using
      * {@link MediaBrowserServiceCompat#onGetRoot(String, int, Bundle)} in root hints bundle.
      *
-     * <p>Presence and non-zero value of this key in the root hints indicates that custom browse
+     * <p>Presence of this key and positive value in the root hints indicates that custom browse
      * actions feature is supported. Actions beyond this limit will be truncated.
      *
      * <p>TYPE: int, number of actions each item is limited to.
@@ -678,7 +678,7 @@
      * @see MediaConstants#DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST
      */
     public static final String BROWSER_ROOT_HINTS_KEY_CUSTOM_BROWSER_ACTION_LIMIT =
-            "androidx.media.MediaBrowserCompat.CUSTOM_BROWSER_ACTION_LIMIT";
+            "androidx.media.utils.MediaBrowserCompat.extras.CUSTOM_BROWSER_ACTION_LIMIT";
 
     /**
      * {@link Bundle} key used to define the ID of the {@link MediaBrowserCompat.MediaItem}
@@ -686,7 +686,8 @@
      *
      * <p>A {@link MediaBrowserCompat} that supports custom browser actions can set this key
      * in the parameter extra bundle when using
-     * {@link MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)}.
+     * {@link MediaBrowserCompat#sendCustomAction(String, Bundle,
+     * MediaBrowserCompat.CustomActionCallback)}.
      *
      * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions should override
      * {@link MediaBrowserServiceCompat#onCustomAction(
@@ -701,22 +702,24 @@
      * @see
      * MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_MEDIA_ITEM_ID";
     /**
-     * {@link Bundle} key set in custom browser action extra bundle or
-     * {@link MediaBrowserServiceCompat.Result} to indicate which browse node should be displayed
-     * next.
+     * {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to indicate which
+     * browse node should be displayed next.
      *
      * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key
      * in the {@link MediaBrowserServiceCompat.Result} passed in
-     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
-     *
-     * If this key is present in a {@link MediaBrowserCompat.CustomActionCallback} data
-     * {@link Bundle} the {@link MediaBrowserCompat} will update the current browse root when
-     * {@link MediaBrowserCompat.CustomActionCallback#onProgressUpdate(String, Bundle, Bundle)} or
+     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle,
+     * MediaBrowserServiceCompat.Result)}.
+     * <p>If this key is present in a {@link MediaBrowserCompat.CustomActionCallback} data
+     * {@link Bundle} the {@link MediaBrowserCompat} will update the current browse node when
      * {@link MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by
-     * the {@link MediaBrowserServiceCompat}.
+     * the {@link MediaBrowserServiceCompat}. The new browse node will be fetched by
+     * {@link MediaBrowserCompat#getItem(String, MediaBrowserCompat.ItemCallback)}.
+     * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions must implement
+     * {@link MediaBrowserServiceCompat#onLoadItem(String, MediaBrowserServiceCompat.Result)} to
+     * use this feature.
      *
      * <p>TYPE: string, string {@link MediaBrowserCompat.MediaItem} ID to set as new browse node.
      *
@@ -726,55 +729,51 @@
      * @see
      * MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_BROWSE_NODE";
 
     /**
-     * {@link Bundle} key set in custom browser action extra bundle or
-     * {@link MediaBrowserServiceCompat.Result} to show the playing item.
+     * {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to show the
+     * currently playing item.
      *
      * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key
-     * in the
-     * {@link MediaBrowserServiceCompat.Result} passed in
-     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
-     *
-     * <p>If this key is present in {@link MediaBrowserCompat.CustomActionCallback}
-     * {@link MediaBrowserServiceCompat.Result}, the currently playing item will be shown
-     * when result is handled by {@link MediaBrowserCompat}.
-     *
-     * <P>TYPE: string, string {@link MediaBrowserCompat.MediaItem} ID of item to show, or if no
-     * value is set then show currently playing
-     * {@link MediaBrowserCompat.MediaItem}
-     *
+     * in the {@link MediaBrowserServiceCompat.Result} passed in
+     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle,
+     * MediaBrowserServiceCompat.Result)}.
+     * <p>If this key is present and the value is true in
+     * {@link MediaBrowserCompat.CustomActionCallback}
+     * {@link MediaBrowserServiceCompat.Result}, the currently playing item will be shown when
+     * {@link MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by
+     * the {@link MediaBrowserServiceCompat}.
+     * <p>TYPE: boolean, boolean value of true will show currently playing item.
      * @see
      * MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)
      * @see MediaBrowserCompat.CustomActionCallback
      * @see
      * MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_SHOW_PLAYING_ITEM";
 
     /**
-     * {@link Bundle} key set in custom browser action extra bundle or
-     * {@link MediaBrowserServiceCompat.Result} to refresh a
+     * {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to refresh a
      * {@link MediaBrowserCompat.MediaItem} in the browse tree.
      *
      * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key
      * in the {@link MediaBrowserServiceCompat.Result} passed in
-     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
+     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle,
+     * MediaBrowserServiceCompat.Result)}.
      *
-     * <p>A {@link MediaBrowserCompat} that supports custom browser actions will refresh
-     * the items custom browser action IDs by using
-     * {@link MediaBrowserCompat#getItem(String, MediaBrowserCompat.ItemCallback)}.
-     * A {@link MediaBrowserServiceCompat} that supports custom browser actions  must implement
+     * <p>If this key is present in {@link MediaBrowserCompat.CustomActionCallback}
+     * {@link MediaBrowserServiceCompat.Result}, the item will be refreshed with
+     * {@link MediaBrowserCompat#getItem(String, MediaBrowserCompat.ItemCallback)} when
+     * {@link MediaBrowserCompat.CustomActionCallback#onProgressUpdate(String, Bundle, Bundle)} or
+     * {@link MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by
+     * the {@link MediaBrowserServiceCompat}.
+     * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions must
+     * implement
      * {@link MediaBrowserServiceCompat#onLoadItem(String, MediaBrowserServiceCompat.Result)} in
-     * order to refresh actions in an item.
-     *
-     * The key in the action result bundle will trigger the item refresh
-     * when the {@link MediaBrowserCompat.CustomActionCallback} is called, which is passed
-     * to the {@link MediaBrowserServiceCompat} using
-     * {@link MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)}
+     * order to update the state of the item.
      *
      * <p>TYPE: string, string {@link MediaBrowserCompat.MediaItem} ID to refresh.
      *
@@ -784,22 +783,24 @@
      * @see
      * MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_REFRESH_ITEM";
     /**
-     * {@link Bundle} key set in custom browser action extra bundle or
-     * {@link MediaBrowserServiceCompat.Result} to set a message for user.
+     * {@link Bundle} key set in {@link MediaBrowserServiceCompat.Result} to set a message for
+     * the user.
      *
      * <p>A {@link MediaBrowserServiceCompat} that supports custom browser actions can set this key
      * in the {@link MediaBrowserServiceCompat.Result} passed in
-     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)}.
+     * {@link MediaBrowserServiceCompat#onCustomAction(String, Bundle,
+     * MediaBrowserServiceCompat.Result)}.
      *
-     * The key in the action result bundle will trigger the message
-     * handling when the {@link MediaBrowserCompat.CustomActionCallback} is called, which is passed
-     * to the {@link MediaBrowserServiceCompat} using
-     * {@link MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)}
+     * <p>If this key is present in {@link MediaBrowserCompat.CustomActionCallback}
+     * {@link MediaBrowserServiceCompat.Result}, the message will be shown to the user when
+     * {@link MediaBrowserCompat.CustomActionCallback#onProgressUpdate(String, Bundle, Bundle)} or
+     * {@link MediaBrowserCompat.CustomActionCallback#onResult(String, Bundle, Bundle)} is called by
+     * the {@link MediaBrowserServiceCompat}.
      *
-     * <p>TYPE: string, localized message string to show user.
+     * <p>TYPE: string, localized message string to show the user.
      *
      * @see
      * MediaBrowserCompat#sendCustomAction(String, Bundle, MediaBrowserCompat.CustomActionCallback)
@@ -807,8 +808,8 @@
      * @see
      * MediaBrowserServiceCompat#onCustomAction(String, Bundle, MediaBrowserServiceCompat.Result)
      */
-    public static final String EXTRA_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE =
-            "androidx.media.utils.extra.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
+    public static final String EXTRAS_KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE =
+            "androidx.media.utils.extras.KEY_CUSTOM_BROWSER_ACTION_RESULT_MESSAGE";
 
     /**
      * Bundle key used for the media ID in {@link PlaybackStateCompat playback state} extras. It's
diff --git a/navigation/navigation-common/api/current.txt b/navigation/navigation-common/api/current.txt
index a0905df..feb3c32 100644
--- a/navigation/navigation-common/api/current.txt
+++ b/navigation/navigation-common/api/current.txt
@@ -127,6 +127,7 @@
     property public androidx.lifecycle.ViewModelProvider.Factory defaultViewModelProviderFactory;
     property public final androidx.navigation.NavDestination destination;
     property public final String id;
+    property public androidx.lifecycle.Lifecycle lifecycle;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
     property public androidx.lifecycle.ViewModelStore viewModelStore;
diff --git a/navigation/navigation-common/api/public_plus_experimental_current.txt b/navigation/navigation-common/api/public_plus_experimental_current.txt
index a0905df..feb3c32 100644
--- a/navigation/navigation-common/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-common/api/public_plus_experimental_current.txt
@@ -127,6 +127,7 @@
     property public androidx.lifecycle.ViewModelProvider.Factory defaultViewModelProviderFactory;
     property public final androidx.navigation.NavDestination destination;
     property public final String id;
+    property public androidx.lifecycle.Lifecycle lifecycle;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
     property public androidx.lifecycle.ViewModelStore viewModelStore;
diff --git a/navigation/navigation-common/api/restricted_current.txt b/navigation/navigation-common/api/restricted_current.txt
index a0905df..feb3c32 100644
--- a/navigation/navigation-common/api/restricted_current.txt
+++ b/navigation/navigation-common/api/restricted_current.txt
@@ -127,6 +127,7 @@
     property public androidx.lifecycle.ViewModelProvider.Factory defaultViewModelProviderFactory;
     property public final androidx.navigation.NavDestination destination;
     property public final String id;
+    property public androidx.lifecycle.Lifecycle lifecycle;
     property public final androidx.lifecycle.SavedStateHandle savedStateHandle;
     property public androidx.savedstate.SavedStateRegistry savedStateRegistry;
     property public androidx.lifecycle.ViewModelStore viewModelStore;
diff --git a/navigation/navigation-common/build.gradle b/navigation/navigation-common/build.gradle
index 08168f1..9f6e589 100644
--- a/navigation/navigation-common/build.gradle
+++ b/navigation/navigation-common/build.gradle
@@ -34,6 +34,7 @@
 
 dependencies {
     api("androidx.annotation:annotation:1.1.0")
+    api(project(":lifecycle:lifecycle-common"))
     api(project(":lifecycle:lifecycle-runtime-ktx"))
     api(project(":lifecycle:lifecycle-viewmodel-ktx"))
     api("androidx.savedstate:savedstate-ktx:1.2.0")
diff --git a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
index 953aae9..2803a2e 100644
--- a/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
+++ b/navigation/navigation-common/src/main/java/androidx/navigation/NavBackStackEntry.kt
@@ -107,7 +107,7 @@
         )
     }
 
-    private var lifecycle = LifecycleRegistry(this)
+    private var _lifecycle = LifecycleRegistry(this)
     private val savedStateRegistryController = SavedStateRegistryController.create(this)
     private var savedStateRegistryAttached = false
     private val defaultFactory by lazy {
@@ -154,9 +154,8 @@
      * [androidx.navigation.NavHostController.setLifecycleOwner], the
      * Lifecycle will be capped at [Lifecycle.State.CREATED].
      */
-    public override fun getLifecycle(): Lifecycle {
-        return lifecycle
-    }
+    override val lifecycle: Lifecycle
+        get() = _lifecycle
 
     /** @suppress */
     @get:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
@@ -191,9 +190,9 @@
             savedStateRegistryController.performRestore(savedState)
         }
         if (hostLifecycleState.ordinal < maxLifecycle.ordinal) {
-            lifecycle.currentState = hostLifecycleState
+            _lifecycle.currentState = hostLifecycleState
         } else {
-            lifecycle.currentState = maxLifecycle
+            _lifecycle.currentState = maxLifecycle
         }
     }
 
@@ -251,7 +250,8 @@
     override fun equals(other: Any?): Boolean {
         if (other == null || other !is NavBackStackEntry) return false
         return id == other.id && destination == other.destination &&
-            lifecycle == other.lifecycle && savedStateRegistry == other.savedStateRegistry &&
+            lifecycle == other.lifecycle &&
+            savedStateRegistry == other.savedStateRegistry &&
             (
                 immutableArgs == other.immutableArgs ||
                     immutableArgs?.keySet()
diff --git a/navigation/navigation-compose/build.gradle b/navigation/navigation-compose/build.gradle
index 8275048..4cdcea4 100644
--- a/navigation/navigation-compose/build.gradle
+++ b/navigation/navigation-compose/build.gradle
@@ -28,12 +28,12 @@
 
     implementation(libs.kotlinStdlib)
     implementation("androidx.compose.foundation:foundation-layout:1.0.1")
-    api("androidx.activity:activity-compose:1.6.1")
+    api(project(":activity:activity-compose"))
     api("androidx.compose.animation:animation:1.0.1")
     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(project(":lifecycle:lifecycle-viewmodel-compose"))
+    api("androidx.lifecycle:lifecycle-viewmodel-compose:2.5.1")
     // old version of common-java8 conflicts with newer version, because both have
     // DefaultLifecycleEventObserver.
     // Outside of androidx this is resolved via constraint added to lifecycle-common,
diff --git a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
index a04b504..4003262 100644
--- a/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
+++ b/navigation/navigation-compose/src/androidTest/java/androidx/navigation/compose/NavHostTest.kt
@@ -49,6 +49,7 @@
 import androidx.compose.ui.test.performClick
 import androidx.lifecycle.Lifecycle
 import androidx.lifecycle.LifecycleEventObserver
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.ViewModel
 import androidx.lifecycle.ViewModelProvider
 import androidx.lifecycle.testing.TestLifecycleOwner
@@ -785,13 +786,12 @@
 
     @Test
     fun testNestedNavHostOnBackPressed() {
-        val lifecycleOwner = TestLifecycleOwner()
         var innerLifecycleOwner = TestLifecycleOwner(Lifecycle.State.RESUMED)
         val onBackPressedDispatcher = OnBackPressedDispatcher()
-        val dispatcherOwner = object : OnBackPressedDispatcherOwner {
-            override fun getLifecycle() = lifecycleOwner.lifecycle
-            override fun getOnBackPressedDispatcher() = onBackPressedDispatcher
-        }
+        val dispatcherOwner =
+            object : OnBackPressedDispatcherOwner, LifecycleOwner by TestLifecycleOwner() {
+                override val onBackPressedDispatcher = onBackPressedDispatcher
+            }
         lateinit var navController: NavHostController
         lateinit var innerNavController: NavHostController
 
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
index b48b6de..6f25f9e 100644
--- a/paging/paging-testing/api/current.txt
+++ b/paging/paging-testing/api/current.txt
@@ -7,16 +7,10 @@
 
   public final class SnapshotLoader<Value> {
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? flingTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
-    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
-  }
-
-  public enum SnapshotLoader.ScrollBehavior {
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
+    method public suspend Object? scrollTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/api/public_plus_experimental_current.txt b/paging/paging-testing/api/public_plus_experimental_current.txt
index b48b6de..6f25f9e 100644
--- a/paging/paging-testing/api/public_plus_experimental_current.txt
+++ b/paging/paging-testing/api/public_plus_experimental_current.txt
@@ -7,16 +7,10 @@
 
   public final class SnapshotLoader<Value> {
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? flingTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
-    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
-  }
-
-  public enum SnapshotLoader.ScrollBehavior {
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
+    method public suspend Object? scrollTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
index b48b6de..6f25f9e 100644
--- a/paging/paging-testing/api/restricted_current.txt
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -7,16 +7,10 @@
 
   public final class SnapshotLoader<Value> {
     method public suspend Object? appendScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
+    method public suspend Object? flingTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? prependScrollWhile(kotlin.jvm.functions.Function1<Value,java.lang.Boolean> predicate, kotlin.coroutines.Continuation<kotlin.Unit>);
     method public suspend Object? refresh(kotlin.coroutines.Continuation<kotlin.Unit>);
-    method public suspend Object? scrollTo(int index, optional androidx.paging.testing.SnapshotLoader.ScrollBehavior scrollBehavior, optional kotlin.coroutines.Continuation<kotlin.Unit>);
-  }
-
-  public enum SnapshotLoader.ScrollBehavior {
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior valueOf(String name) throws java.lang.IllegalArgumentException;
-    method public static androidx.paging.testing.SnapshotLoader.ScrollBehavior[] values();
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior ScrollIntoPlaceholders;
-    enum_constant public static final androidx.paging.testing.SnapshotLoader.ScrollBehavior WaitForPlaceholdersToLoad;
+    method public suspend Object? scrollTo(int index, kotlin.coroutines.Continuation<kotlin.Unit>);
   }
 
   public final class StaticListPagingSourceFactoryKt {
diff --git a/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt b/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
index 815d718..fbb8cde 100644
--- a/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
+++ b/paging/paging-testing/src/main/java/androidx/paging/testing/SnapshotLoader.kt
@@ -17,15 +17,16 @@
 package androidx.paging.testing
 
 import androidx.paging.DifferCallback
-import androidx.paging.PagingData
-import androidx.paging.PagingDataDiffer
-import androidx.paging.PagingSource
 import androidx.paging.LoadType.APPEND
 import androidx.paging.LoadType.PREPEND
 import androidx.paging.PagingConfig
+import androidx.paging.PagingData
+import androidx.paging.PagingDataDiffer
+import androidx.paging.PagingSource
 import androidx.paging.testing.LoaderCallback.CallbackType.ON_INSERTED
 import java.util.concurrent.atomic.AtomicInteger
 import java.util.concurrent.atomic.AtomicReference
+import kotlin.math.abs
 import kotlinx.coroutines.flow.MutableStateFlow
 import kotlinx.coroutines.flow.filterNotNull
 import kotlinx.coroutines.flow.first
@@ -123,7 +124,9 @@
     }
 
     /**
-     * Imitates scrolling from current index to the target index.
+     * Imitates scrolling from current index to the target index. It waits for an item to be loaded
+     * in before triggering load on next item. Returns all available data that has been scrolled
+     * through.
      *
      * The scroll direction (prepend or append) is dependent on current index and target index. In
      * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger
@@ -131,14 +134,20 @@
      *
      * When [PagingConfig.enablePlaceholders] is false, the [index] is scoped within currently
      * loaded items. For example, in a list of items(0-20) with currently loaded items(10-15),
-     * index[2] = item(12). However, this function does supports an [index] beyond
-     * currently loaded items. For prepends, this means it supports negative indices for as long
-     * as there are still available data to load from. Note that in the case of prepend, each call
-     * to [scrollTo] will anchor the last prepended item to index[0]. For example, in a list of
-     * items(0-20) with currently loaded items(10-15), index[0] = item(10). The first `scrollTo(-2)`
-     * will scroll to item(8) and update index[0] = item(8). The next `scrollTo(-2)` will scroll
-     * to item(6) with index[0] = item(6). This example does not account for prefetches.
+     * index[0] = item(10), index[4] = item(15).
      *
+     * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders]
+     * is false:
+     * 1. For prepends, it supports negative indices for as long as there are still available
+     * data to load from. For example, take a list of items(0-20), pageSize = 1, with currently
+     * loaded items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and
+     * update index[0] = item(6).
+     * 2. For appends, it supports indices >= loadedDataSize. For example, take a list of
+     * items(0-20), pageSize = 1, with currently loaded items(10-15). With
+     * index[4] = item(15), a `scrollTo(7)` will scroll to item(18) and update
+     * index[7] = item(18).
+     * Note that both examples does not account for prefetches.
+
      * The [index] accounts for separators/headers/footers where each one of those consumes one
      * scrolled index.
      *
@@ -147,15 +156,12 @@
      *
      * @param [index] The target index to scroll to
      *
-     * @param [scrollBehavior] The default scroll behavior is
-     * [ScrollBehavior.WaitForPlaceholdersToLoad]. See [ScrollBehavior] for all scroll types.
+     * @see [flingTo] for faking a scroll that continues scrolling without waiting for items to
+     * be loaded in. Supports jumping.
      */
-    public suspend fun scrollTo(
-        index: Int,
-        scrollBehavior: ScrollBehavior = ScrollBehavior.WaitForPlaceholdersToLoad
-    ): @JvmSuppressWildcards Unit {
+    public suspend fun scrollTo(index: Int): @JvmSuppressWildcards Unit {
         differ.awaitNotLoading()
-        appendOrPrependScrollTo(index, scrollBehavior)
+        appendOrPrependScrollTo(index)
         differ.awaitNotLoading()
     }
 
@@ -168,47 +174,146 @@
      * direction and placeholders. Therefore we try to fulfill the expected amount of scrolling
      * rather than the actual requested index.
      */
-    private suspend fun appendOrPrependScrollTo(
-        index: Int,
-        scrollBehavior: ScrollBehavior,
-    ) {
+    private suspend fun appendOrPrependScrollTo(index: Int) {
         val startIndex = generations.value.lastAccessedIndex.get()
         val loadType = if (startIndex > index) LoadType.PREPEND else LoadType.APPEND
+        val scrollCount = abs(startIndex - index)
+        awaitScroll(loadType, scrollCount)
+    }
+
+    /**
+     * Imitates flinging from current index to the target index. It will continue scrolling
+     * even as data is being loaded in. Returns all available data that has been scrolled
+     * through.
+     *
+     * The scroll direction (prepend or append) is dependent on current index and target index. In
+     * general, scrolling to a smaller index triggers [PREPEND] while scrolling to a larger
+     * index triggers [APPEND].
+     *
+     * This function will scroll into placeholders. This means jumping is supported when
+     * [PagingConfig.enablePlaceholders] is true and the amount of placeholders traversed
+     * has reached [PagingConfig.jumpThreshold]. Jumping is disabled when
+     * [PagingConfig.enablePlaceholders] is false.
+     *
+     * When [PagingConfig.enablePlaceholders] is false, the [index] is scoped within currently
+     * loaded items. For example, in a list of items(0-20) with currently loaded items(10-15),
+     * index[0] = item(10), index[4] = item(15).
+     *
+     * Supports [index] beyond currently loaded items when [PagingConfig.enablePlaceholders]
+     * is false:
+     * 1. For prepends, it supports negative indices for as long as there are still available
+     * data to load from. For example, take a list of items(0-20), pageSize = 1, with currently
+     * loaded items(10-15). With index[0] = item(10), a `scrollTo(-4)` will scroll to item(6) and
+     * update index[0] = item(6).
+     * 2. For appends, it supports indices >= loadedDataSize. For example, take a list of
+     * items(0-20), pageSize = 1, with currently loaded items(10-15). With
+     * index[4] = item(15), a `scrollTo(7)` will scroll to item(18) and update
+     * index[7] = item(18).
+     * Note that both examples does not account for prefetches.
+
+     * The [index] accounts for separators/headers/footers where each one of those consumes one
+     * scrolled index.
+     *
+     * For both append/prepend, this function stops loading prior to fulfilling requested scroll
+     * distance if there are no more data to load from.
+     *
+     * @param [index] The target index to scroll to
+     *
+     * @see [scrollTo] for faking scrolls that awaits for placeholders to load before continuing
+     * to scroll.
+     */
+    public suspend fun flingTo(index: Int): @JvmSuppressWildcards Unit {
+        differ.awaitNotLoading()
+        appendOrPrependFlingTo(index)
+        differ.awaitNotLoading()
+    }
+
+    /**
+     * We start scrolling from startIndex +/- 1 so we don't accidentally trigger
+     * a prefetch on the opposite direction.
+     */
+    private suspend fun appendOrPrependFlingTo(index: Int) {
+        val startIndex = generations.value.lastAccessedIndex.get()
+        val loadType = if (startIndex > index) LoadType.PREPEND else LoadType.APPEND
+
         when (loadType) {
-            LoadType.PREPEND -> prependScrollTo(index, startIndex, scrollBehavior)
-            LoadType.APPEND -> appendScrollTo(index, startIndex, scrollBehavior)
+            LoadType.PREPEND -> prependFlingTo(startIndex, index)
+            LoadType.APPEND -> appendFlingTo(startIndex, index)
         }
     }
 
-    private suspend fun prependScrollTo(
-        index: Int,
-        startIndex: Int,
-        scrollBehavior: ScrollBehavior
+    /**
+     * Prepend flings to target index.
+     *
+     * If target index is negative, from index[0] onwards it will normal scroll until it fulfills
+     * remaining distance.
+     */
+    private suspend fun prependFlingTo(startIndex: Int, index: Int) {
+        var lastAccessedIndex = startIndex
+        val endIndex = maxOf(0, index)
+        // first, fast scroll to index or zero
+        for (i in startIndex - 1 downTo endIndex) {
+            differ[i]
+            lastAccessedIndex = i
+        }
+        setLastAccessedIndex(lastAccessedIndex)
+        // for negative indices, we delegate remainder of scrolling (distance below zero)
+        // to the awaiting version.
+        if (index < 0) {
+            val scrollCount = abs(index)
+            flingToOutOfBounds(LoadType.PREPEND, lastAccessedIndex, scrollCount)
+        }
+    }
+
+    /**
+     * Append flings to target index.
+     *
+     * If target index is beyond [PagingDataDiffer.size] - 1, from index(differ.size) and onwards,
+     * it will normal scroll until it fulfills remaining distance.
+     */
+    private suspend fun appendFlingTo(startIndex: Int, index: Int) {
+        var lastAccessedIndex = startIndex
+        val endIndex = minOf(index, differ.size - 1)
+        // first, fast scroll to endIndex
+        for (i in startIndex + 1..endIndex) {
+            differ[i]
+            lastAccessedIndex = i
+        }
+        setLastAccessedIndex(lastAccessedIndex)
+        // for indices at or beyond differ.size, we delegate remainder of scrolling (distance
+        // beyond differ.size) to the awaiting version.
+        if (index >= differ.size) {
+            val scrollCount = index - lastAccessedIndex
+            flingToOutOfBounds(LoadType.APPEND, lastAccessedIndex, scrollCount)
+        }
+    }
+
+    /**
+     * Delegated work from [flingTo] that is responsible for scrolling to indices that is
+     * beyond the range of [0 to differ.size-1].
+     *
+     * When [PagingConfig.enablePlaceholders] is true, this function is no-op because
+     * there is no more data to load from.
+     *
+     * When [PagingConfig.enablePlaceholders] is false, its delegated work to [awaitScroll]
+     * essentially loops (trigger next page --> await for next page) until
+     * it fulfills remaining (out of bounds) requested scroll distance.
+     */
+    private suspend fun flingToOutOfBounds(
+        loadType: LoadType,
+        lastAccessedIndex: Int,
+        scrollCount: Int
     ) {
-        val scrollCount = startIndex - index
-        when (scrollBehavior) {
-            ScrollBehavior.WaitForPlaceholdersToLoad -> awaitScrollTo(LoadType.PREPEND, scrollCount)
-            ScrollBehavior.ScrollIntoPlaceholders -> {
-                // TODO
-            }
-        }
+        // Wait for the page triggered by differ[lastAccessedIndex] to load in. This gives us the
+        // offsetIndex for next differ.get() because the current lastAccessedIndex is already the
+        // boundary index, such that differ[lastAccessedIndex +/- 1] will throw IndexOutOfBounds.
+        val (_, offsetIndex) = awaitLoad(lastAccessedIndex)
+        setLastAccessedIndex(offsetIndex)
+        // starts loading from the offsetIndex and scrolls the remaining requested distance
+        awaitScroll(loadType, scrollCount)
     }
 
-    private suspend fun appendScrollTo(
-        index: Int,
-        startIndex: Int,
-        scrollBehavior: ScrollBehavior
-    ) {
-        val scrollCount = index - startIndex
-        when (scrollBehavior) {
-            ScrollBehavior.WaitForPlaceholdersToLoad -> awaitScrollTo(LoadType.APPEND, scrollCount)
-            ScrollBehavior.ScrollIntoPlaceholders -> {
-                // TODO
-            }
-        }
-    }
-
-    private suspend fun awaitScrollTo(loadType: LoadType, scrollCount: Int) {
+    private suspend fun awaitScroll(loadType: LoadType, scrollCount: Int) {
         repeat(scrollCount) {
             awaitNextItem(loadType) ?: return
         }
@@ -321,29 +426,6 @@
         PREPEND,
         APPEND
     }
-
-    /**
-     * Determines whether the fake scroll will wait for asynchronous data to be loaded in or not.
-     *
-     * @see scrollTo
-     */
-    public enum class ScrollBehavior {
-        /**
-         * Imitates slow scrolls by waiting for item to be loaded in before triggering
-         * load on next item. A scroll with this behavior will return all available data
-         * that has been scrolled through.
-         */
-        ScrollIntoPlaceholders,
-
-        /**
-         * Imitates fast scrolling that will continue scrolling as data is being loaded in
-         * asynchronously. A scroll with this behavior will return only the data that has been
-         * loaded in by the time scrolling ends. This mode can also be used to trigger paging
-         * jumps if the number of placeholders scrolled through is larger than
-         * [PagingConfig.jumpThreshold].
-         */
-        WaitForPlaceholdersToLoad
-    }
 }
 
 internal data class Generation(
diff --git a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
index 4405498..8150e6f 100644
--- a/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
+++ b/paging/paging-testing/src/test/kotlin/androidx/paging/testing/PagerFlowSnapshotTest.kt
@@ -22,7 +22,6 @@
 import androidx.paging.PagingState
 import androidx.paging.cachedIn
 import androidx.paging.insertSeparators
-import androidx.paging.testing.SnapshotLoader.ScrollBehavior
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.delay
@@ -69,12 +68,9 @@
     @Test
     fun initialRefresh() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this) {}
             // first page + prefetched page
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7)
@@ -85,10 +81,7 @@
     @Test
     fun initialRefresh_withSeparators() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.map { pagingData ->
+        val pager = createPager(dataFlow).map { pagingData ->
             pagingData.insertSeparators { before: Int?, after: Int? ->
                 if (before != null && after != null) "sep" else null
             }
@@ -105,12 +98,9 @@
     @Test
     fun initialRefresh_withoutPrefetch() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPagerNoPrefetch(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this) {}
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4)
@@ -121,13 +111,9 @@
     @Test
     fun initialRefresh_withInitialKey() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPager(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this) {}
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
@@ -138,13 +124,9 @@
     @Test
     fun initialRefresh_withInitialKey_withoutPrefetch() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPagerNoPrefetch(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this) {}
 
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(10, 11, 12, 13, 14)
@@ -155,12 +137,9 @@
     @Test
     fun emptyInitialRefresh() {
         val dataFlow = emptyFlow<List<Int>>()
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {}
+            val snapshot = pager.asSnapshot(this) {}
 
             assertThat(snapshot).containsExactlyElementsIn(
                 emptyList<Int>()
@@ -171,11 +150,7 @@
     @Test
     fun manualRefresh() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
-
+        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 refresh()
@@ -189,12 +164,9 @@
     @Test
     fun manualEmptyRefresh() {
         val dataFlow = emptyFlow<List<Int>>()
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        )
+        val pager = createPagerNoPrefetch(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 refresh()
             }
             assertThat(snapshot).containsExactlyElementsIn(
@@ -206,12 +178,9 @@
     @Test
     fun appendWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
                     item < 14
                 }
@@ -229,10 +198,7 @@
     @Test
     fun appendWhile_withDrops() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_WITH_DROPS,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPagerWithDrops(dataFlow)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item ->
@@ -249,10 +215,7 @@
     @Test
     fun appendWhile_withSeparators() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.map { pagingData ->
+        val pager = createPager(dataFlow).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 9 || before == 12) "sep" else null
             }
@@ -279,10 +242,7 @@
     @Test
     fun appendWhile_withoutPrefetch() {
         val dataFlow = flowOf(List(50) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPagerNoPrefetch(dataFlow)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
@@ -300,10 +260,7 @@
     @Test
     fun appendWhile_withoutPlaceholders() {
         val dataFlow = flowOf(List(50) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPagerNoPlaceholders(dataFlow)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
@@ -323,13 +280,9 @@
     @Test
     fun prependWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 20)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
                     item > 14
                 }
@@ -347,13 +300,9 @@
     @Test
     fun prependWhile_withDrops() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_WITH_DROPS,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerWithDrops(dataFlow, 20)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
                     item > 14
                 }
@@ -368,11 +317,7 @@
     @Test
     fun prependWhile_withSeparators() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.map { pagingData ->
+        val pager = createPager(dataFlow, 20).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 14 || before == 18) "sep" else null
             }
@@ -399,11 +344,7 @@
     @Test
     fun prependWhile_withoutPrefetch() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPagerNoPrefetch(dataFlow, 20)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
@@ -421,11 +362,7 @@
     @Test
     fun prependWhile_withoutPlaceholders() {
         val dataFlow = flowOf(List(50) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 30,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPagerNoPlaceholders(dataFlow, 30)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
@@ -447,13 +384,9 @@
     @Test
     fun appendWhile_withInitialKey() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
                     item < 18
                 }
@@ -471,13 +404,9 @@
     @Test
     fun appendWhile_withInitialKey_withoutPlaceholders() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerNoPlaceholders(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
                     item != 19
                 }
@@ -495,13 +424,9 @@
     @Test
     fun appendWhile_withInitialKey_withoutPrefetch() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerNoPrefetch(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
                     item < 18
                 }
@@ -517,12 +442,9 @@
     @Test
     fun prependWhile_withoutInitialKey() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
                     item > -3
                 }
@@ -538,10 +460,7 @@
     @Test
     fun consecutiveAppendWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
@@ -570,11 +489,7 @@
     @Test
     fun consecutivePrependWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, 20).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
@@ -598,12 +513,9 @@
     @Test
     fun appendWhile_outOfBounds_returnsCurrentlyLoadedItems() {
         val dataFlow = flowOf(List(10) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
                     // condition scrolls till end of data since we only have 10 items
                     item < 18
@@ -620,13 +532,9 @@
     @Test
     fun prependWhile_outOfBounds_returnsCurrentlyLoadedItems() {
         val dataFlow = flowOf(List(20) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 10)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
+            val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
                     // condition scrolls till index = 0
                     item > -3
@@ -645,10 +553,7 @@
     @Test
     fun refreshAndAppendWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 refresh() // triggers second gen
@@ -665,11 +570,7 @@
     @Test
     fun refreshAndPrependWhile() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 20,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, 20).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // this prependScrollWhile does not cause paging to load more items
@@ -696,10 +597,7 @@
     @Test
     fun appendWhileAndRefresh() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 appendScrollWhile { item: Int ->
@@ -719,11 +617,7 @@
     @Test
     fun prependWhileAndRefresh() {
         val dataFlow = flowOf(List(30) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 15,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, 15).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 prependScrollWhile { item: Int ->
@@ -753,10 +647,7 @@
 
             emit(List(30) { it + 30 })
         }
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) { }
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -782,10 +673,7 @@
     @Test
     fun consecutiveGenerations_fromSharedFlow_emitAfterRefresh() {
         val dataFlow = MutableSharedFlow<List<Int>>()
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) { }
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -811,10 +699,7 @@
     @Test
     fun consecutiveGenerations_fromSharedFlow_emitBeforeRefresh() {
         val dataFlow = MutableSharedFlow<List<Int>>()
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             dataFlow.emit(emptyList())
             val snapshot1 = pager.asSnapshot(this) { }
@@ -847,10 +732,7 @@
             // second gen
             emit(List(20) { it })
         }
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) {
                 // we scroll to register a non-null anchorPos
@@ -882,11 +764,7 @@
             // second gen
             emit(List(20) { it })
         }
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, 10).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) { }
             assertThat(snapshot1).containsExactlyElementsIn(
@@ -912,11 +790,7 @@
             // second gen
             emit(List(20) { it })
         }
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 10,
-            pagingSourceFactory = createFactory(dataFlow)
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPrefetch(dataFlow, 10).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot1 = pager.asSnapshot(this) {
                 // we scroll to register a non-null anchorPos
@@ -938,16 +812,12 @@
     }
 
     @Test
-    fun prependToAwait() {
+    fun prependScroll() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 50)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(42)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -962,16 +832,12 @@
     }
 
     @Test
-    fun prependToAwait_withDrops() {
+    fun prependScroll_withDrops() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_WITH_DROPS,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerWithDrops(dataFlow, 50)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(42)
             }
             // dropped [47-57]
             assertThat(snapshot).containsExactlyElementsIn(
@@ -981,20 +847,16 @@
     }
 
     @Test
-    fun prependToAwait_withSeparators() {
+    fun prependScroll_withSeparators() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.map { pagingData ->
+        val pager = createPager(dataFlow, 50).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 42 || before == 49) "sep" else null
             }
         }
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(42)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -1010,17 +872,13 @@
     }
 
     @Test
-    fun consecutivePrependToAwait() {
+    fun consecutivePrependScroll() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 50)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
-                scrollTo(38, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(42)
+                scrollTo(38)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -1036,16 +894,12 @@
     }
 
     @Test
-    fun consecutivePrependToAwait_multiSnapshots() {
+    fun consecutivePrependScroll_multiSnapshots() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow, 50)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(42)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -1057,8 +911,8 @@
                 )
             )
 
-            val snapshot2 = pager.flow.asSnapshot(this) {
-                scrollTo(38, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot2 = pager.asSnapshot(this) {
+                scrollTo(38)
             }
             // prefetched [35-37]
             assertThat(snapshot2).containsExactlyElementsIn(
@@ -1071,16 +925,12 @@
     }
 
     @Test
-    fun prependToAwait_indexOutOfBounds() {
+    fun prependScroll_indexOutOfBounds() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 5,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, 5).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(-5, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(-5)
             }
             // ensure index is capped when no more data to load
             // initial load [5-9]
@@ -1093,20 +943,16 @@
     }
 
     @Test
-    fun prependToAwait_accessBoundaryIndex() {
+    fun prependScroll_accessPageBoundary() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow, 50).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(47, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(47)
             }
-            // ensure SnapshotLoader waited for the last prefetch before returning
+            // ensure that SnapshotLoader waited for last prefetch before returning
             // initial load [50-54]
-            // prefetched [47-49], [55-57]
+            // prefetched [47-49], [55-57] - expect only one extra page to be prefetched after this
             // scrollTo prepended [44-46]
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
@@ -1115,16 +961,12 @@
     }
 
     @Test
-    fun prependToAwait_withoutPrefetch() {
+    fun prependScroll_withoutPrefetch() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerNoPrefetch(dataFlow, 50)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(42, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(42)
             }
             // initial load [50-54]
             // prepended [41-49]
@@ -1135,45 +977,72 @@
     }
 
     @Test
-    fun prependToAwait_withoutPlaceholders() {
+    fun prependScroll_withoutPlaceholders() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // Without placeholders, first loaded page always starts at index[0]
-                scrollTo(-4, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(0)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
+            // prepended [44-46]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
+    @Test
+    fun prependScroll_withoutPlaceholders_indexOutOfBounds() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(-5)
+            }
+            // ensure it honors negative indices starting with index[0] = item[47]
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
             // scrollTo prepended [41-46]
             // prefetched [38-40]
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(
-                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54,
-                    55, 56, 57
+                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
                 )
             )
         }
     }
 
     @Test
-    fun consecutivePrependToAwait_withoutPlaceholders() {
+    fun prependScroll_withoutPlaceholders_indexOutOfBoundsIsCapped() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, 5).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(-5)
+            }
+            // ensure index is capped when no more data to load
+            // initial load [5-9]
+            // prefetched [2-4], [10-12]
+            // scrollTo prepended [0-1]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependScroll_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // Without placeholders, first loaded page always starts at index[0]
-                scrollTo(-1, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(-1)
                 // Without placeholders, first loaded page always starts at index[0]
-                scrollTo(-5, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(-5)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -1191,17 +1060,13 @@
     }
 
     @Test
-    fun consecutivePrependToAwait_withoutPlaceholders_multiSnapshot() {
+    fun consecutivePrependScroll_withoutPlaceholders_multiSnapshot() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 50,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // Without placeholders, first loaded page always starts at index[0]
-                scrollTo(-1, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(-1)
             }
             // initial load [50-54]
             // prefetched [47-49], [55-57]
@@ -1213,7 +1078,7 @@
 
             val snapshot2 = pager.asSnapshot(this) {
                 // Without placeholders, first loaded page always starts at index[0]
-                scrollTo(-5, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(-5)
             }
             // scrollTo prepended [35-40]
             // prefetched [32-34]
@@ -1227,29 +1092,7 @@
     }
 
     @Test
-    fun prependToAwait_withoutPlaceholders_indexOutOfBounds() {
-        val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            initialKey = 5,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
-        testScope.runTest {
-            val snapshot = pager.asSnapshot(this) {
-                scrollTo(-5, ScrollBehavior.WaitForPlaceholdersToLoad)
-            }
-            // ensure index is capped when no more data to load
-            // initial load [5-9]
-            // prefetched [2-4], [10-12]
-            // scrollTo prepended [0-1]
-            assertThat(snapshot).containsExactlyElementsIn(
-                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
-            )
-        }
-    }
-
-    @Test
-    fun prependToAwait_withoutPlaceholders_noPrefetchTriggered() {
+    fun prependScroll_withoutPlaceholders_noPrefetchTriggered() {
         val dataFlow = flowOf(List(100) { it })
         val pager = Pager(
             config = PagingConfig(
@@ -1277,15 +1120,12 @@
     }
 
     @Test
-    fun appendToAwait() {
+    fun appendScroll() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(12)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1298,15 +1138,12 @@
     }
 
     @Test
-    fun appendToAwait_withDrops() {
+    fun appendScroll_withDrops() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_WITH_DROPS,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerWithDrops(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(12)
             }
             // dropped [0-7]
             assertThat(snapshot).containsExactlyElementsIn(
@@ -1316,19 +1153,16 @@
     }
 
     @Test
-    fun appendToAwait_withSeparators() {
+    fun appendScroll_withSeparators() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.map { pagingData ->
+        val pager = createPager(dataFlow).map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 0 || before == 14) "sep" else null
             }
         }
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(12)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1341,16 +1175,13 @@
     }
 
     @Test
-    fun consecutiveAppendToAwait() {
+    fun consecutiveAppendScroll() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
-                scrollTo(18, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(12)
+                scrollTo(18)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1364,15 +1195,12 @@
     }
 
     @Test
-    fun consecutiveAppendToAwait_multiSnapshots() {
+    fun consecutiveAppendScroll_multiSnapshots() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPager(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(12)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1382,8 +1210,8 @@
                 listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
             )
 
-            val snapshot2 = pager.flow.asSnapshot(this) {
-                scrollTo(18, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot2 = pager.asSnapshot(this) {
+                scrollTo(18)
             }
             // appended [17-19]
             // prefetched [20-22]
@@ -1396,16 +1224,13 @@
     }
 
     @Test
-    fun appendToAwait_indexOutOfBounds() {
+    fun appendScroll_indexOutOfBounds() {
         val dataFlow = flowOf(List(15) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // index out of bounds
-                scrollTo(50, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(50)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1417,20 +1242,17 @@
     }
 
     @Test
-    fun appendToAwait_accessBoundaryIndex() {
+    fun appendScroll_accessPageBoundary() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
                 // after initial Load and prefetch, max loaded index is 7
-                scrollTo(7, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(7)
             }
             // ensure that SnapshotLoader waited for last prefetch before returning
             // initial load [0-4]
-            // prefetched [5-7]
+            // prefetched [5-7] - expect only one extra page to be prefetched after this
             // scrollTo appended [8-10]
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
@@ -1439,15 +1261,12 @@
     }
 
     @Test
-    fun appendToAwait_withoutPrefetch() {
+    fun appendScroll_withoutPrefetch() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PREFETCH,
-            pagingSourceFactory = createFactory(dataFlow),
-        )
+        val pager = createPagerNoPrefetch(dataFlow)
         testScope.runTest {
-            val snapshot = pager.flow.asSnapshot(this) {
-                scrollTo(10, ScrollBehavior.WaitForPlaceholdersToLoad)
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(10)
             }
             // initial load [0-4]
             // appended [5-10]
@@ -1458,22 +1277,55 @@
     }
 
     @Test
-    fun appendToAwait_withoutPlaceholders() {
+    fun appendScroll_withoutPlaceholders() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                // Without placeholders, SnapshotLoader will only scroll to max loaded
-                // index (differ.size - 1) regardless of what the requested index is
-                scrollTo(15, ScrollBehavior.WaitForPlaceholdersToLoad)
+                // scroll to max loaded index
+                scrollTo(7)
             }
             // initial load [0-4]
             // prefetched [5-7]
-            // scrollTo appended [8-16]
-            // prefetched [17-19]
+            // scrollTo appended [8-10]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+            )
+        }
+    }
+
+    @Test
+    fun appendScroll_withoutPlaceholders_indexOutOfBounds() {
+        val dataFlow = flowOf(List(20) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // 12 is larger than differ.size = 8 after initial refresh
+                scrollTo(12)
+            }
+            // ensure it honors scrollTo indices >= differ.size
+            // initial load [0-4]
+            // prefetched [5-7]
+            // scrollTo appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+        }
+    }
+
+    @Test
+    fun appendToScroll_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+        val dataFlow = flowOf(List(20) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(50)
+            }
+            // ensure index is still capped to max index available
+            // initial load [0-4]
+            // prefetched [5-7]
+            // scrollTo appended [8-19]
             assertThat(snapshot).containsExactlyElementsIn(
                 listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
             )
@@ -1481,16 +1333,13 @@
     }
 
     @Test
-    fun consecutiveAppendToAwait_withoutPlaceholders() {
+    fun consecutiveAppendScroll_withoutPlaceholders() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(12, ScrollBehavior.WaitForPlaceholdersToLoad)
-                scrollTo(17, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(12)
+                scrollTo(17)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1505,22 +1354,32 @@
     }
 
     @Test
-    fun appendToAwait_withoutPlaceholders_indexOutOfBounds() {
-        val dataFlow = flowOf(List(20) { it })
-        val pager = Pager(
-            config = CONFIG_NO_PLACEHOLDERS,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow.cachedIn(testScope.backgroundScope)
+    fun consecutiveAppendScroll_withoutPlaceholders_multiSnapshot() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(50, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(12)
             }
-            // ensure index is still capped to max index available
             // initial load [0-4]
             // prefetched [5-7]
-            // scrollTo appended [8-19]
+            // scrollTo appended [8-13]
+            // prefetched [14-16]
             assertThat(snapshot).containsExactlyElementsIn(
-                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                scrollTo(17)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // first scrollTo appended [8-13]
+            // second scrollTo appended [14-19]
+            // prefetched [19-22]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                    21, 22)
             )
         }
     }
@@ -1528,10 +1387,7 @@
     @Test
     fun scrollTo_indexAccountsForSeparators() {
         val dataFlow = flowOf(List(100) { it })
-        val pager = Pager(
-            config = CONFIG,
-            pagingSourceFactory = createFactory(dataFlow),
-        ).flow
+        val pager = createPager(dataFlow)
         val pagerWithSeparator = pager.map { pagingData ->
             pagingData.insertSeparators { before: Int?, _ ->
                 if (before == 6) "sep" else null
@@ -1539,7 +1395,7 @@
         }
         testScope.runTest {
             val snapshot = pager.asSnapshot(this) {
-                scrollTo(8, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(8)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1550,7 +1406,7 @@
             )
 
             val snapshotWithSeparator = pagerWithSeparator.asSnapshot(this) {
-                scrollTo(8, ScrollBehavior.WaitForPlaceholdersToLoad)
+                scrollTo(8)
             }
             // initial load [0-4]
             // prefetched [5-7]
@@ -1562,29 +1418,775 @@
         }
     }
 
-    val CONFIG = PagingConfig(
-        pageSize = 3,
-        initialLoadSize = 5,
-    )
+    @Test
+    fun prependFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(42)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [41-46]
+            // prefetched [38-40]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
 
-    val CONFIG_WITH_DROPS = PagingConfig(
-        pageSize = 3,
-        initialLoadSize = 5,
-        maxSize = 9
-    )
+    @Test
+    fun prependFling_withDrops() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithDrops(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(42)
+            }
+            // dropped [47-57]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(38, 39, 40, 41, 42, 43, 44, 45, 46)
+            )
+        }
+    }
 
-    val CONFIG_NO_PLACEHOLDERS = PagingConfig(
-        pageSize = 3,
-        initialLoadSize = 5,
-        enablePlaceholders = false,
-        prefetchDistance = 3
-    )
+    @Test
+    fun prependFling_withSeparators() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 50).map { pagingData ->
+            pagingData.insertSeparators { before: Int?, _ ->
+                if (before == 42 || before == 49) "sep" else null
+            }
+        }
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(42)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [41-46]
+            // prefetched [38-40]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    38, 39, 40, 41, 42, "sep", 43, 44, 45, 46, 47, 48, 49, "sep", 50, 51, 52,
+                    53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
 
-    val CONFIG_NO_PREFETCH = PagingConfig(
-        pageSize = 3,
-        initialLoadSize = 5,
-        prefetchDistance = 0
-    )
+    @Test
+    fun consecutivePrependFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(42)
+                flingTo(38)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [38-46]
+            // prefetched [35-37]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+                    51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependFling_multiSnapshots() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(42)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [41-46]
+            // prefetched [38-40]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                flingTo(38)
+            }
+            // prefetched [35-37]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(
+                    35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50,
+                    51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+    @Test
+    fun prependFling_jump() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(30)
+                // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+            }
+            // initial load [28-32]
+            // prefetched [25-27], [33-35]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_scrollThenJump() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(43)
+                flingTo(30)
+                // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+            }
+            // initial load [28-32]
+            // prefetched [25-27], [33-35]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_jumpThenFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(30)
+                // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+                flingTo(22)
+            }
+            // initial load [28-32]
+            // prefetched [25-27], [33-35]
+            // flingTo prepended [22-24]
+            // prefetched [19-21]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_indexOutOfBounds() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 10)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(-3)
+            }
+            // initial load [10-14]
+            // prefetched [7-9], [15-17]
+            // flingTo prepended [0-6]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_accessPageBoundary() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // page boundary
+                flingTo(44)
+            }
+            // ensure that SnapshotLoader waited for last prefetch before returning
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [44-46] - expect only one extra page to be prefetched after this
+            // prefetched [41-43]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                flingTo(0)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [44-46]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexOutOfBounds() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(-8)
+            }
+            // ensure we honor negative indices if there is data to load
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // prepended [38-46]
+            // prefetched [35-37]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53,
+                    54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 5).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(-20)
+            }
+            // ensure index is capped when no more data to load
+            // initial load [5-9]
+            // prefetched [2-4], [10-12]
+            // flingTo prepended [0-1]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12)
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependFling_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                flingTo(-1)
+                // Without placeholders, first loaded page always starts at index[0]
+                flingTo(-5)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // first flingTo prepended [41-46]
+            // index[0] is now anchored to [41]
+            // second flingTo prepended [35-40]
+            // prefetched [32-34]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(
+                    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+                    50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun consecutivePrependFling_withoutPlaceholders_multiSnapshot() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow, 50).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                flingTo(-1)
+            }
+            // initial load [50-54]
+            // prefetched [47-49], [55-57]
+            // flingTo prepended [44-46]
+            // prefetched [41-43]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57)
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                // Without placeholders, first loaded page always starts at index[0]
+                flingTo(-5)
+            }
+            // flingTo prepended [35-40]
+            // prefetched [32-34]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(
+                    32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49,
+                    50, 51, 52, 53, 54, 55, 56, 57
+                )
+            )
+        }
+    }
+
+    @Test
+    fun prependFling_withoutPlaceholders_indexPrecision() {
+        val dataFlow = flowOf(List(100) { it })
+        // load sizes and prefetch set to 1 to test precision of flingTo indexing
+        val pager = Pager(
+            config = PagingConfig(
+                pageSize = 1,
+                initialLoadSize = 1,
+                enablePlaceholders = false,
+                prefetchDistance = 1
+            ),
+            initialKey = 50,
+            pagingSourceFactory = createFactory(dataFlow),
+        )
+        testScope.runTest {
+            val snapshot = pager.flow.asSnapshot(this) {
+                // after refresh, lastAccessedIndex == index[2] == item(9)
+                flingTo(-1)
+            }
+            // initial load [50]
+            // prefetched [49], [51]
+            // prepended [48]
+            // prefetched [47]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(47, 48, 49, 50, 51)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withDrops() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithDrops(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+            }
+            // dropped [0-7]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withSeparators() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow).map { pagingData ->
+            pagingData.insertSeparators { before: Int?, _ ->
+                if (before == 0 || before == 14) "sep" else null
+            }
+        }
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, "sep", 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, "sep", 15, 16)
+            )
+        }
+    }
+
+    @Test
+    fun consecutiveAppendFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+                flingTo(18)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // appended [8-19]
+            // prefetched [20-22]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                    21, 22)
+            )
+        }
+    }
+
+    @Test
+    fun consecutiveAppendFling_multiSnapshots() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                flingTo(18)
+            }
+            // appended [17-19]
+            // prefetched [20-22]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17,
+                    18, 19, 20, 21, 22
+                )
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_jump() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(30)
+                // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+            }
+            // initial load [28-32]
+            // prefetched [25-27], [33-35]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_scrollThenJump() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                scrollTo(30)
+                flingTo(43)
+                // jump triggered when flingTo registered lastAccessedIndex[43], refreshKey[41]
+            }
+            // initial load [41-45]
+            // prefetched [38-40], [46-48]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_jumpThenFling() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerWithJump(dataFlow)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(30)
+                // jump triggered when flingTo registered lastAccessedIndex[30], refreshKey[28]
+                flingTo(38)
+            }
+            // initial load [28-32]
+            // prefetched [25-27], [33-35]
+            // flingTo appended [36-38]
+            // prefetched [39-41]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_indexOutOfBounds() {
+        val dataFlow = flowOf(List(15) { it })
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // index out of bounds
+                flingTo(50)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // flingTo appended [8-10]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_accessPageBoundary() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // after initial Load and prefetch, max loaded index is 7
+                flingTo(7)
+            }
+            // ensure that SnapshotLoader waited for last prefetch before returning
+            // initial load [0-4]
+            // prefetched [5-7] - expect only one extra page to be prefetched after this
+            // flingTo appended [8-10]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // scroll to max loaded index
+                flingTo(7)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // flingTo appended [8-10]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexOutOfBounds() {
+        val dataFlow = flowOf(List(20) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                // 12 is larger than differ.size = 8 after initial refresh
+                flingTo(12)
+            }
+            // ensure it honors scrollTo indices >= differ.size
+            // initial load [0-4]
+            // prefetched [5-7]
+            // flingTo appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexOutOfBoundsIsCapped() {
+        val dataFlow = flowOf(List(20) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(50)
+            }
+            // ensure index is still capped to max index available
+            // initial load [0-4]
+            // prefetched [5-7]
+            // flingTo appended [8-19]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19)
+            )
+        }
+    }
+
+    @Test
+    fun consecutiveAppendFling_withoutPlaceholders() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+                flingTo(17)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // first flingTo appended [8-13]
+            // second flingTo appended [14-19]
+            // prefetched [19-22]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                    21, 22)
+            )
+        }
+    }
+
+    @Test
+    fun consecutiveAppendFling_withoutPlaceholders_multiSnapshot() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPagerNoPlaceholders(dataFlow).cachedIn(testScope.backgroundScope)
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(12)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // flingTo appended [8-13]
+            // prefetched [14-16]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16)
+            )
+
+            val snapshot2 = pager.asSnapshot(this) {
+                flingTo(17)
+            }
+            // initial load [0-4]
+            // prefetched [5-7]
+            // first flingTo appended [8-13]
+            // second flingTo appended [14-19]
+            // prefetched [19-22]
+            assertThat(snapshot2).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20,
+                    21, 22)
+            )
+        }
+    }
+
+    @Test
+    fun appendFling_withoutPlaceholders_indexPrecision() {
+        val dataFlow = flowOf(List(100) { it })
+        // load sizes and prefetch set to 1 to test precision of flingTo indexing
+        val pager = Pager(
+            config = PagingConfig(
+                pageSize = 1,
+                initialLoadSize = 1,
+                enablePlaceholders = false,
+                prefetchDistance = 1
+            ),
+            pagingSourceFactory = createFactory(dataFlow),
+        )
+        testScope.runTest {
+            val snapshot = pager.flow.asSnapshot(this) {
+                // after refresh, lastAccessedIndex == index[2] == item(9)
+                flingTo(2)
+            }
+            // initial load [0]
+            // prefetched [1]
+            // appended [2]
+            // prefetched [3]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(0, 1, 2, 3)
+            )
+        }
+    }
+
+    @Test
+    fun flingTo_indexAccountsForSeparators() {
+        val dataFlow = flowOf(List(100) { it })
+        val pager = createPager(
+            dataFlow,
+            PagingConfig(
+                pageSize = 1,
+                initialLoadSize = 1,
+                prefetchDistance = 1
+            ),
+            50
+        )
+        val pagerWithSeparator = pager.map { pagingData ->
+            pagingData.insertSeparators { before: Int?, _ ->
+                if (before == 49) "sep" else null
+            }
+        }
+        testScope.runTest {
+            val snapshot = pager.asSnapshot(this) {
+                flingTo(51)
+            }
+            // initial load [50]
+            // prefetched [49], [51]
+            // flingTo [51] accessed item[51]prefetched [52]
+            assertThat(snapshot).containsExactlyElementsIn(
+                listOf(49, 50, 51, 52)
+            )
+
+            val snapshotWithSeparator = pagerWithSeparator.asSnapshot(this) {
+                flingTo(51)
+            }
+            // initial load [50]
+            // prefetched [49], [51]
+            // flingTo [51] accessed item[50], no prefetch triggered
+            assertThat(snapshotWithSeparator).containsExactlyElementsIn(
+                listOf(49, "sep", 50, 51)
+            )
+        }
+    }
+
+    private fun createPager(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+        createPager(
+            dataFlow,
+            PagingConfig(pageSize = 3, initialLoadSize = 5),
+            initialKey
+        )
+
+    private fun createPagerNoPlaceholders(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+        createPager(
+            dataFlow,
+            PagingConfig(
+                pageSize = 3,
+                initialLoadSize = 5,
+                enablePlaceholders = false,
+                prefetchDistance = 3
+            ),
+            initialKey)
+
+    private fun createPagerNoPrefetch(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+        createPager(
+            dataFlow,
+            PagingConfig(pageSize = 3, initialLoadSize = 5, prefetchDistance = 0),
+            initialKey
+        )
+
+    private fun createPagerWithJump(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+        createPager(
+            dataFlow,
+            PagingConfig(pageSize = 3, initialLoadSize = 5, jumpThreshold = 5),
+            initialKey
+        )
+
+    private fun createPagerWithDrops(dataFlow: Flow<List<Int>>, initialKey: Int = 0) =
+        createPager(
+            dataFlow,
+            PagingConfig(pageSize = 3, initialLoadSize = 5, maxSize = 9),
+            initialKey
+        )
+
+    private fun createPager(
+        dataFlow: Flow<List<Int>>,
+        config: PagingConfig,
+        initialKey: Int = 0,
+    ) = Pager(
+            config = config,
+            initialKey = initialKey,
+            pagingSourceFactory = createFactory(dataFlow),
+        ).flow
 }
 
 private class WrappedPagingSourceFactory(
diff --git a/profileinstaller/profileinstaller/api/1.3.0-beta01.txt b/profileinstaller/profileinstaller/api/1.3.0-beta01.txt
new file mode 100644
index 0000000..e7da088
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/1.3.0-beta01.txt
@@ -0,0 +1,74 @@
+// Signature format: 4.0
+package androidx.profileinstaller {
+
+  public class ProfileInstallReceiver extends android.content.BroadcastReceiver {
+    ctor public ProfileInstallReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent?);
+    field public static final String ACTION_BENCHMARK_OPERATION = "androidx.profileinstaller.action.BENCHMARK_OPERATION";
+    field public static final String ACTION_INSTALL_PROFILE = "androidx.profileinstaller.action.INSTALL_PROFILE";
+    field public static final String ACTION_SAVE_PROFILE = "androidx.profileinstaller.action.SAVE_PROFILE";
+    field public static final String ACTION_SKIP_FILE = "androidx.profileinstaller.action.SKIP_FILE";
+  }
+
+  public class ProfileInstaller {
+    method @WorkerThread public static void writeProfile(android.content.Context);
+    method @WorkerThread public static void writeProfile(android.content.Context, java.util.concurrent.Executor, androidx.profileinstaller.ProfileInstaller.DiagnosticsCallback);
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_DOES_NOT_EXIST = 2; // 0x2
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_EXISTS = 1; // 0x1
+    field public static final int DIAGNOSTIC_PROFILE_IS_COMPRESSED = 5; // 0x5
+    field public static final int DIAGNOSTIC_REF_PROFILE_DOES_NOT_EXIST = 4; // 0x4
+    field public static final int DIAGNOSTIC_REF_PROFILE_EXISTS = 3; // 0x3
+    field public static final int RESULT_ALREADY_INSTALLED = 2; // 0x2
+    field public static final int RESULT_BASELINE_PROFILE_NOT_FOUND = 6; // 0x6
+    field public static final int RESULT_BENCHMARK_OPERATION_FAILURE = 15; // 0xf
+    field public static final int RESULT_BENCHMARK_OPERATION_SUCCESS = 14; // 0xe
+    field public static final int RESULT_BENCHMARK_OPERATION_UNKNOWN = 16; // 0x10
+    field public static final int RESULT_DELETE_SKIP_FILE_SUCCESS = 11; // 0xb
+    field public static final int RESULT_DESIRED_FORMAT_UNSUPPORTED = 5; // 0x5
+    field public static final int RESULT_INSTALL_SKIP_FILE_SUCCESS = 10; // 0xa
+    field public static final int RESULT_INSTALL_SUCCESS = 1; // 0x1
+    field public static final int RESULT_IO_EXCEPTION = 7; // 0x7
+    field public static final int RESULT_META_FILE_REQUIRED_BUT_NOT_FOUND = 9; // 0x9
+    field public static final int RESULT_NOT_WRITABLE = 4; // 0x4
+    field public static final int RESULT_PARSE_EXCEPTION = 8; // 0x8
+    field public static final int RESULT_SAVE_PROFILE_SIGNALLED = 12; // 0xc
+    field public static final int RESULT_SAVE_PROFILE_SKIPPED = 13; // 0xd
+    field public static final int RESULT_UNSUPPORTED_ART_VERSION = 3; // 0x3
+  }
+
+  public static interface ProfileInstaller.DiagnosticsCallback {
+    method public void onDiagnosticReceived(int, Object?);
+    method public void onResultReceived(int, Object?);
+  }
+
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result> {
+    ctor public ProfileInstallerInitializer();
+    method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+  }
+
+  public static class ProfileInstallerInitializer.Result {
+    ctor public ProfileInstallerInitializer.Result();
+  }
+
+  public final class ProfileVerifier {
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.profileinstaller.ProfileVerifier.CompilationStatus!> getCompilationStatusAsync();
+    method @WorkerThread public static androidx.profileinstaller.ProfileVerifier.CompilationStatus writeProfileVerification(android.content.Context);
+  }
+
+  public static class ProfileVerifier.CompilationStatus {
+    method public int getProfileInstallResultCode();
+    method public boolean hasProfileEnqueuedForCompilation();
+    method public boolean isCompiledWithProfile();
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE = 1; // 0x1
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING = 3; // 0x3
+    field public static final int RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ = 131072; // 0x20000
+    field public static final int RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE = 196608; // 0x30000
+    field public static final int RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST = 65536; // 0x10000
+    field public static final int RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION = 262144; // 0x40000
+    field public static final int RESULT_CODE_NO_PROFILE = 0; // 0x0
+    field public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2; // 0x2
+  }
+
+}
+
diff --git a/profileinstaller/profileinstaller/api/public_plus_experimental_1.3.0-beta01.txt b/profileinstaller/profileinstaller/api/public_plus_experimental_1.3.0-beta01.txt
new file mode 100644
index 0000000..e7da088
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/public_plus_experimental_1.3.0-beta01.txt
@@ -0,0 +1,74 @@
+// Signature format: 4.0
+package androidx.profileinstaller {
+
+  public class ProfileInstallReceiver extends android.content.BroadcastReceiver {
+    ctor public ProfileInstallReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent?);
+    field public static final String ACTION_BENCHMARK_OPERATION = "androidx.profileinstaller.action.BENCHMARK_OPERATION";
+    field public static final String ACTION_INSTALL_PROFILE = "androidx.profileinstaller.action.INSTALL_PROFILE";
+    field public static final String ACTION_SAVE_PROFILE = "androidx.profileinstaller.action.SAVE_PROFILE";
+    field public static final String ACTION_SKIP_FILE = "androidx.profileinstaller.action.SKIP_FILE";
+  }
+
+  public class ProfileInstaller {
+    method @WorkerThread public static void writeProfile(android.content.Context);
+    method @WorkerThread public static void writeProfile(android.content.Context, java.util.concurrent.Executor, androidx.profileinstaller.ProfileInstaller.DiagnosticsCallback);
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_DOES_NOT_EXIST = 2; // 0x2
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_EXISTS = 1; // 0x1
+    field public static final int DIAGNOSTIC_PROFILE_IS_COMPRESSED = 5; // 0x5
+    field public static final int DIAGNOSTIC_REF_PROFILE_DOES_NOT_EXIST = 4; // 0x4
+    field public static final int DIAGNOSTIC_REF_PROFILE_EXISTS = 3; // 0x3
+    field public static final int RESULT_ALREADY_INSTALLED = 2; // 0x2
+    field public static final int RESULT_BASELINE_PROFILE_NOT_FOUND = 6; // 0x6
+    field public static final int RESULT_BENCHMARK_OPERATION_FAILURE = 15; // 0xf
+    field public static final int RESULT_BENCHMARK_OPERATION_SUCCESS = 14; // 0xe
+    field public static final int RESULT_BENCHMARK_OPERATION_UNKNOWN = 16; // 0x10
+    field public static final int RESULT_DELETE_SKIP_FILE_SUCCESS = 11; // 0xb
+    field public static final int RESULT_DESIRED_FORMAT_UNSUPPORTED = 5; // 0x5
+    field public static final int RESULT_INSTALL_SKIP_FILE_SUCCESS = 10; // 0xa
+    field public static final int RESULT_INSTALL_SUCCESS = 1; // 0x1
+    field public static final int RESULT_IO_EXCEPTION = 7; // 0x7
+    field public static final int RESULT_META_FILE_REQUIRED_BUT_NOT_FOUND = 9; // 0x9
+    field public static final int RESULT_NOT_WRITABLE = 4; // 0x4
+    field public static final int RESULT_PARSE_EXCEPTION = 8; // 0x8
+    field public static final int RESULT_SAVE_PROFILE_SIGNALLED = 12; // 0xc
+    field public static final int RESULT_SAVE_PROFILE_SKIPPED = 13; // 0xd
+    field public static final int RESULT_UNSUPPORTED_ART_VERSION = 3; // 0x3
+  }
+
+  public static interface ProfileInstaller.DiagnosticsCallback {
+    method public void onDiagnosticReceived(int, Object?);
+    method public void onResultReceived(int, Object?);
+  }
+
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result> {
+    ctor public ProfileInstallerInitializer();
+    method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+  }
+
+  public static class ProfileInstallerInitializer.Result {
+    ctor public ProfileInstallerInitializer.Result();
+  }
+
+  public final class ProfileVerifier {
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.profileinstaller.ProfileVerifier.CompilationStatus!> getCompilationStatusAsync();
+    method @WorkerThread public static androidx.profileinstaller.ProfileVerifier.CompilationStatus writeProfileVerification(android.content.Context);
+  }
+
+  public static class ProfileVerifier.CompilationStatus {
+    method public int getProfileInstallResultCode();
+    method public boolean hasProfileEnqueuedForCompilation();
+    method public boolean isCompiledWithProfile();
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE = 1; // 0x1
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING = 3; // 0x3
+    field public static final int RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ = 131072; // 0x20000
+    field public static final int RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE = 196608; // 0x30000
+    field public static final int RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST = 65536; // 0x10000
+    field public static final int RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION = 262144; // 0x40000
+    field public static final int RESULT_CODE_NO_PROFILE = 0; // 0x0
+    field public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2; // 0x2
+  }
+
+}
+
diff --git a/camera/camera-viewfinder/api/res-1.2.0-beta01.txt b/profileinstaller/profileinstaller/api/res-1.3.0-beta01.txt
similarity index 100%
rename from camera/camera-viewfinder/api/res-1.2.0-beta01.txt
rename to profileinstaller/profileinstaller/api/res-1.3.0-beta01.txt
diff --git a/profileinstaller/profileinstaller/api/restricted_1.3.0-beta01.txt b/profileinstaller/profileinstaller/api/restricted_1.3.0-beta01.txt
new file mode 100644
index 0000000..e7da088
--- /dev/null
+++ b/profileinstaller/profileinstaller/api/restricted_1.3.0-beta01.txt
@@ -0,0 +1,74 @@
+// Signature format: 4.0
+package androidx.profileinstaller {
+
+  public class ProfileInstallReceiver extends android.content.BroadcastReceiver {
+    ctor public ProfileInstallReceiver();
+    method public void onReceive(android.content.Context, android.content.Intent?);
+    field public static final String ACTION_BENCHMARK_OPERATION = "androidx.profileinstaller.action.BENCHMARK_OPERATION";
+    field public static final String ACTION_INSTALL_PROFILE = "androidx.profileinstaller.action.INSTALL_PROFILE";
+    field public static final String ACTION_SAVE_PROFILE = "androidx.profileinstaller.action.SAVE_PROFILE";
+    field public static final String ACTION_SKIP_FILE = "androidx.profileinstaller.action.SKIP_FILE";
+  }
+
+  public class ProfileInstaller {
+    method @WorkerThread public static void writeProfile(android.content.Context);
+    method @WorkerThread public static void writeProfile(android.content.Context, java.util.concurrent.Executor, androidx.profileinstaller.ProfileInstaller.DiagnosticsCallback);
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_DOES_NOT_EXIST = 2; // 0x2
+    field public static final int DIAGNOSTIC_CURRENT_PROFILE_EXISTS = 1; // 0x1
+    field public static final int DIAGNOSTIC_PROFILE_IS_COMPRESSED = 5; // 0x5
+    field public static final int DIAGNOSTIC_REF_PROFILE_DOES_NOT_EXIST = 4; // 0x4
+    field public static final int DIAGNOSTIC_REF_PROFILE_EXISTS = 3; // 0x3
+    field public static final int RESULT_ALREADY_INSTALLED = 2; // 0x2
+    field public static final int RESULT_BASELINE_PROFILE_NOT_FOUND = 6; // 0x6
+    field public static final int RESULT_BENCHMARK_OPERATION_FAILURE = 15; // 0xf
+    field public static final int RESULT_BENCHMARK_OPERATION_SUCCESS = 14; // 0xe
+    field public static final int RESULT_BENCHMARK_OPERATION_UNKNOWN = 16; // 0x10
+    field public static final int RESULT_DELETE_SKIP_FILE_SUCCESS = 11; // 0xb
+    field public static final int RESULT_DESIRED_FORMAT_UNSUPPORTED = 5; // 0x5
+    field public static final int RESULT_INSTALL_SKIP_FILE_SUCCESS = 10; // 0xa
+    field public static final int RESULT_INSTALL_SUCCESS = 1; // 0x1
+    field public static final int RESULT_IO_EXCEPTION = 7; // 0x7
+    field public static final int RESULT_META_FILE_REQUIRED_BUT_NOT_FOUND = 9; // 0x9
+    field public static final int RESULT_NOT_WRITABLE = 4; // 0x4
+    field public static final int RESULT_PARSE_EXCEPTION = 8; // 0x8
+    field public static final int RESULT_SAVE_PROFILE_SIGNALLED = 12; // 0xc
+    field public static final int RESULT_SAVE_PROFILE_SKIPPED = 13; // 0xd
+    field public static final int RESULT_UNSUPPORTED_ART_VERSION = 3; // 0x3
+  }
+
+  public static interface ProfileInstaller.DiagnosticsCallback {
+    method public void onDiagnosticReceived(int, Object?);
+    method public void onResultReceived(int, Object?);
+  }
+
+  public class ProfileInstallerInitializer implements androidx.startup.Initializer<androidx.profileinstaller.ProfileInstallerInitializer.Result> {
+    ctor public ProfileInstallerInitializer();
+    method public androidx.profileinstaller.ProfileInstallerInitializer.Result create(android.content.Context);
+    method public java.util.List<java.lang.Class<? extends androidx.startup.Initializer<?>>!> dependencies();
+  }
+
+  public static class ProfileInstallerInitializer.Result {
+    ctor public ProfileInstallerInitializer.Result();
+  }
+
+  public final class ProfileVerifier {
+    method public static com.google.common.util.concurrent.ListenableFuture<androidx.profileinstaller.ProfileVerifier.CompilationStatus!> getCompilationStatusAsync();
+    method @WorkerThread public static androidx.profileinstaller.ProfileVerifier.CompilationStatus writeProfileVerification(android.content.Context);
+  }
+
+  public static class ProfileVerifier.CompilationStatus {
+    method public int getProfileInstallResultCode();
+    method public boolean hasProfileEnqueuedForCompilation();
+    method public boolean isCompiledWithProfile();
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE = 1; // 0x1
+    field public static final int RESULT_CODE_COMPILED_WITH_PROFILE_NON_MATCHING = 3; // 0x3
+    field public static final int RESULT_CODE_ERROR_CACHE_FILE_EXISTS_BUT_CANNOT_BE_READ = 131072; // 0x20000
+    field public static final int RESULT_CODE_ERROR_CANT_WRITE_PROFILE_VERIFICATION_RESULT_CACHE_FILE = 196608; // 0x30000
+    field public static final int RESULT_CODE_ERROR_PACKAGE_NAME_DOES_NOT_EXIST = 65536; // 0x10000
+    field public static final int RESULT_CODE_ERROR_UNSUPPORTED_API_VERSION = 262144; // 0x40000
+    field public static final int RESULT_CODE_NO_PROFILE = 0; // 0x0
+    field public static final int RESULT_CODE_PROFILE_ENQUEUED_FOR_COMPILATION = 2; // 0x2
+  }
+
+}
+
diff --git a/recyclerview/OWNERS b/recyclerview/OWNERS
index bcc9f1e..33d6a94 100644
--- a/recyclerview/OWNERS
+++ b/recyclerview/OWNERS
@@ -1,2 +1,3 @@
+# Bug component: 461174
 ryanmentley@google.com
 yboyar@google.com
\ No newline at end of file
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AlteredTableColumnOrderTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AlteredTableColumnOrderTest.kt
index d873f1c..99740cc 100644
--- a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AlteredTableColumnOrderTest.kt
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/AlteredTableColumnOrderTest.kt
@@ -116,7 +116,7 @@
     @Dao
     internal interface FooDao {
         @Insert
-        fun insertFoo(f: Foo?)
+        fun insertFoo(f: Foo)
 
         @Query("SELECT * FROM Foo LIMIT 1")
         fun getOneFoo(): Foo
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 866789b..43badcf 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
@@ -48,6 +48,7 @@
 import kotlinx.coroutines.withContext
 import org.junit.After
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -340,6 +341,7 @@
         }
     }
 
+    @Ignore // b/260592924
     @Test
     fun prependWithBlockingObserver() {
         val items = createItems(startId = 0, count = 90)
diff --git a/room/room-compiler/build.gradle b/room/room-compiler/build.gradle
index e0d4d13..08515b3 100644
--- a/room/room-compiler/build.gradle
+++ b/room/room-compiler/build.gradle
@@ -101,7 +101,6 @@
     implementation(libs.kspApi)
     shadowed(libs.antlr4)
     implementation(libs.sqliteJdbc)
-    implementation(libs.kotlinMetadataJvm)
     implementation(libs.apacheCommonsCodec)
     implementation(libs.intellijAnnotations)
     testImplementation(libs.truth)
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 98c9db5..b077b98 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
@@ -206,6 +206,9 @@
         "private, final, or abstract. It can be abstract only if the method is also" +
         " annotated with @Query."
 
+    fun nullableParamInShortcutMethod(param: String) = "Methods annotated with [@Insert, " +
+        "@Upsert, @Update, @Delete] shouldn't declare nullable parameters ($param)."
+
     fun transactionMethodAsync(returnTypeName: String) = "Method annotated with @Transaction must" +
         " not return deferred/async return type $returnTypeName. Since transactions are" +
         " thread confined and Room cannot guarantee that all queries in the method" +
diff --git a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
index d706e03..442c03e 100644
--- a/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
+++ b/room/room-compiler/src/main/kotlin/androidx/room/processor/ShortcutParameterProcessor.kt
@@ -16,6 +16,7 @@
 
 package androidx.room.processor
 
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.XVariableElement
 import androidx.room.compiler.processing.isArray
@@ -32,10 +33,20 @@
     val context = baseContext.fork(element)
     fun process(): ShortcutQueryParameter {
         val asMember = element.asMemberOf(containing)
+        if (isParamNullable(asMember)) {
+            context.logger.e(
+                element = element,
+                msg = ProcessorErrors.nullableParamInShortcutMethod(
+                    asMember.asTypeName().toString(context.codeLanguage)
+                )
+            )
+        }
+
         val name = element.name
         context.checker.check(
-            !name.startsWith("_"), element,
-            ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE
+            !name.startsWith("_"),
+            element = element,
+            errorMsg = ProcessorErrors.QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE
         )
 
         val (pojoType, isMultiple) = extractPojoType(asMember)
@@ -48,6 +59,13 @@
         )
     }
 
+    private fun isParamNullable(paramType: XType): Boolean {
+        if (paramType.nullability == XNullability.NULLABLE) return true
+        if (paramType.isArray() && paramType.componentType.nullability == XNullability.NULLABLE)
+            return true
+        return paramType.typeArguments.any { it.nullability == XNullability.NULLABLE }
+    }
+
     @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
     private fun extractPojoType(typeMirror: XType): Pair<XType?, Boolean> {
 
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
index 4bb5f54..32f453f 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/DeleteOrUpdateShortcutMethodProcessorTest.kt
@@ -118,6 +118,20 @@
     }
 
     @Test
+    fun singleNullableParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user: User?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+            }
+        }
+    }
+
+    @Test
     fun notAnEntity() {
         singleShortcutMethod(
             """
@@ -168,6 +182,21 @@
     }
 
     @Test
+    fun twoNullableParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user1: User?, user2: User?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+                hasErrorCount(2)
+            }
+        }
+    }
+
+    @Test
     fun list() {
         listOf(
             "int",
@@ -206,6 +235,24 @@
     }
 
     @Test
+    fun nullableListParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: List<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "java.util.List<? extends foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun array() {
         singleShortcutMethod(
             """
@@ -231,6 +278,22 @@
     }
 
     @Test
+    fun nullableArrayParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: Array<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User[]")
+                )
+            }
+        }
+    }
+
+    @Test
     fun set() {
         singleShortcutMethod(
             """
@@ -256,6 +319,24 @@
     }
 
     @Test
+    fun nullableSetParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: Set<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "java.util.Set<? extends foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun iterable() {
         singleShortcutMethod(
             """
@@ -309,6 +390,25 @@
     }
 
     @Test
+    fun nullableCustomCollectionParamError() {
+        singleShortcutMethodKotlin(
+            """
+                class MyList<Irrelevant, Item> : ArrayList<Item> {}
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: MyList<String?, User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "foo.bar.MyClass.MyList<java.lang.String, foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun differentTypes() {
         listOf(
             "void",
@@ -352,6 +452,22 @@
     }
 
     @Test
+    fun twoNullableDifferentParamError() {
+        singleShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user1: User?, book1: Book?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.Book"))
+                hasErrorCount(2)
+            }
+        }
+    }
+
+    @Test
     fun invalidReturnType() {
         listOf(
             "long",
diff --git a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
index de17c33..bce40fb 100644
--- a/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
+++ b/room/room-compiler/src/test/kotlin/androidx/room/processor/InsertOrUpsertShortcutMethodProcessorTest.kt
@@ -145,6 +145,21 @@
                 .isEqualTo(XTypeName.PRIMITIVE_LONG)
         }
     }
+
+    @Test
+    fun singleNullableParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user: User?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+            }
+        }
+    }
+
     @Test
     fun two() {
         singleInsertUpsertShortcutMethod(
@@ -178,6 +193,21 @@
     }
 
     @Test
+    fun twoNullableParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user1: User?, user2: User?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+                hasErrorCount(2)
+            }
+        }
+    }
+
+    @Test
     fun list() {
         singleInsertUpsertShortcutMethod(
             """
@@ -209,6 +239,24 @@
     }
 
     @Test
+    fun nullableListParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: List<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "java.util.List<? extends foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun array() {
         singleInsertUpsertShortcutMethod(
             """
@@ -233,6 +281,20 @@
     }
 
     @Test
+    fun nullableArrayParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: Array<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User[]"))
+            }
+        }
+    }
+
+    @Test
     fun set() {
         singleInsertUpsertShortcutMethod(
             """
@@ -258,6 +320,24 @@
     }
 
     @Test
+    fun nullableSetParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: Set<User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "java.util.Set<? extends foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun queue() {
         singleInsertUpsertShortcutMethod(
             """
@@ -336,6 +416,25 @@
     }
 
     @Test
+    fun nullableCustomCollectionParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                class MyList<Irrelevant, Item> : ArrayList<Item> {}
+                @${annotation.java.canonicalName}
+                abstract fun foo(users: MyList<String?, User?>)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.nullableParamInShortcutMethod(
+                        "foo.bar.MyClass.MyList<java.lang.String, foo.bar.User>"
+                    )
+                )
+            }
+        }
+    }
+
+    @Test
     fun differentTypes() {
         singleInsertUpsertShortcutMethod(
             """
@@ -367,6 +466,22 @@
     }
 
     @Test
+    fun twoNullableDifferentParamError() {
+        singleInsertUpsertShortcutMethodKotlin(
+            """
+                @${annotation.java.canonicalName}
+                abstract fun foo(user1: User?, book1: Book?)
+                """
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.User"))
+                hasErrorContaining(ProcessorErrors.nullableParamInShortcutMethod("foo.bar.Book"))
+                hasErrorCount(2)
+            }
+        }
+    }
+
+    @Test
     fun invalidReturnType() {
         listOf(
             "int",
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
index 8117b44..6133c5e 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/activities/MainActivity.java
@@ -116,6 +116,16 @@
     final SessionManager mSessionManager = new SessionManager("app");
     Player mPlayer;
 
+    private final MediaRouter.OnPrepareTransferListener mOnPrepareTransferListener =
+            (fromRoute, toRoute) -> {
+                Log.d(TAG, "onPrepareTransfer: from=" + fromRoute.getId()
+                        + ", to=" + toRoute.getId());
+                return CallbackToFutureAdapter.getFuture(completer -> {
+                    mHandler.postDelayed(() -> completer.set(null), 3000);
+                    return "onPrepareTransfer";
+                });
+            };
+
     private final MediaRouter.Callback mMediaRouterCB = new MediaRouter.Callback() {
         // Return a custom callback that will simply log all of the route events
         // for demonstration purposes.
@@ -229,14 +239,7 @@
                 .addControlCategory(SampleMediaRouteProvider.CATEGORY_SAMPLE_ROUTE)
                 .build();
 
-        mMediaRouter.setOnPrepareTransferListener((fromRoute, toRoute) -> {
-            Log.d(TAG, "onPrepareTransfer: from=" + fromRoute.getId()
-                    + ", to=" + toRoute.getId());
-            return CallbackToFutureAdapter.getFuture(completer -> {
-                mHandler.postDelayed(() -> completer.set(null), 3000);
-                return "onPrepareTransfer";
-            });
-        });
+        mMediaRouter.setOnPrepareTransferListener(mOnPrepareTransferListener);
 
         // Add a fragment to take care of media route discovery.
         // This fragment automatically adds or removes a callback whenever the activity
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/LocalPlayer.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/LocalPlayer.java
index 468118b..ff82614 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/LocalPlayer.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/LocalPlayer.java
@@ -150,19 +150,22 @@
     }
 
     @Override
-    public void getStatus(@NonNull final PlaylistItem item, final boolean update) {
+    public void getStatus(@NonNull final PlaylistItem item, final boolean shouldUpdate) {
         if (mState == STATE_PLAYING || mState == STATE_PAUSED) {
-            // use mSeekToPos if we're currently seeking (mSeekToPos is reset
-            // when seeking is completed)
             item.setDuration(mMediaPlayer.getDuration());
-            item.setPosition(mSeekToPos > 0 ? mSeekToPos : mMediaPlayer.getCurrentPosition());
+            item.setPosition(getCurrentPosition());
             item.setTimestamp(SystemClock.elapsedRealtime());
         }
-        if (update && mCallback != null) {
+        if (shouldUpdate && mCallback != null) {
             mCallback.onPlaylistReady();
         }
     }
 
+    private int getCurrentPosition() {
+        // Use mSeekToPos if we're currently seeking (mSeekToPos is reset when seeking is completed)
+        return mSeekToPos > 0 ? mSeekToPos : mMediaPlayer.getCurrentPosition();
+    }
+
     @Override
     public void pause() {
         if (DEBUG) {
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
index cda63c3..3a5d154 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/Player.java
@@ -129,9 +129,9 @@
     /**
      * Get player status of an item.
      * @param item
-     * @param update
+     * @param shouldUpdate
      */
-    public abstract void getStatus(@NonNull PlaylistItem item, boolean update);
+    public abstract void getStatus(@NonNull PlaylistItem item, boolean shouldUpdate);
 
     /**
      * Player pause operation.
diff --git a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/RemotePlayer.java b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/RemotePlayer.java
index 2ffdedd..cc2ded3 100644
--- a/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/RemotePlayer.java
+++ b/samples/MediaRoutingDemo/src/main/java/com/example/androidx/mediarouting/player/RemotePlayer.java
@@ -161,7 +161,7 @@
     }
 
     @Override
-    public void getStatus(final @NonNull PlaylistItem item, final boolean update) {
+    public void getStatus(final @NonNull PlaylistItem item, final boolean shouldUpdate) {
         if (!mClient.hasSession() || item.getRemoteItemId() == null) {
             // if session is not valid or item id not assigend yet.
             // just return, it's not fatal
@@ -169,8 +169,12 @@
         }
 
         if (DEBUG) {
-            Log.d(TAG, "getStatus: item=" + item + ", update=" + update);
+            Log.d(TAG, "getStatus: item=" + item + ", shouldUpdate=" + shouldUpdate);
         }
+        updateStatus(item, shouldUpdate);
+    }
+
+    private void updateStatus(@NonNull PlaylistItem item, boolean shouldUpdate) {
         mClient.getStatus(item.getRemoteItemId(), null, new ItemActionCallback() {
             @Override
             public void onResult(@NonNull Bundle data, @NonNull String sessionId,
@@ -186,7 +190,7 @@
                     item.setDuration(itemStatus.getContentDuration());
                     item.setTimestamp(itemStatus.getTimestamp());
                 }
-                if (update && mCallback != null) {
+                if (shouldUpdate && mCallback != null) {
                     mCallback.onPlaylistReady();
                 }
             }
@@ -194,7 +198,7 @@
             @Override
             public void onError(String error, int code, Bundle data) {
                 logError("getStatus: failed", error, code);
-                if (update && mCallback != null) {
+                if (shouldUpdate && mCallback != null) {
                     mCallback.onPlaylistReady();
                 }
             }
diff --git a/savedstate/savedstate/build.gradle b/savedstate/savedstate/build.gradle
index 26d8317..e2be4cf 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("androidx.lifecycle:lifecycle-common:2.4.0")
+    implementation(project(":lifecycle:lifecycle-common"))
     api(libs.kotlinStdlib)
 
     androidTestImplementation("androidx.lifecycle:lifecycle-runtime:2.4.0")
diff --git a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
index 8389a1c..1d6389e 100644
--- a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
+++ b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/SavedStateRegistryTest.kt
@@ -232,7 +232,8 @@
     val lifecycleRegistry = LifecycleRegistry(this)
     val savedStateRegistryController = SavedStateRegistryController.create(this)
 
-    override fun getLifecycle() = lifecycleRegistry
+    override val lifecycle
+        get() = lifecycleRegistry
     override val savedStateRegistry: SavedStateRegistry
         get() = savedStateRegistryController.savedStateRegistry
 }
diff --git a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
index d176efc..42cd55e 100644
--- a/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
+++ b/savedstate/savedstate/src/androidTest/java/androidx/savedstate/ViewTreeSavedStateRegistryOwnerTest.kt
@@ -116,9 +116,8 @@
     }
 
     internal class FakeSavedStateRegistryOwner : SavedStateRegistryOwner {
-        override fun getLifecycle(): Lifecycle {
-            throw UnsupportedOperationException("not a real SavedStateRegistryOwner")
-        }
+        override val lifecycle: Lifecycle
+            get() = throw UnsupportedOperationException("not a real SavedStateRegistryOwner")
 
         override val savedStateRegistry: SavedStateRegistry
             get() = throw UnsupportedOperationException("not a real SavedStateRegistryOwner")
diff --git a/settings.gradle b/settings.gradle
index 2471fc2d..cc816be 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -743,7 +743,7 @@
 includeProject(":lifecycle:lifecycle-runtime-ktx", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE, BuildType.CAMERA])
 includeProject(":lifecycle:lifecycle-runtime-ktx-lint", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE, BuildType.CAMERA])
 includeProject(":lifecycle:lifecycle-runtime-testing", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE, BuildType.CAMERA])
-includeProject(":lifecycle:lifecycle-service", [BuildType.MAIN, BuildType.FLAN])
+includeProject(":lifecycle:lifecycle-service", [BuildType.MAIN, BuildType.FLAN, BuildType.GLANCE])
 includeProject(":lifecycle:lifecycle-viewmodel", [BuildType.MAIN, BuildType.FLAN, BuildType.WEAR, BuildType.COMPOSE, BuildType.CAMERA])
 includeProject(":lifecycle:lifecycle-viewmodel-compose", [BuildType.COMPOSE])
 includeProject(":lifecycle:lifecycle-viewmodel-compose:lifecycle-viewmodel-compose-samples", "lifecycle/lifecycle-viewmodel-compose/samples", [BuildType.COMPOSE])
@@ -1040,7 +1040,7 @@
 includeProject(":internal-testutils-appcompat", "testutils/testutils-appcompat", [BuildType.MAIN])
 includeProject(":internal-testutils-espresso", "testutils/testutils-espresso", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":internal-testutils-fonts", "testutils/testutils-fonts", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR])
-includeProject(":internal-testutils-truth", "testutils/testutils-truth", [BuildType.MAIN, BuildType.GLANCE, BuildType.MEDIA, BuildType.FLAN, BuildType.COMPOSE, BuildType.WEAR, BuildType.KMP, BuildType.CAMERA])
+includeProject(":internal-testutils-truth", "testutils/testutils-truth")
 includeProject(":internal-testutils-ktx", "testutils/testutils-ktx")
 includeProject(":internal-testutils-kmp", "testutils/testutils-kmp", [BuildType.MAIN, BuildType.KMP])
 includeProject(":internal-testutils-macrobenchmark", "testutils/testutils-macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
index 05186fc..58dab98 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiDeviceTest.java
@@ -38,6 +38,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -245,6 +246,7 @@
         assertEquals("keycode Z pressed with meta shift left on", textView.getText());
     }
 
+    @Ignore // b/266617096
     @Test
     public void testPressRecentApps() throws Exception {
         launchTestActivity(MainActivity.class);
@@ -299,6 +301,7 @@
         assertEquals("I've been clicked!", button.getText());
     }
 
+    @Ignore // b/266617096
     @Test
     public void testSwipe() {
         launchTestActivity(SwipeTestActivity.class);
@@ -328,6 +331,7 @@
         assertTrue(dragDestination.wait(Until.textEquals("drag_received"), TIMEOUT_MS));
     }
 
+    @Ignore // b/266617096
     @Test
     public void testSwipe_withPointArray() {
         launchTestActivity(SwipeTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
index d34b1b3..bdcf9c0 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObject2Test.java
@@ -35,6 +35,7 @@
 import androidx.test.uiautomator.UiObject2;
 import androidx.test.uiautomator.Until;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 import java.util.HashSet;
@@ -155,6 +156,7 @@
                 TIMEOUT_MS));
     }
 
+    @Ignore // b/266617335
     @Test
     @SdkSuppress(minSdkVersion = 24)
     public void testDrag_dest() {
@@ -518,6 +520,7 @@
                 + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch < 1f);
     }
 
+    @Ignore // b/266617335
     @Test
     public void testPinchOpen() {
         launchTestActivity(PinchTestActivity.class);
@@ -544,6 +547,7 @@
                 + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch > 1f);
     }
 
+    @Ignore // b/266617335
     @Test
     public void testSwipe() {
         launchTestActivity(SwipeTestActivity.class);
@@ -624,6 +628,7 @@
         assertTrue(mDevice.hasObject(By.res(TEST_APP, "bottom_text")));
     }
 
+    @Ignore // b/266617335
     @Test
     public void testFling_direction() {
         launchTestActivity(FlingTestActivity.class);
@@ -674,6 +679,7 @@
                 () -> flingRegion.fling(Direction.DOWN, speed));
     }
 
+    @Ignore // b/267208902
     @Test
     public void testSetGestureMargin() {
         launchTestActivity(PinchTestActivity.class);
@@ -704,6 +710,7 @@
                 + "but got [%f]", scaleValueAfterPinch), scaleValueAfterPinch < 1f);
     }
 
+    @Ignore // b/266617335
     @Test
     public void testSetGestureMargins() {
         launchTestActivity(PinchTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
index 175b865..d186654 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiObjectTest.java
@@ -29,6 +29,7 @@
 import androidx.test.uiautomator.UiObject;
 import androidx.test.uiautomator.UiSelector;
 
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class UiObjectTest extends BaseTest {
@@ -118,6 +119,7 @@
         assertTrue(expectedDragDest.waitForExists(TIMEOUT_MS));
     }
 
+    @Ignore // b/266617747
     @Test
     public void testSwipeUp() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -139,6 +141,7 @@
         assertTrue(expectedSwipeRegion.waitForExists(TIMEOUT_MS));
     }
 
+    @Ignore // b/266617747
     @Test
     public void testSwipeDown() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -178,6 +181,7 @@
         assertTrue(expectedSwipeRegion.waitForExists(TIMEOUT_MS));
     }
 
+    @Ignore // b/266617747
     @Test
     public void testSwipeRight() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
diff --git a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
index 963f28b..0d7208d 100644
--- a/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
+++ b/test/uiautomator/integration-tests/testapp/src/androidTest/java/androidx/test/uiautomator/testapp/UiScrollableTest.java
@@ -28,6 +28,7 @@
 
 import org.junit.After;
 import org.junit.Before;
+import org.junit.Ignore;
 import org.junit.Test;
 
 public class UiScrollableTest extends BaseTest {
@@ -52,6 +53,7 @@
                 mDefaultSwipeDeadZonePercentage);
     }
 
+    @Ignore // b/266965027
     @Test
     public void testGetChildByDescription() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -69,6 +71,7 @@
                         "This is non-existent"));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testGetChildByDescription_withoutScrollSearch() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -86,6 +89,7 @@
                         false));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testGetChildByInstance() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -102,6 +106,7 @@
                 1).exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testGetChildByText() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -118,6 +123,7 @@
                         "This is non-existent"));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testGetChildByText_withoutScrollSearch() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -134,6 +140,7 @@
                         "This is the bottom", false));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollDescriptionIntoView() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -149,6 +156,7 @@
         assertFalse(relativeLayout.scrollDescriptionIntoView("This is non-existent"));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollIntoView_withUiObject() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -166,6 +174,7 @@
         assertFalse(relativeLayout.scrollIntoView(nonExistentTarget));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollIntoView_withUiSelector() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -181,6 +190,7 @@
         assertFalse(relativeLayout.scrollIntoView(nonExistentTarget));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testEnsureFullyVisible() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -197,6 +207,7 @@
                         mDevice.findObject(new UiSelector().resourceId(TEST_APP + ":id/no_node"))));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollTextIntoView() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -212,12 +223,14 @@
         assertFalse(relativeLayout.scrollTextIntoView("This is non-existent"));
     }
 
+    @Ignore // b/266965027
     @Test
     public void testSetMaxSearchSwipesAndGetMaxSearchSwipes() {
         UiScrollable scrollable = new UiScrollable(new UiSelector()).setMaxSearchSwipes(5);
         assertEquals(5, scrollable.getMaxSearchSwipes());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testFlingForward() throws Exception {
         launchTestActivity(FlingTestActivity.class);
@@ -235,6 +248,7 @@
         assertUiObjectNotFound(noNode::flingForward);
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollForward_vertical() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -246,6 +260,7 @@
         assertEquals("swipe_up", scrollRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollForward_horizontal() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -257,6 +272,7 @@
         assertEquals("swipe_left", scrollRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testFlingBackward() throws Exception {
         launchTestActivity(FlingTestActivity.class);
@@ -272,6 +288,7 @@
         assertUiObjectNotFound(noNode::flingBackward);
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollBackward_vertical() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -283,6 +300,7 @@
         assertEquals("swipe_down", scrollRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollBackward_horizontal() throws Exception {
         launchTestActivity(SwipeTestActivity.class);
@@ -294,6 +312,7 @@
         assertEquals("swipe_right", scrollRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToBeginning_withSteps() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -309,6 +328,7 @@
         assertTrue(topText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToBeginning_notEnoughSwipes_failed() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -324,6 +344,7 @@
         assertFalse(topText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToBeginning() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -339,6 +360,7 @@
         assertTrue(topText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testFlingToBeginning() throws Exception {
         launchTestActivity(FlingTestActivity.class);
@@ -350,6 +372,7 @@
         assertEquals("fling_up", flingRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToEnd_withSteps() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -364,6 +387,7 @@
         assertTrue(bottomText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToEnd_notEnoughSwipes_failed() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -378,6 +402,7 @@
         assertFalse(bottomText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testScrollToEnd() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
@@ -392,6 +417,7 @@
         assertTrue(bottomText.exists());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testFlingToEnd() throws Exception {
         launchTestActivity(FlingTestActivity.class);
@@ -403,6 +429,7 @@
         assertEquals("fling_down", flingRegion.getText());
     }
 
+    @Ignore // b/266965027
     @Test
     public void testSetSwipeDeadZonePercentageAndGetSwipeDeadZonePercentage() {
         UiScrollable scrollable =
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
index b082a80..6b7931d 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/Configurator.java
@@ -35,9 +35,8 @@
     private long mWaitForSelector = 10 * 1000;
     private long mWaitForActionAcknowledgment = 3 * 1000;
 
-    // The events for a scroll typically complete even before touchUp occurs.
-    // This short timeout to make sure we get the very last in cases where the above isn't true.
-    private long mScrollEventWaitTimeout = 200; // ms
+    // Scroll timeout used only in InteractionController
+    private long mScrollEventWaitTimeout = 1_000; // ms
 
     // Default is inject as fast as we can
     private long mKeyInjectionDelay = 0; // ms
diff --git a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
index 271926c..3d6eee1 100644
--- a/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
+++ b/test/uiautomator/uiautomator/src/main/java/androidx/test/uiautomator/InteractionController.java
@@ -36,8 +36,6 @@
 import android.view.ViewConfiguration;
 import android.view.accessibility.AccessibilityEvent;
 
-import java.util.ArrayList;
-import java.util.List;
 import java.util.concurrent.TimeoutException;
 
 /**
@@ -84,34 +82,6 @@
     }
 
     /**
-     * Predicate for waiting for all the events specified in the mask and populating
-     * a ctor passed list with matching events. User of this predicate must recycle
-     * all populated events in the events list.
-     */
-    static class EventCollectingPredicate implements AccessibilityEventFilter {
-        final int mMask;
-        final List<AccessibilityEvent> mEventsList;
-
-        EventCollectingPredicate(int mask, List<AccessibilityEvent> events) {
-            mMask = mask;
-            mEventsList = events;
-        }
-
-        @Override
-        public boolean accept(AccessibilityEvent t) {
-            // check current event in the list
-            if ((t.getEventType() & mMask) != 0) {
-                // For the events you need, always store a copy when returning false from
-                // predicates since the original will automatically be recycled after the call.
-                mEventsList.add(AccessibilityEvent.obtain(t));
-            }
-
-            // get more
-            return false;
-        }
-    }
-
-    /**
      * Predicate for waiting for every event specified in the mask to be matched at least once
      */
     static class WaitForAllEventPredicate implements AccessibilityEventFilter {
@@ -334,56 +304,21 @@
             final int steps) {
         Runnable command = () -> swipe(downX, downY, upX, upY, steps);
 
-        // Collect all accessibility events generated during the swipe command and get the
-        // last event
-        ArrayList<AccessibilityEvent> events = new ArrayList<>();
+        // Get scroll direction based on position.
+        Direction direction;
+        if (Math.abs(downX - upX) > Math.abs(downY - upY)) {
+            // Horizontal.
+            direction = downX > upX ? Direction.RIGHT : Direction.LEFT;
+        } else {
+            // Vertical.
+            direction = downY > upY ? Direction.DOWN : Direction.UP;
+        }
+        EventCondition<Boolean> condition = Until.scrollFinished(direction);
         runAndWaitForEvents(command,
-                new EventCollectingPredicate(AccessibilityEvent.TYPE_VIEW_SCROLLED, events),
+                condition,
                 Configurator.getInstance().getScrollAcknowledgmentTimeout());
 
-        AccessibilityEvent event = getLastMatchingEvent(events,
-                AccessibilityEvent.TYPE_VIEW_SCROLLED);
-
-        if (event == null) {
-            // end of scroll since no new scroll events received
-            recycleAccessibilityEvents(events);
-            return false;
-        }
-
-        // AdapterViews have indices we can use to check for the beginning.
-        boolean foundEnd = false;
-        if (event.getFromIndex() != -1 && event.getToIndex() != -1 && event.getItemCount() != -1) {
-            foundEnd = event.getFromIndex() == 0 ||
-                    (event.getItemCount() - 1) == event.getToIndex();
-        } else if (event.getScrollX() != -1 && event.getScrollY() != -1) {
-            // Determine if we are scrolling vertically or horizontally.
-            if (downX == upX) {
-                // Vertical
-                foundEnd = event.getScrollY() == 0 ||
-                        event.getScrollY() == event.getMaxScrollY();
-            } else if (downY == upY) {
-                // Horizontal
-                foundEnd = event.getScrollX() == 0 ||
-                        event.getScrollX() == event.getMaxScrollX();
-            }
-        }
-        recycleAccessibilityEvents(events);
-        return !foundEnd;
-    }
-
-    private AccessibilityEvent getLastMatchingEvent(List<AccessibilityEvent> events, int type) {
-        for (int x = events.size(); x > 0; x--) {
-            AccessibilityEvent event = events.get(x - 1);
-            if (event.getEventType() == type)
-                return event;
-        }
-        return null;
-    }
-
-    private void recycleAccessibilityEvents(List<AccessibilityEvent> events) {
-        for (AccessibilityEvent event : events)
-            event.recycle();
-        events.clear();
+        return !condition.getResult();
     }
 
     /**
diff --git a/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
index 2bad306..b255881 100644
--- a/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
+++ b/testutils/testutils-kmp/src/commonMain/kotlin/androidx/kruth/IterableSubject.kt
@@ -226,10 +226,26 @@
      * on the object returned by this method. The expected elements must appear in the given order
      * within the actual elements, but they are not required to be consecutive.
      */
-    fun containsAtLeastElementsIn(expected: Array<Any?>?): Ordered =
+    fun containsAtLeastElementsIn(expected: Array<out Any?>?): Ordered =
         containsAtLeastElementsIn(expected?.asList())
 
     /**
+     * Checks that a subject contains exactly the provided objects or fails.
+     *
+     * Multiplicity is respected. For example, an object duplicated exactly 3 times in the
+     * parameters asserts that the object must likewise be duplicated exactly 3 times in the subject.
+     *
+     * To also test that the contents appear in the given order, make a call to [Ordered.inOrder]
+     * on the object returned by this method.
+     *
+     * To test that the iterable contains the same elements as an array, prefer
+     * [containsExactlyElementsIn]. It makes clear that the given array is a list of elements, not
+     * an element itself. This helps human readers and avoids a compiler warning.
+     */
+    fun containsExactly(vararg expected: Any?): Ordered =
+        containsExactlyElementsIn(expected.asList())
+
+    /**
      * Checks that the subject contains exactly the provided objects or fails.
      *
      * Multiplicity is respected. For example, an object duplicated exactly 3 times in the
@@ -365,4 +381,142 @@
         // order, so inOrder() can just succeed.
         return NoopOrdered
     }
+
+    /**
+     * Checks that a subject contains exactly the [expected] objects or fails.
+     *
+     *
+     * Multiplicity is respected. For example, an object duplicated exactly 3 times in the array
+     * parameter asserts that the object must likewise be duplicated exactly 3 times in the subject.
+     *
+     *
+     * To also test that the contents appear in the given order, make a call to `inOrder()`
+     * on the object returned by this method.
+     */
+    fun containsExactlyElementsIn(expected: Array<out Any?>?): Ordered =
+        containsExactlyElementsIn(expected?.asList())
+
+    /**
+     * Checks that a actual iterable contains none of the excluded objects or fails. (Duplicates are
+     * irrelevant to this test, which fails if any of the actual elements equal any of the excluded)
+     */
+    fun containsNoneOf(
+        firstExcluded: Any?,
+        secondExcluded: Any?,
+        vararg restOfExcluded: Any?,
+    ) {
+        containsNoneIn(listOf(firstExcluded, secondExcluded, *restOfExcluded))
+    }
+
+    /**
+     * Checks that the actual iterable contains none of the elements contained in the [excluded]
+     * iterable or fails. (Duplicates are irrelevant to this test, which fails if any of the actual
+     * elements equal any of the excluded)
+     */
+    fun containsNoneIn(excluded: Iterable<*>?) {
+        requireNonNull(excluded)
+        val actual = requireNonNull(actual).toSet()
+        val present = excluded.intersect(actual)
+
+        if (present.isNotEmpty()) {
+            fail(
+                """
+                    Expected not to contain any of $excluded but contained $present.
+                    Actual: $actual.
+                """.trimIndent()
+            )
+        }
+    }
+
+    /**
+     * Checks that the actual iterable contains none of the elements contained in the [excluded]
+     * array or fails. (Duplicates are irrelevant to this test, which fails if any of the actual
+     * elements equal any of the excluded)
+     */
+    fun containsNoneIn(excluded: Array<Any?>?) {
+        containsNoneIn(excluded?.asList())
+    }
+
+    /**
+     * Fails if the iterable is not strictly ordered, according to the natural ordering of its
+     * elements. Strictly ordered means that each element in the iterable is <i>strictly</i> greater
+     * than the element that preceded it.
+     *
+     * @throws ClassCastException if any pair of elements is not mutually Comparable
+     * @throws NullPointerException if any element is null
+     */
+    fun isInStrictOrder() {
+        isInStrictOrder(compareBy<Comparable<Any>> { it })
+    }
+
+    /**
+     * Fails if the iterable is not strictly ordered, according to the given [comparator]. Strictly
+     * ordered means that each element in the iterable is *strictly* greater than the element
+     * that preceded it.
+     *
+     * Note: star-projection in `Comparator<*>` is for compatibility with Truth.
+     *
+     * @throws ClassCastException if any pair of elements is not mutually Comparable
+     */
+    fun isInStrictOrder(comparator: Comparator<*>?) {
+        @Suppress("UNCHECKED_CAST")
+        val cmp = requireNonNull(comparator) as Comparator<in Any?>
+
+        verifyInOrder(
+            predicate = { a, b -> cmp.compare(a, b) < 0 },
+            message = { a, b ->
+                """
+                    Expected to be in strict order but contained $a followed by $b.
+                    Actual: $actual.
+                """.trimIndent()
+            }
+        )
+    }
+
+    /**
+     * Fails if the iterable is not ordered, according to the natural ordering of its elements.
+     * Ordered means that each element in the iterable is greater than or equal to the element that
+     * preceded it.
+     *
+     * @throws ClassCastException if any pair of elements is not mutually Comparable
+     * @throws NullPointerException if any element is null
+     */
+    fun isInOrder() {
+        isInOrder(compareBy<Comparable<Any>> { it })
+    }
+
+    /**
+     * Fails if the iterable is not ordered, according to the given [comparator]. Ordered means that
+     * each element in the iterable is greater than or equal to the element that preceded it.
+     *
+     * @throws ClassCastException if any pair of elements is not mutually Comparable
+     */
+    fun isInOrder(comparator: Comparator<*>?) {
+        @Suppress("UNCHECKED_CAST")
+        val cmp = requireNonNull(comparator) as Comparator<in Any?>
+
+        verifyInOrder(
+            predicate = { a, b -> cmp.compare(a, b) <= 0 },
+            message = { a, b ->
+                """
+                    Expected to be in order but contained $a followed by $b.
+                    Actual: $actual.
+                """.trimIndent()
+            }
+        )
+    }
+
+    private inline fun verifyInOrder(
+        predicate: (a: Any?, b: Any?) -> Boolean,
+        message: (a: Any?, b: Any?) -> String,
+    ) {
+        requireNonNull(actual)
+            .asSequence()
+            .zipWithNext(::Pair)
+            .forEach { (a, b) ->
+                if (!predicate(a, b)) {
+                    fail(message(a, b))
+                }
+            }
+    }
 }
diff --git a/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt b/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
index ba97a52..e08ecda 100644
--- a/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
+++ b/testutils/testutils-kmp/src/commonTest/kotlin/androidx/kruth/IterableSubjectTest.kt
@@ -418,125 +418,123 @@
         }
     }
 
-//    @Test
-//    fun iterableContainsNoneOf() {
-//        assertThat(listOf(1, 2, 3)).containsNoneOf(4, 5, 6)
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneOfFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsNoneOf(1, 2, 4)
-//        assertFailureKeys("expected not to contain any of", "but contained", "full contents")
-//        assertFailureValue("expected not to contain any of", "[1, 2, 4]")
-//        assertFailureValue("but contained", "[1, 2]")
-//        assertFailureValue("full contents", "[1, 2, 3]")
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneOfFailureWithDuplicateInSubject() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 2, 3)).containsNoneOf(1, 2, 4)
-//        assertFailureValue("but contained", "[1, 2]")
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneOfFailureWithDuplicateInExpected() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsNoneOf(1, 2, 2, 4)
-//        assertFailureValue("but contained", "[1, 2]")
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneOfFailureWithEmptyString() {
-//        expectFailureWhenTestingThat(listOf("")).containsNoneOf("", null)
-//        assertFailureKeys("expected not to contain any of", "but contained", "full contents")
-//        assertFailureValue("expected not to contain any of", "[\"\" (empty String), null]")
-//        assertFailureValue("but contained", "[\"\" (empty String)]")
-//        assertFailureValue("full contents", "[]")
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneInIterable() {
-//        assertThat(listOf(1, 2, 3)).containsNoneIn(listOf(4, 5, 6))
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsNoneIn(listOf(1, 2, 4))
-//        assertFailureKeys("expected not to contain any of", "but contained", "full contents")
-//        assertFailureValue("expected not to contain any of", "[1, 2, 4]")
-//        assertFailureValue("but contained", "[1, 2]")
-//        assertFailureValue("full contents", "[1, 2, 3]")
-//    }
-//
-//    @Test
-//    fun iterableContainsNoneInArray() {
-//        assertThat(listOf(1, 2, 3)).containsNoneIn(arrayOf(4, 5, 6))
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsNoneIn(arrayOf(1, 2, 4))
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyArray() {
-//        val stringArray = arrayOf("a", "b")
-//        val iterable: ImmutableList<Array<String>> = listOf(stringArray)
-//        // This test fails w/o the explicit cast
-//        assertThat(iterable).containsExactly(stringArray as Any)
-//    }
-//
-//    @Test
-//    fun arrayContainsExactly() {
-//        val iterable: ImmutableList<String> = listOf("a", "b")
-//        val array = arrayOf("a", "b")
-//        assertThat(iterable).containsExactly(array as Array<Any>)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithMany() {
-//        assertThat(listOf(1, 2, 3)).containsExactly(1, 2, 3)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyOutOfOrder() {
-//        assertThat(listOf(1, 2, 3, 4)).containsExactly(3, 1, 4, 2)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicates() {
-//        assertThat(listOf(1, 2, 2, 2, 3)).containsExactly(1, 2, 2, 2, 3)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesOutOfOrder() {
-//        assertThat(listOf(1, 2, 2, 2, 3)).containsExactly(2, 1, 2, 3, 2)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithOnlyNullPassedAsNullArray() {
-//        // Truth is tolerant of this erroneous varargs call.
-//        val actual: Iterable<Any> = listOf(null as Any?)
-//        assertThat(actual).containsExactly(null as Array<Any?>?)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithOnlyNull() {
-//        val actual: Iterable<Any> = listOf(null as Any?)
-//        assertThat(actual).containsExactly(null as Any?)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithNullSecond() {
-//        assertThat(listOf(1, null)).containsExactly(1, null)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithNullThird() {
-//        assertThat(listOf(1, 2, null)).containsExactly(1, 2, null)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithNull() {
-//        assertThat(listOf(1, null, 3)).containsExactly(1, null, 3)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithNullOutOfOrder() {
-//        assertThat(listOf(1, null, 3)).containsExactly(1, 3, null as Int?)
-//    }
-//
+    @Test
+    fun iterableContainsNoneOf() {
+        assertThat(listOf(1, 2, 3)).containsNoneOf(4, 5, 6)
+    }
+
+    @Test
+    fun iterableContainsNoneOfFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsNoneOf(1, 2, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsNoneOfFailureWithDuplicateInSubject() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 2, 3)).containsNoneOf(1, 2, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsNoneOfFailureWithDuplicateInExpected() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsNoneOf(1, 2, 2, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsNoneOfFailureWithEmptyString() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("")).containsNoneOf("", null)
+        }
+    }
+
+    @Test
+    fun iterableContainsNoneInIterable() {
+        assertThat(listOf(1, 2, 3)).containsNoneIn(listOf(4, 5, 6))
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsNoneIn(listOf(1, 2, 4))
+        }
+    }
+
+    @Test
+    fun iterableContainsNoneInArray() {
+        assertThat(listOf(1, 2, 3)).containsNoneIn(arrayOf(4, 5, 6))
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsNoneIn(arrayOf(1, 2, 4))
+        }
+    }
+
+        @Test
+    fun iterableContainsExactlyArray() {
+        val stringArray = arrayOf("a", "b")
+        val iterable = listOf(stringArray)
+        // This test fails w/o the explicit cast
+        assertThat(iterable).containsExactly(stringArray as Any)
+    }
+
+    @Test
+    fun arrayContainsExactly() {
+        val iterable = listOf("a", "b")
+        val array = arrayOf("a", "b")
+        assertThat(iterable).containsExactly(*array)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithMany() {
+        assertThat(listOf(1, 2, 3)).containsExactly(1, 2, 3)
+    }
+
+    @Test
+    fun iterableContainsExactlyOutOfOrder() {
+        assertThat(listOf(1, 2, 3, 4)).containsExactly(3, 1, 4, 2)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicates() {
+        assertThat(listOf(1, 2, 2, 2, 3)).containsExactly(1, 2, 2, 2, 3)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesOutOfOrder() {
+        assertThat(listOf(1, 2, 2, 2, 3)).containsExactly(2, 1, 2, 3, 2)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithOnlyNullPassedAsNullArray() {
+        // Truth is tolerant of this erroneous varargs call.
+        val actual = listOf(null as Any?)
+        assertThat(actual).containsExactly(null as Array<Any?>?)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithOnlyNull() {
+        val actual = listOf(null as Any?)
+        assertThat(actual).containsExactly(null as Any?)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithNullSecond() {
+        assertThat(listOf(1, null)).containsExactly(1, null)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithNullThird() {
+        assertThat(listOf(1, 2, null)).containsExactly(1, 2, null)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithNull() {
+        assertThat(listOf(1, null, 3)).containsExactly(1, null, 3)
+    }
+
+    @Test
+    fun iterableContainsExactlyWithNullOutOfOrder() {
+        assertThat(listOf(1, null, 3)).containsExactly(1, 3, null as Int?)
+    }
+
     @Test
     fun iterableContainsExactlyOutOfOrderDoesNotStringify() {
         val o = CountsToStringCalls()
@@ -552,90 +550,77 @@
         assertTrue(o.calls > 0)
     }
 
-//    @Test
-//    fun iterableContainsExactlyWithEmptyString() {
-//        expectFailureWhenTestingThat(listOf()).containsExactly("")
-//        assertFailureValue("missing (1)", "")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithEmptyStringAndUnexpectedItem() {
-//        expectFailureWhenTestingThat(listOf("a", null)).containsExactly("")
-//        assertFailureKeys("missing (1)", "unexpected (2)", "---", "expected", "but was")
-//        assertFailureValue("missing (1)", "")
-//        assertFailureValue("unexpected (2)", "a, null")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithEmptyStringAndMissingItem() {
-//        expectFailureWhenTestingThat(listOf("")).containsExactly("a", null)
-//        assertFailureValue("missing (2)", "a, null")
-//        assertFailureValue("unexpected (1)", "")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithEmptyStringAmongMissingItems() {
-//        expectFailureWhenTestingThat(listOf("a")).containsExactly("", "b")
-//        assertFailureKeys(
-//            "missing (2)", "#1", "#2", "", "unexpected (1)", "#1", "---", "expected", "but was"
-//        )
-//        assertFailureValueIndexed("#1", 0, "")
-//        assertFailureValueIndexed("#2", 0, "b")
-//        assertFailureValueIndexed("#1", 1, "a")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlySingleElement() {
-//        assertThat(listOf(1)).containsExactly(1)
-//        expectFailureWhenTestingThat(listOf(1)).containsExactly(2)
-//        assertFailureKeys("value of", "expected", "but was")
-//        assertFailureValue("value of", "iterable.onlyElement()")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlySingleElementNoEqualsMagic() {
-//        expectFailureWhenTestingThat(listOf(1)).containsExactly(1L)
-//        assertFailureValueIndexed("an instance of", 0, "java.lang.Long")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCode() {
-//        val one = HashCodeThrower()
-//        val two = HashCodeThrower()
-//        assertThat(listOf(one, two)).containsExactly(two, one)
-//        assertThat(listOf(one, two)).containsExactly(one, two).inOrder()
-//        assertThat(listOf(one, two)).containsExactlyElementsIn(listOf(two, one))
-//        assertThat(listOf(one, two)).containsExactlyElementsIn(listOf(one, two)).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeFailureTooMany() {
-//        val one = HashCodeThrower()
-//        val two = HashCodeThrower()
-//        expectFailureWhenTestingThat(listOf(one, two)).containsExactly(one)
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeOneMismatch() {
-//        val one = HashCodeThrower()
-//        val two = HashCodeThrower()
-//        expectFailureWhenTestingThat(listOf(one, one)).containsExactly(one, two)
-//    }
-//
-//    private class HashCodeThrower() {
-//        override fun equals(other: Any?): Boolean {
-//            return this === other
-//        }
-//
-//        override fun hashCode(): Int {
-//            throw java.lang.UnsupportedOperationException()
-//        }
-//
-//        override fun toString(): String {
-//            return "HCT"
-//        }
-//    }
-//
+    @Test
+    fun iterableContainsExactlyWithEmptyString() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf<Any?>()).containsExactly("")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithEmptyStringAndUnexpectedItem() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a", null)).containsExactly("")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithEmptyStringAndMissingItem() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("")).containsExactly("a", null)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithEmptyStringAmongMissingItems() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a")).containsExactly("", "b")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlySingleElement() {
+        assertThat(listOf(1)).containsExactly(1)
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1)).containsExactly(2)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlySingleElementNoEqualsMagic() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1)).containsExactly(1L)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCode() {
+        val one = HashCodeThrower()
+        val two = HashCodeThrower()
+        assertThat(listOf(one, two)).containsExactly(two, one)
+        assertThat(listOf(one, two)).containsExactly(one, two).inOrder()
+        assertThat(listOf(one, two)).containsExactlyElementsIn(listOf(two, one))
+        assertThat(listOf(one, two)).containsExactlyElementsIn(listOf(one, two)).inOrder()
+    }
+
+    @Test
+    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeFailureTooMany() {
+        val one = HashCodeThrower()
+        val two = HashCodeThrower()
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(one, two)).containsExactly(one)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithElementsThatThrowWhenYouCallHashCodeOneMismatch() {
+        val one = HashCodeThrower()
+        val two = HashCodeThrower()
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(one, one)).containsExactly(one, two)
+        }
+    }
+
     @Test
     fun iterableContainsExactlyElementsInInOrderPassesWithEmptyExpectedAndActual() {
         assertThat(emptyList<Any>()).containsExactlyElementsIn(emptyList<Any>()).inOrder()
@@ -655,152 +640,137 @@
         }
     }
 
-//    @Test
-//    fun iterableContainsExactlyMissingItemFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2)).containsExactly(1, 2, 4)
-//        assertFailureValue("missing (1)", "4")
-//    }
+    @Test
+    fun iterableContainsExactlyMissingItemFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2)).containsExactly(1, 2, 4)
+        }
+    }
 
-//    @Test
-//    fun iterableContainsExactlyUnexpectedItemFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsExactly(1, 2)
-//        assertFailureValue("unexpected (1)", "3")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesNotEnoughItemsFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsExactly(1, 2, 2, 2, 3)
-//        assertFailureValue("missing (2)", "2 [2 copies]")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesMissingItemFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsExactly(1, 2, 2, 2, 3, 4)
-//        assertFailureValue("missing (3)", "2 [2 copies], 4")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesMissingItemsWithNewlineFailure() {
-//        expectFailureWhenTestingThat(listOf("a", "b", "foo\nbar"))
-//            .containsExactly("a", "b", "foo\nbar", "foo\nbar", "foo\nbar")
-//        assertFailureKeys("missing (2)", "#1 [2 copies]", "---", "expected", "but was")
-//        assertFailureValue("#1 [2 copies]", "foo\nbar")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesMissingAndExtraItemsWithNewlineFailure() {
-//        expectFailureWhenTestingThat(listOf("a\nb", "a\nb")).containsExactly("foo\nbar", "foo\nbar")
-//        assertFailureKeys(
-//            "missing (2)",
-//            "#1 [2 copies]",
-//            "",
-//            "unexpected (2)",
-//            "#1 [2 copies]",
-//            "---",
-//            "expected",
-//            "but was"
-//        )
-//        assertFailureValueIndexed("#1 [2 copies]", 0, "foo\nbar")
-//        assertFailureValueIndexed("#1 [2 copies]", 1, "a\nb")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicatesUnexpectedItemFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 2, 2, 2, 3)).containsExactly(1, 2, 2, 3)
-//        assertFailureValue("unexpected (2)", "2 [2 copies]")
-//    }
-//
-//    /*
-//   * Slightly subtle test to ensure that if multiple equal elements are found
-//   * to be missing we only reference it once in the output message.
-//   */
-//    @Test
-//    fun iterableContainsExactlyWithDuplicateMissingElements() {
-//        expectFailureWhenTestingThat(listOf()).containsExactly(4, 4, 4)
-//        assertFailureValue("missing (3)", "4 [3 copies]")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithNullFailure() {
-//        expectFailureWhenTestingThat(listOf(1, null, 3)).containsExactly(1, null, null, 3)
-//        assertFailureValue("missing (1)", "null")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithMissingAndExtraElements() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3)).containsExactly(1, 2, 4)
-//        assertFailureValue("missing (1)", "4")
-//        assertFailureValue("unexpected (1)", "3")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithDuplicateMissingAndExtraElements() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3, 3)).containsExactly(1, 2, 4, 4)
-//        assertFailureValue("missing (2)", "4 [2 copies]")
-//        assertFailureValue("unexpected (2)", "3 [2 copies]")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithCommaSeparatedVsIndividual() {
-//        expectFailureWhenTestingThat(listOf("a, b")).containsExactly("a", "b")
-//        assertFailureKeys(
-//            "missing (2)", "#1", "#2", "", "unexpected (1)", "#1", "---", "expected", "but was"
-//        )
-//        assertFailureValueIndexed("#1", 0, "a")
-//        assertFailureValueIndexed("#2", 0, "b")
-//        assertFailureValueIndexed("#1", 1, "a, b")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyFailsWithSameToStringAndHomogeneousList() {
-//        expectFailureWhenTestingThat(listOf(1L, 2L)).containsExactly(1, 2)
-//        assertFailureValue("missing (2)", "1, 2 (java.lang.Integer)")
-//        assertFailureValue("unexpected (2)", "1, 2 (java.lang.Long)")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyFailsWithSameToStringAndListWithNull() {
-//        expectFailureWhenTestingThat(listOf(1L, 2L)).containsExactly(null, 1, 2)
-//        assertFailureValue(
-//            "missing (3)", "null (null type), 1 (java.lang.Integer), 2 (java.lang.Integer)"
-//        )
-//        assertFailureValue("unexpected (2)", "1, 2 (java.lang.Long)")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyFailsWithSameToStringAndHeterogeneousList() {
-//        expectFailureWhenTestingThat(listOf(1L, 2)).containsExactly(1, null, 2L)
-//        assertFailureValue(
-//            "missing (3)", "1 (java.lang.Integer), null (null type), 2 (java.lang.Long)"
-//        )
-//        assertFailureValue("unexpected (2)", "1 (java.lang.Long), 2 (java.lang.Integer)")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyFailsWithSameToStringAndHomogeneousListWithDuplicates() {
-//        expectFailureWhenTestingThat(listOf(1L, 2L)).containsExactly(1, 2, 2)
-//        assertFailureValue("missing (3)", "1, 2 [2 copies] (java.lang.Integer)")
-//        assertFailureValue("unexpected (2)", "1, 2 (java.lang.Long)")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyFailsWithSameToStringAndHeterogeneousListWithDuplicates() {
-//        expectFailureWhenTestingThat(listOf(1L, 2)).containsExactly(1, null, null, 2L, 2L)
-//        assertFailureValue(
-//            "missing (5)",
-//            "1 (java.lang.Integer), null (null type) [2 copies], 2 (java.lang.Long) [2 copies]"
-//        )
-//        assertFailureValue("unexpected (2)", "1 (java.lang.Long), 2 (java.lang.Integer)")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyWithOneIterableGivesWarning() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3, 4)).containsExactly(listOf(1, 2, 3, 4))
-//        assertThat(expectFailure.getFailure())
-//            .hasMessageThat()
-//            .contains(CONTAINS_EXACTLY_ITERABLE_WARNING)
-//    }
-//
+    @Test
+    fun iterableContainsExactlyUnexpectedItemFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsExactly(1, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesNotEnoughItemsFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsExactly(1, 2, 2, 2, 3)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesMissingItemFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsExactly(1, 2, 2, 2, 3, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesMissingItemsWithNewlineFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a", "b", "foo\nbar"))
+                .containsExactly("a", "b", "foo\nbar", "foo\nbar", "foo\nbar")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesMissingAndExtraItemsWithNewlineFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a\nb", "a\nb")).containsExactly("foo\nbar", "foo\nbar")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicatesUnexpectedItemFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 2, 2, 2, 3)).containsExactly(1, 2, 2, 3)
+        }
+    }
+
+    /*
+   * Slightly subtle test to ensure that if multiple equal elements are found
+   * to be missing we only reference it once in the output message.
+   */
+    @Test
+    fun iterableContainsExactlyWithDuplicateMissingElements() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf<Any?>()).containsExactly(4, 4, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithNullFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, null, 3)).containsExactly(1, null, null, 3)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithMissingAndExtraElements() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3)).containsExactly(1, 2, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithDuplicateMissingAndExtraElements() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3, 3)).containsExactly(1, 2, 4, 4)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithCommaSeparatedVsIndividual() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("a, b")).containsExactly("a", "b")
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyFailsWithSameToStringAndHomogeneousList() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2L)).containsExactly(1, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyFailsWithSameToStringAndListWithNull() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2L)).containsExactly(null, 1, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyFailsWithSameToStringAndHeterogeneousList() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2)).containsExactly(1, null, 2L)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyFailsWithSameToStringAndHomogeneousListWithDuplicates() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2L)).containsExactly(1, 2, 2)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyFailsWithSameToStringAndHeterogeneousListWithDuplicates() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1L, 2)).containsExactly(1, null, null, 2L, 2L)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithOneIterableGivesWarning() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3, 4)).containsExactly(listOf(1, 2, 3, 4))
+        }
+    }
+
     @Test
     fun iterableContainsExactlyElementsInWithOneIterableDoesNotGiveWarning() {
         assertFailsWith<AssertionError> {
@@ -808,81 +778,71 @@
         }
     }
 
-//    @Test
-//    fun iterableContainsExactlyWithTwoIterableDoesNotGivesWarning() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3, 4)).containsExactly(listOf(1, 2), listOf(3, 4))
-//        assertThat(expectFailure.getFailure())
-//            .hasMessageThat()
-//            .doesNotContain(CONTAINS_EXACTLY_ITERABLE_WARNING)
-//    }
-//
-//    private val CONTAINS_EXACTLY_ITERABLE_WARNING =
-//        ("Passing an iterable to the varargs method containsExactly(Object...) is "
-//            + "often not the correct thing to do. Did you mean to call "
-//            + "containsExactlyElementsIn(Iterable) instead?")
-//
-//    @Test
-//    fun iterableContainsExactlyWithOneNonIterableDoesNotGiveWarning() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 3, 4)).containsExactly(1)
-//        assertFailureValue("unexpected (3)", "2, 3, 4")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyInOrder() {
-//        assertThat(listOf(3, 2, 5)).containsExactly(3, 2, 5).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyInOrderWithNull() {
-//        assertThat(listOf(3, null, 5)).containsExactly(3, null, 5).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyInOrderWithFailure() {
-//        expectFailureWhenTestingThat(listOf(1, null, 3)).containsExactly(null, 1, 3).inOrder()
-//        assertFailureKeys("contents match, but order was wrong", "expected", "but was")
-//        assertFailureValue("expected", "[null, 1, 3]")
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyInOrderWithOneShotIterable() {
-//        val iterator: Iterator<Any> = listOf(1 as Any, null, 3).iterator()
-//        val iterable: Iterable<Any> = object : Iterable<Any?> {
-//            override fun iterator(): Iterator<Any> {
-//                return iterator
-//            }
-//        }
-//        assertThat(iterable).containsExactly(1, null, 3).inOrder()
-//    }
-//
-//    @Test
-//    fun iterableContainsExactlyInOrderWithOneShotIterableWrongOrder() {
-//        val iterator: Iterator<Any> = listOf(1 as Any, null, 3).iterator()
-//        val iterable: Iterable<Any> = object : Iterable<Any?> {
-//            override fun iterator(): Iterator<Any> {
-//                return iterator
-//            }
-//
-//            override fun toString(): String {
-//                return "BadIterable"
-//            }
-//        }
-//        expectFailureWhenTestingThat(iterable).containsExactly(1, 3, null).inOrder()
-//        assertFailureKeys("contents match, but order was wrong", "expected", "but was")
-//        assertFailureValue("expected", "[1, 3, null]")
-//    }
-//
-//    @Test
-//    fun iterableWithNoToStringOverride() {
-//        val iterable: Iterable<Int> = object : Iterable<Int?> {
-//            override fun iterator(): Iterator<Int> {
-//                return Iterators.forArray(1, 2, 3)
-//            }
-//        }
-//        expectFailureWhenTestingThat(iterable).containsExactly(1, 2).inOrder()
-//        assertFailureValue("but was", "[1, 2, 3]")
-//    }
-//
+    @Test
+    fun iterableContainsExactlyWithTwoIterableDoesNotGivesWarning() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3, 4)).containsExactly(
+                listOf(1, 2),
+                listOf(3, 4)
+            )
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyWithOneNonIterableDoesNotGiveWarning() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 3, 4)).containsExactly(1)
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyInOrder() {
+        assertThat(listOf(3, 2, 5)).containsExactly(3, 2, 5).inOrder()
+    }
+
+    @Test
+    fun iterableContainsExactlyInOrderWithNull() {
+        assertThat(listOf(3, null, 5)).containsExactly(3, null, 5).inOrder()
+    }
+
+    @Test
+    fun iterableContainsExactlyInOrderWithFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, null, 3)).containsExactly(null, 1, 3).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableContainsExactlyInOrderWithOneShotIterable() {
+        val iterator = listOf(1 as Any, null, 3).iterator()
+        val iterable = object : Iterable<Any?> {
+            override fun iterator(): Iterator<Any?> = iterator
+        }
+        assertThat(iterable).containsExactly(1, null, 3).inOrder()
+    }
+
+    @Test
+    fun iterableContainsExactlyInOrderWithOneShotIterableWrongOrder() {
+        val iterator = listOf(1 as Any, null, 3).iterator()
+        val iterable = object : Iterable<Any?> {
+            override fun iterator(): Iterator<Any?> = iterator
+            override fun toString(): String = "BadIterable"
+        }
+        assertFailsWith<AssertionError> {
+            assertThat(iterable).containsExactly(1, 3, null).inOrder()
+        }
+    }
+
+    @Test
+    fun iterableWithNoToStringOverride() {
+        val iterable = object : Iterable<Int?> {
+            override fun iterator(): Iterator<Int> = listOf(1, 2, 3).iterator()
+        }
+        assertFailsWith<AssertionError> {
+            assertThat(iterable).containsExactly(1, 2).inOrder()
+        }
+    }
+
     @Test
     fun iterableContainsExactlyElementsInIterable() {
         assertThat(listOf(1, 2)).containsExactlyElementsIn(listOf(1, 2))
@@ -892,14 +852,14 @@
         }
     }
 
-//    @Test
-//    fun iterableContainsExactlyElementsInArray() {
-//        assertThat(listOf(1, 2)).containsExactlyElementsIn(arrayOf(1, 2))
-//
-//        assertFailsWith<AssertionError> {
-//            assertThat(listOf(1, 2)).containsExactlyElementsIn(arrayOf<Int?>(1, 2, 4))
-//        }
-//    }
+    @Test
+    fun iterableContainsExactlyElementsInArray() {
+        assertThat(listOf(1, 2)).containsExactlyElementsIn(arrayOf<Any?>(1, 2))
+
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2)).containsExactlyElementsIn(arrayOf<Int?>(1, 2, 4))
+        }
+    }
 
     @Test
     fun nullEqualToNull() {
@@ -974,121 +934,72 @@
         }
     }
 
-//    @Test
-//    fun iterableIsInStrictOrder() {
-//        assertThat(emptyList<Any>()).isInStrictOrder()
-//        assertThat(listOf(1)).isInStrictOrder()
-//        assertThat(listOf(1, 2, 3, 4)).isInStrictOrder()
-//    }
+    @Test
+    fun iterableIsInStrictOrder() {
+        assertThat(emptyList<Any>()).isInStrictOrder()
+        assertThat(listOf(1)).isInStrictOrder()
+        assertThat(listOf(1, 2, 3, 4)).isInStrictOrder()
+    }
 
-//    @Test
-//    fun isInStrictOrderFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 2, 2, 4)).isInStrictOrder()
-//        assertFailureKeys(
-//            "expected to be in strict order", "but contained", "followed by", "full contents"
-//        )
-//        assertFailureValue("but contained", "2")
-//        assertFailureValue("followed by", "2")
-//        assertFailureValue("full contents", "[1, 2, 2, 4]")
-//    }
-//
-//    @Test
-//    fun isInStrictOrderWithNonComparableElementsFailure() {
-//        try {
-//            assertThat(listOf(1 as Any, "2", 3, "4")).isInStrictOrder()
-//            fail("Should have thrown.")
-//        } catch (expected: java.lang.ClassCastException) {
-//        }
-//    }
-//
-//    @Test
-//    fun iterableIsInOrder() {
-//        assertThat(listOf()).isInOrder()
-//        assertThat(listOf(1)).isInOrder()
-//        assertThat(listOf(1, 1, 2, 3, 3, 3, 4)).isInOrder()
-//    }
-//
-//    @Test
-//    fun isInOrderFailure() {
-//        expectFailureWhenTestingThat(listOf(1, 3, 2, 4)).isInOrder()
-//        assertFailureKeys(
-//            "expected to be in order",
-//            "but contained",
-//            "followed by",
-//            "full contents"
-//        )
-//        assertFailureValue("but contained", "3")
-//        assertFailureValue("followed by", "2")
-//        assertFailureValue("full contents", "[1, 3, 2, 4]")
-//    }
-//
-//    @Test
-//    fun isInOrderMultipleFailures() {
-//        expectFailureWhenTestingThat(listOf(1, 3, 2, 4, 0)).isInOrder()
-//    }
-//
-//    @Test
-//    fun isInOrderWithNonComparableElementsFailure() {
-//        try {
-//            assertThat(listOf(1 as Any, "2", 2, "3")).isInOrder()
-//            fail("Should have thrown.")
-//        } catch (expected: java.lang.ClassCastException) {
-//        }
-//    }
-//
-//    @Test
-//    fun iterableIsInStrictOrderWithComparator() {
-//        val emptyStrings: Iterable<String> = listOf()
-//        assertThat(emptyStrings).isInStrictOrder(COMPARE_AS_DECIMAL)
-//        assertThat(listOf("1")).isInStrictOrder(COMPARE_AS_DECIMAL)
-//        // Note: Use "10" and "20" to distinguish numerical and lexicographical ordering.
-//        assertThat(listOf("1", "2", "10", "20")).isInStrictOrder(COMPARE_AS_DECIMAL)
-//    }
-//
-//    @Test
-//    fun iterableIsInStrictOrderWithComparatorFailure() {
-//        expectFailureWhenTestingThat(
-//            listOf(
-//                "1",
-//                "2",
-//                "2",
-//                "10"
-//            )
-//        ).isInStrictOrder(COMPARE_AS_DECIMAL)
-//        assertFailureKeys(
-//            "expected to be in strict order", "but contained", "followed by", "full contents"
-//        )
-//        assertFailureValue("but contained", "2")
-//        assertFailureValue("followed by", "2")
-//        assertFailureValue("full contents", "[1, 2, 2, 10]")
-//    }
-//
-//    @Test
-//    fun iterableIsInOrderWithComparator() {
-//        val emptyStrings: Iterable<String> = listOf()
-//        assertThat(emptyStrings).isInOrder(COMPARE_AS_DECIMAL)
-//        assertThat(listOf("1")).isInOrder(COMPARE_AS_DECIMAL)
-//        assertThat(listOf("1", "1", "2", "10", "10", "10", "20")).isInOrder(COMPARE_AS_DECIMAL)
-//    }
-//
-//    @Test
-//    fun iterableIsInOrderWithComparatorFailure() {
-//        expectFailureWhenTestingThat(listOf("1", "10", "2", "20")).isInOrder(COMPARE_AS_DECIMAL)
-//        assertFailureKeys(
-//            "expected to be in order",
-//            "but contained",
-//            "followed by",
-//            "full contents"
-//        )
-//        assertFailureValue("but contained", "10")
-//        assertFailureValue("followed by", "2")
-//        assertFailureValue("full contents", "[1, 10, 2, 20]")
-//    }
-//
-//    private val COMPARE_AS_DECIMAL: Comparator<String> =
-//        Comparator<String?> { a, b ->
-//            java.lang.Integer.valueOf(a).compareTo(java.lang.Integer.valueOf(b))
-//        }
+    @Test
+    fun isInStrictOrderFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 2, 2, 4)).isInStrictOrder()
+        }
+    }
+
+    @Test
+    fun iterableIsInOrder() {
+        assertThat(listOf<Any?>()).isInOrder()
+        assertThat(listOf(1)).isInOrder()
+        assertThat(listOf(1, 1, 2, 3, 3, 3, 4)).isInOrder()
+    }
+
+    @Test
+    fun isInOrderFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 3, 2, 4)).isInOrder()
+        }
+    }
+
+    @Test
+    fun isInOrderMultipleFailures() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf(1, 3, 2, 4, 0)).isInOrder()
+        }
+    }
+
+    @Test
+    fun iterableIsInStrictOrderWithComparator() {
+        val emptyStrings: Iterable<String> = listOf()
+        assertThat(emptyStrings).isInStrictOrder(COMPARE_AS_DECIMAL)
+        assertThat(listOf("1")).isInStrictOrder(COMPARE_AS_DECIMAL)
+        // Note: Use "10" and "20" to distinguish numerical and lexicographical ordering.
+        assertThat(listOf("1", "2", "10", "20")).isInStrictOrder(COMPARE_AS_DECIMAL)
+    }
+
+    @Test
+    fun iterableIsInStrictOrderWithComparatorFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("1", "2", "2", "10")).isInStrictOrder(COMPARE_AS_DECIMAL)
+        }
+    }
+
+    @Test
+    fun iterableIsInOrderWithComparator() {
+        val emptyStrings: Iterable<String> = listOf()
+        assertThat(emptyStrings).isInOrder(COMPARE_AS_DECIMAL)
+        assertThat(listOf("1")).isInOrder(COMPARE_AS_DECIMAL)
+        assertThat(listOf("1", "1", "2", "10", "10", "10", "20")).isInOrder(COMPARE_AS_DECIMAL)
+    }
+
+    @Test
+    fun iterableIsInOrderWithComparatorFailure() {
+        assertFailsWith<AssertionError> {
+            assertThat(listOf("1", "10", "2", "20")).isInOrder(COMPARE_AS_DECIMAL)
+        }
+    }
+
 //
 //    private class Foo private constructor(val x: Int)
 //
@@ -1153,7 +1064,12 @@
 //                    + "containsNoneOf(...)/containsNoneIn(...) instead. Non-iterables: [a, b]")
 //            )
 //    }
-//
+
+    private companion object {
+        private val COMPARE_AS_DECIMAL: Comparator<String?> =
+            Comparator { a, b -> a!!.toInt().compareTo(b!!.toInt()) }
+    }
+
     private class CountsToStringCalls {
         var calls = 0
 
@@ -1162,4 +1078,14 @@
             return super.toString()
         }
     }
+
+    private class HashCodeThrower {
+        override fun equals(other: Any?): Boolean = this === other
+
+        override fun hashCode(): Int {
+            throw UnsupportedOperationException()
+        }
+
+        override fun toString(): String = "HCT"
+    }
 }
diff --git a/testutils/testutils-kmp/src/jvmTest/kotlin/androidx/kruth/IterableSubjectJvmTest.kt b/testutils/testutils-kmp/src/jvmTest/kotlin/androidx/kruth/IterableSubjectJvmTest.kt
new file mode 100644
index 0000000..c1adf10
--- /dev/null
+++ b/testutils/testutils-kmp/src/jvmTest/kotlin/androidx/kruth/IterableSubjectJvmTest.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.kruth
+
+import kotlin.test.Test
+import kotlin.test.assertFailsWith
+
+class IterableSubjectJvmTest {
+
+    // The test fails on native, see https://youtrack.jetbrains.com/issue/KT-56089
+    @Test
+    fun isInStrictOrderWithNonComparableElementsFailure() {
+        assertFailsWith<ClassCastException> {
+            assertThat(listOf(1 as Any, "2", 3, "4")).isInStrictOrder()
+        }
+    }
+
+    // The test fails on native, see https://youtrack.jetbrains.com/issue/KT-56089
+    @Test
+    fun isInOrderWithNonComparableElementsFailure() {
+        assertFailsWith<ClassCastException> {
+            assertThat(listOf(1 as Any, "2", 2, "3")).isInOrder()
+        }
+    }
+}
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 15d409d..1d4abee 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
@@ -16,6 +16,7 @@
 
 package androidx.tv.integration.demos
 
+import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
@@ -42,11 +43,10 @@
 import androidx.compose.ui.focus.onFocusChanged
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.dp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
-import androidx.tv.material3.CarouselItem
 import androidx.tv.material3.CarouselState
+import androidx.tv.material3.ExperimentalTvMaterial3Api
 
 @Composable
 fun FeaturedCarouselContent() {
@@ -95,7 +95,7 @@
         .onFocusChanged { isFocused = it.isFocused }
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Composable
 internal fun FeaturedCarousel(modifier: Modifier = Modifier) {
     val backgrounds = listOf(
@@ -127,7 +127,6 @@
         }
     ) { itemIndex ->
         CarouselItem(
-            overlayEnterTransitionStartDelayMillis = 0,
             background = {
                 Box(
                     modifier = Modifier
@@ -136,18 +135,22 @@
                 )
             }
         ) {
-            OverlayButton()
+            Box(modifier = Modifier) {
+                OverlayButton(
+                    modifier = Modifier
+                )
+            }
         }
     }
 }
 
 @Composable
-private fun OverlayButton() {
+private fun OverlayButton(modifier: Modifier = Modifier) {
     var isFocused by remember { mutableStateOf(false) }
 
     Button(
         onClick = { },
-        modifier = Modifier
+        modifier = modifier
             .onFocusChanged { isFocused = it.isFocused }
             .padding(40.dp)
             .border(
diff --git a/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TextField.kt b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TextField.kt
new file mode 100644
index 0000000..ff5793b
--- /dev/null
+++ b/tv/integration-tests/demos/src/main/java/androidx/tv/integration/demos/TextField.kt
@@ -0,0 +1,107 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.background
+import androidx.compose.foundation.focusable
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.width
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.material3.Button
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.OutlinedTextField
+import androidx.compose.material3.Text
+import androidx.compose.material3.TextFieldDefaults
+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.unit.dp
+
+@Composable
+fun TextFieldContent() {
+    LazyRow(horizontalArrangement = Arrangement.spacedBy(20.dp)) {
+        item {
+            Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+                repeat(4) { SampleCardItem() }
+            }
+        }
+        item {
+            LazyColumn(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+                item { SampleTextField(label = "Email") }
+                item { SampleTextField(label = "Password") }
+                item { SampleButton(text = "Submit") }
+            }
+        }
+        item {
+            Column(verticalArrangement = Arrangement.spacedBy(20.dp)) {
+                repeat(4) { SampleCardItem() }
+            }
+        }
+    }
+}
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun SampleTextField(label: String) {
+    var text by remember { mutableStateOf("") }
+
+    OutlinedTextField(
+        value = text,
+        onValueChange = { text = it },
+        label = {
+            Text(label)
+        },
+        singleLine = true,
+        placeholder = {
+            Text("$label...")
+        },
+        colors = TextFieldDefaults.outlinedTextFieldColors(
+            focusedBorderColor = Color.Cyan,
+            focusedLabelColor = Color.Cyan,
+            cursorColor = Color.White
+        )
+    )
+}
+
+@Composable
+fun SampleButton(text: String) {
+    Button(
+        onClick = { }
+    ) {
+        Text(text)
+    }
+}
+
+@Composable
+private fun SampleCardItem() {
+    Box(
+        modifier = Modifier
+            .background(Color.Magenta.copy(alpha = 0.3f))
+            .width(50.dp)
+            .height(50.dp)
+            .drawBorderOnFocus()
+            .focusable()
+    )
+}
\ 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 44d7542..ba1423e 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
@@ -40,6 +40,7 @@
   FeaturedCarousel("Featured Carousel", { FeaturedCarouselContent() }),
   ImmersiveList("Immersive List", { ImmersiveListContent() }),
   StickyHeader("Sticky Header", { StickyHeaderContent() }),
+  TextField("Text Field", { TextFieldContent() }),
 }
 
 @Composable
diff --git a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
index b235403..ba6bdb0 100644
--- a/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
+++ b/tv/samples/src/main/java/androidx/tv/samples/CarouselSamples.kt
@@ -17,6 +17,7 @@
 package androidx.tv.samples
 
 import androidx.annotation.Sampled
+import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.layout.Box
@@ -39,13 +40,12 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.RectangleShape
 import androidx.compose.ui.unit.dp
-import androidx.tv.material3.ExperimentalTvMaterial3Api
 import androidx.tv.material3.Carousel
 import androidx.tv.material3.CarouselDefaults
-import androidx.tv.material3.CarouselItem
 import androidx.tv.material3.CarouselState
+import androidx.tv.material3.ExperimentalTvMaterial3Api
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Sampled
 @Composable
 fun SimpleCarousel() {
@@ -62,7 +62,6 @@
             .fillMaxWidth(),
     ) { itemIndex ->
         CarouselItem(
-            overlayEnterTransitionStartDelayMillis = 0,
             background = {
                 Box(
                     modifier = Modifier
@@ -92,7 +91,7 @@
     }
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Sampled
 @Composable
 fun CarouselIndicatorWithRectangleShape() {
@@ -132,7 +131,6 @@
         }
     ) { itemIndex ->
         CarouselItem(
-            overlayEnterTransitionStartDelayMillis = 0,
             background = {
                 Box(
                     modifier = Modifier
diff --git a/tv/tv-material/api/current.txt b/tv/tv-material/api/current.txt
index 3e782bc..1c7d493 100644
--- a/tv/tv-material/api/current.txt
+++ b/tv/tv-material/api/current.txt
@@ -21,6 +21,9 @@
   public final class ImmersiveListKt {
   }
 
+  public final class KeyEventUtilsKt {
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
diff --git a/tv/tv-material/api/public_plus_experimental_current.txt b/tv/tv-material/api/public_plus_experimental_current.txt
index 1e8c90f..4afda2a 100644
--- a/tv/tv-material/api/public_plus_experimental_current.txt
+++ b/tv/tv-material/api/public_plus_experimental_current.txt
@@ -6,29 +6,33 @@
 
   @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselDefaults {
     method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public void IndicatorRow(int slideCount, int activeSlideIndex, optional androidx.compose.ui.Modifier modifier, optional float spacing, optional kotlin.jvm.functions.Function1<? super java.lang.Boolean,kotlin.Unit> indicator);
-    method public androidx.compose.animation.EnterTransition getEnterTransition();
-    method public androidx.compose.animation.ExitTransition getExitTransition();
-    property public final androidx.compose.animation.EnterTransition EnterTransition;
-    property public final androidx.compose.animation.ExitTransition ExitTransition;
+    method @androidx.compose.runtime.Composable public androidx.compose.animation.ContentTransform getContentTransform();
+    property @androidx.compose.runtime.Composable public final androidx.compose.animation.ContentTransform contentTransform;
     field public static final androidx.tv.material3.CarouselDefaults INSTANCE;
     field public static final long TimeToDisplaySlideMillis = 5000L; // 0x1388L
   }
 
   @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselItemDefaults {
-    method public androidx.compose.animation.EnterTransition getOverlayEnterTransition();
-    method public androidx.compose.animation.ExitTransition getOverlayExitTransition();
-    property public final androidx.compose.animation.EnterTransition OverlayEnterTransition;
-    property public final androidx.compose.animation.ExitTransition OverlayExitTransition;
+    method @androidx.compose.runtime.Composable public androidx.compose.animation.ContentTransform getContentTransformBackward();
+    method @androidx.compose.runtime.Composable public androidx.compose.animation.ContentTransform getContentTransformForward();
+    method @androidx.compose.runtime.Composable public androidx.compose.animation.ContentTransform getContentTransformLeftToRight();
+    method @androidx.compose.runtime.Composable public androidx.compose.animation.ContentTransform getContentTransformRightToLeft();
+    property @androidx.compose.runtime.Composable public final androidx.compose.animation.ContentTransform contentTransformBackward;
+    property @androidx.compose.runtime.Composable public final androidx.compose.animation.ContentTransform contentTransformForward;
+    property @androidx.compose.runtime.Composable public final androidx.compose.animation.ContentTransform contentTransformLeftToRight;
+    property @androidx.compose.runtime.Composable public final androidx.compose.animation.ContentTransform contentTransformRightToLeft;
     field public static final androidx.tv.material3.CarouselItemDefaults INSTANCE;
-    field public static final long OverlayEnterTransitionStartDelayMillis = 200L; // 0xc8L
   }
 
   public final class CarouselItemKt {
-    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void CarouselItem(kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.compose.ui.Modifier modifier, optional long overlayEnterTransitionStartDelayMillis, optional androidx.compose.animation.EnterTransition overlayEnterTransition, optional androidx.compose.animation.ExitTransition overlayExitTransition, kotlin.jvm.functions.Function0<kotlin.Unit> overlay);
   }
 
   public final class CarouselKt {
-    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Carousel(int slideCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.CarouselState carouselState, optional long timeToDisplaySlideMillis, optional androidx.compose.animation.EnterTransition enterTransition, optional androidx.compose.animation.ExitTransition exitTransition, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> carouselIndicator, kotlin.jvm.functions.Function1<? super java.lang.Integer,kotlin.Unit> content);
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public static void Carousel(int slideCount, optional androidx.compose.ui.Modifier modifier, optional androidx.tv.material3.CarouselState carouselState, optional long autoScrollDurationMillis, optional androidx.compose.animation.ContentTransform contentTransformForward, optional androidx.compose.animation.ContentTransform contentTransformBackward, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> carouselIndicator, kotlin.jvm.functions.Function2<? super androidx.tv.material3.CarouselScope,? super java.lang.Integer,kotlin.Unit> content);
+  }
+
+  @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselScope {
+    method @androidx.compose.runtime.Composable @androidx.tv.material3.ExperimentalTvMaterial3Api public void CarouselItem(optional androidx.compose.ui.Modifier modifier, optional kotlin.jvm.functions.Function0<kotlin.Unit> background, optional androidx.compose.animation.ContentTransform contentTransformForward, optional androidx.compose.animation.ContentTransform contentTransformBackward, kotlin.jvm.functions.Function0<kotlin.Unit> content);
   }
 
   @androidx.compose.runtime.Stable @androidx.tv.material3.ExperimentalTvMaterial3Api public final class CarouselState {
@@ -138,6 +142,9 @@
     method public androidx.compose.ui.Modifier immersiveListItem(androidx.compose.ui.Modifier, int index);
   }
 
+  public final class KeyEventUtilsKt {
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
diff --git a/tv/tv-material/api/restricted_current.txt b/tv/tv-material/api/restricted_current.txt
index 3e782bc..1c7d493 100644
--- a/tv/tv-material/api/restricted_current.txt
+++ b/tv/tv-material/api/restricted_current.txt
@@ -21,6 +21,9 @@
   public final class ImmersiveListKt {
   }
 
+  public final class KeyEventUtilsKt {
+  }
+
   public final class MaterialTheme {
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.ColorScheme getColorScheme();
     method @androidx.compose.runtime.Composable @androidx.compose.runtime.ReadOnlyComposable public androidx.tv.material3.Shapes getShapes();
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt
deleted file mode 100644
index bc41a0f..0000000
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselItemTest.kt
+++ /dev/null
@@ -1,137 +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.tv.material3
-
-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.fillMaxSize
-import androidx.compose.foundation.layout.size
-import androidx.compose.foundation.text.BasicText
-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.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.semantics.SemanticsActions
-import androidx.compose.ui.test.assertIsFocused
-import androidx.compose.ui.test.junit4.createComposeRule
-import androidx.compose.ui.test.onNodeWithTag
-import androidx.compose.ui.test.performSemanticsAction
-import androidx.compose.ui.unit.dp
-import androidx.test.platform.app.InstrumentationRegistry
-import org.junit.Rule
-import org.junit.Test
-
-class CarouselItemTest {
-    @get:Rule
-    val rule = createComposeRule()
-
-    @OptIn(ExperimentalTvMaterial3Api::class)
-    @Test
-    fun carouselItem_overlayVisibleAfterRenderTime() {
-        val overlayEnterTransitionStartDelay: Long = 2000
-        val overlayTag = "overlay"
-        val backgroundTag = "background"
-        rule.setContent {
-            CarouselItem(
-                overlayEnterTransitionStartDelayMillis = overlayEnterTransitionStartDelay,
-                background = {
-                    Box(
-                        Modifier
-                            .testTag(backgroundTag)
-                            .size(200.dp)
-                            .background(Color.Blue)) }) {
-                Box(
-                    Modifier
-                        .testTag(overlayTag)
-                        .size(50.dp)
-                        .background(Color.Red))
-            }
-        }
-
-        // only background is visible
-        rule.onNodeWithTag(backgroundTag).assertExists()
-        rule.onNodeWithTag(overlayTag).assertDoesNotExist()
-
-        // advance clock by `overlayEnterTransitionStartDelay`
-        rule.mainClock.advanceTimeBy(overlayEnterTransitionStartDelay)
-
-        rule.onNodeWithTag(backgroundTag).assertExists()
-        rule.onNodeWithTag(overlayTag).assertExists()
-    }
-
-    @OptIn(ExperimentalTvMaterial3Api::class)
-    @Test
-    fun carouselItem_parentContainerGainsFocused_onBackPress() {
-        rule.setContent {
-            Box(modifier = Modifier
-                .testTag("box-container")
-                .fillMaxSize()
-                .focusable()) {
-                CarouselItem(
-                    overlayEnterTransitionStartDelayMillis = 0,
-                    modifier = Modifier.testTag("carousel-item"),
-                    background = { Box(Modifier.size(300.dp).background(Color.Cyan)) }
-                ) {
-                    SampleButton()
-                }
-            }
-        }
-
-        // Request focus for Carousel Item on start
-        rule.onNodeWithTag("carousel-item")
-            .performSemanticsAction(SemanticsActions.RequestFocus)
-        rule.waitForIdle()
-
-        // Check if overlay button in carousel item is focused
-        rule.onNodeWithTag("sample-button").assertIsFocused()
-
-        // Trigger back press
-        performKeyPress(NativeKeyEvent.KEYCODE_BACK)
-        rule.waitForIdle()
-
-        // Check if carousel item loses focus and parent container gains focus
-        rule.onNodeWithTag("box-container").assertIsFocused()
-    }
-
-    @Composable
-    private fun SampleButton(text: String = "sample-button") {
-        var isFocused by remember { mutableStateOf(false) }
-        BasicText(
-            text = text,
-            modifier = Modifier.testTag(text)
-                .size(100.dp, 20.dp)
-                .background(Color.Yellow)
-                .onFocusChanged { isFocused = it.isFocused }
-                .border(2.dp, if (isFocused) Color.Green else Color.Transparent)
-                .focusable()
-        )
-    }
-
-    private fun performKeyPress(keyCode: Int, count: Int = 1) {
-        repeat(count) {
-            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
-        }
-    }
-}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselScopeTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselScopeTest.kt
new file mode 100644
index 0000000..10080e0
--- /dev/null
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselScopeTest.kt
@@ -0,0 +1,120 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ExperimentalAnimationApi
+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.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.text.BasicText
+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.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.semantics.SemanticsActions
+import androidx.compose.ui.test.assertIsFocused
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performSemanticsAction
+import androidx.compose.ui.unit.dp
+import androidx.test.platform.app.InstrumentationRegistry
+import org.junit.Rule
+import org.junit.Test
+
+const val sampleButtonTag = "sample-button"
+
+class CarouselScopeTest {
+    @get:Rule
+    val rule = createComposeRule()
+
+    @OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
+    @Test
+    fun carouselItem_parentContainerGainsFocused_onBackPress() {
+        val containerBoxTag = "container-box"
+        val carouselItemTag = "carousel-item"
+
+        rule.setContent {
+            val carouselState = remember { CarouselState() }
+            var isContainerBoxFocused by remember { mutableStateOf(false) }
+            Box(
+                modifier = Modifier
+                    .testTag(containerBoxTag)
+                    .fillMaxSize()
+                    .onFocusChanged { isContainerBoxFocused = it.isFocused }
+                    .border(10.dp, if (isContainerBoxFocused) Color.Green else Color.Transparent)
+                    .focusable()
+            ) {
+                CarouselScope(carouselState = carouselState)
+                    .CarouselItem(
+                        modifier = Modifier.testTag(carouselItemTag),
+                        background = {
+                            Box(
+                                modifier = Modifier
+                                    .size(300.dp)
+                                    .background(Color.Cyan))
+                        },
+                        content = { SampleButton() },
+                    )
+            }
+        }
+
+        // Request focus for Carousel Item on start
+        rule.onNodeWithTag(carouselItemTag)
+            .performSemanticsAction(SemanticsActions.RequestFocus)
+        rule.waitForIdle()
+
+        // Check if overlay button in carousel item is focused
+        rule.onNodeWithTag(sampleButtonTag).assertIsFocused()
+
+        // Trigger back press
+        performKeyPress(NativeKeyEvent.KEYCODE_BACK)
+        rule.waitForIdle()
+
+        // Check if carousel item loses focus and parent container gains focus
+        rule.onNodeWithTag(containerBoxTag).assertIsFocused()
+    }
+
+    private fun performKeyPress(keyCode: Int, count: Int = 1) {
+        repeat(count) {
+            InstrumentationRegistry.getInstrumentation().sendKeyDownUpSync(keyCode)
+        }
+    }
+}
+
+@Composable
+private fun SampleButton(text: String = sampleButtonTag) {
+    var isFocused by remember { mutableStateOf(false) }
+    BasicText(
+        text = text,
+        modifier = Modifier
+            .testTag(text)
+            .size(100.dp, 20.dp)
+            .background(Color.Yellow)
+            .onFocusChanged { isFocused = it.isFocused }
+            .border(2.dp, if (isFocused) Color.Green else Color.Transparent)
+            .focusable()
+    )
+}
diff --git a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
index adb434b..4c152ad 100644
--- a/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
+++ b/tv/tv-material/src/androidTest/java/androidx/tv/material3/CarouselTest.kt
@@ -18,6 +18,8 @@
 
 import android.os.SystemClock
 import android.view.KeyEvent
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.foundation.background
 import androidx.compose.foundation.border
 import androidx.compose.foundation.focusable
@@ -65,12 +67,12 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.platform.app.InstrumentationRegistry
 import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.delay
 import org.junit.Rule
 import org.junit.Test
 
 private const val delayBetweenSlides = 2500L
 private const val animationTime = 900L
-private const val overlayRenderWaitTime = 1500L
 
 @OptIn(ExperimentalTvMaterial3Api::class)
 class CarouselTest {
@@ -273,6 +275,7 @@
         rule.onNodeWithText("Text 2").assertIsDisplayed()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_pagerIndicatorDisplayed() {
         rule.setContent {
@@ -284,6 +287,7 @@
         rule.onNodeWithTag("indicator").assertIsDisplayed()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_withAnimatedContent_successfulTransition() {
         rule.setContent {
@@ -297,15 +301,14 @@
             }
         }
 
-        rule.onNodeWithText("Text 1").assertDoesNotExist()
-
-        rule.mainClock.advanceTimeBy(overlayRenderWaitTime + animationTime, true)
+        rule.mainClock.advanceTimeBy(animationTime, true)
         rule.mainClock.advanceTimeByFrame()
 
         rule.onNodeWithText("Text 1").assertIsDisplayed()
         rule.onNodeWithText("PLAY").assertIsDisplayed()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_withAnimatedContent_successfulFocusIn() {
         rule.setContent {
@@ -319,7 +322,7 @@
             .performSemanticsAction(SemanticsActions.RequestFocus)
 
         // current slide overlay render delay
-        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime, false)
+        rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeByFrame()
 
@@ -362,6 +365,7 @@
         rule.onNodeWithTag("box-container").assertIsFocused()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_withCarouselItem_parentContainerGainsFocus_onBackPress() {
         rule.setContent {
@@ -383,7 +387,7 @@
         // Trigger recomposition after requesting focus and advance time to finish animations
         rule.mainClock.advanceTimeByFrame()
         rule.waitForIdle()
-        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime, false)
+        rule.mainClock.advanceTimeBy(animationTime, false)
         rule.waitForIdle()
 
         // Check if the overlay button is focused
@@ -399,6 +403,7 @@
         rule.onNodeWithTag("box-container").assertIsFocused()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_scrollToRegainFocus_checkBringIntoView() {
         val focusRequester = FocusRequester()
@@ -428,12 +433,9 @@
                             .border(2.dp, Color.Black),
                         carouselState = remember { CarouselState() },
                         slideCount = 3,
-                        timeToDisplaySlideMillis = delayBetweenSlides
+                        autoScrollDurationMillis = delayBetweenSlides
                     ) {
-                        SampleCarouselSlide(
-                            index = it,
-                            overlayRenderWaitTime = overlayRenderWaitTime,
-                        ) {
+                        SampleCarouselSlide(index = it) {
                             Box {
                                 Column(modifier = Modifier.align(Alignment.BottomStart)) {
                                     BasicText(text = "carousel-frame")
@@ -489,6 +491,7 @@
         assertThat(checkNodeCompletelyVisible(rule, "featured-carousel")).isTrue()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_zeroSlideCount_shouldNotCrash() {
         val testTag = "emptyCarousel"
@@ -499,6 +502,7 @@
         rule.onNodeWithTag(testTag).assertExists()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_oneSlideCount_shouldNotCrash() {
         val testTag = "emptyCarousel"
@@ -567,6 +571,7 @@
         rule.onNodeWithText("Button-1").assertIsFocused()
     }
 
+    @OptIn(ExperimentalAnimationApi::class)
     @Test
     fun carousel_manualScrolling_fastMultipleKeyPresses() {
         val carouselState = CarouselState()
@@ -607,18 +612,19 @@
             }
         }
 
-        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime)
+        rule.mainClock.advanceTimeBy(animationTime)
 
         val finalSlide = slideProgression.sum()
         rule.onNodeWithText("Play $finalSlide").assertIsFocused()
 
         performKeyPress(NativeKeyEvent.KEYCODE_DPAD_RIGHT, 3)
 
-        rule.mainClock.advanceTimeBy((animationTime + overlayRenderWaitTime) * 3)
+        rule.mainClock.advanceTimeBy((animationTime) * 3)
 
         rule.onNodeWithText("Play ${finalSlide + 3}").assertIsFocused()
     }
 
+    @Test
     fun carousel_manualScrolling_onDpadLongPress() {
         rule.setContent {
             SampleCarousel(slideCount = 6) { index ->
@@ -676,7 +682,7 @@
             .performSemanticsAction(SemanticsActions.RequestFocus)
 
         // current slide overlay render delay
-        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime, false)
+        rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeByFrame()
 
@@ -726,7 +732,7 @@
             .performSemanticsAction(SemanticsActions.RequestFocus)
 
         // current slide overlay render delay
-        rule.mainClock.advanceTimeBy(animationTime + overlayRenderWaitTime, false)
+        rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeBy(animationTime, false)
         rule.mainClock.advanceTimeByFrame()
 
@@ -757,15 +763,48 @@
         // Assert that slide 1 is in view
         rule.onNodeWithText("Button 1").assertIsDisplayed()
     }
+
+    @Test
+    fun carousel_slideCountChangesDuringAnimation_shouldNotCrash() {
+        val slideDisplayDurationMs: Long = 100
+        var slideChanges = 0
+        // number of slides will fall from 4 to 2, but 4 slide transitions should happen without a
+        // crash
+        val minSuccessfulSlideChanges = 4
+        rule.setContent {
+            var slideCount by remember { mutableStateOf(4) }
+            LaunchedEffect(Unit) {
+                while (slideCount >= 2) {
+                    delay(slideDisplayDurationMs)
+                    slideCount--
+                }
+            }
+            SampleCarousel(
+                slideCount = slideCount,
+                timeToDisplaySlideMillis = slideDisplayDurationMs
+            ) { index ->
+                if (index >= slideCount) {
+                    // slideIndex requested should not be greater than slideCount. User could be
+                    // using a data-structure that could throw an IndexOutOfBoundsException.
+                    // This can happen when the slideCount changes during the transition between
+                    // slides.
+                    throw Exception("Index is larger, index=$index, slideCount=$slideCount")
+                }
+                slideChanges++
+            }
+        }
+
+        rule.waitUntil(timeoutMillis = 5000) { slideChanges > minSuccessfulSlideChanges }
+    }
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Composable
 private fun SampleCarousel(
     carouselState: CarouselState = remember { CarouselState() },
     slideCount: Int = 3,
     timeToDisplaySlideMillis: Long = delayBetweenSlides,
-    content: @Composable (index: Int) -> Unit
+    content: @Composable CarouselScope.(index: Int) -> Unit
 ) {
     Carousel(
         modifier = Modifier
@@ -775,7 +814,7 @@
             .testTag("pager"),
         carouselState = carouselState,
         slideCount = slideCount,
-        timeToDisplaySlideMillis = timeToDisplaySlideMillis,
+        autoScrollDurationMillis = timeToDisplaySlideMillis,
         carouselIndicator = {
             CarouselDefaults.IndicatorRow(
                 modifier = Modifier
@@ -786,22 +825,22 @@
                 slideCount = slideCount
             )
         },
-        content = content,
+        content = { content(it) },
     )
 }
 
-@OptIn(ExperimentalTvMaterial3Api::class)
+@OptIn(ExperimentalTvMaterial3Api::class, ExperimentalAnimationApi::class)
 @Composable
-private fun SampleCarouselSlide(
+private fun CarouselScope.SampleCarouselSlide(
     index: Int,
     modifier: Modifier = Modifier,
-    overlayRenderWaitTime: Long = CarouselItemDefaults.OverlayEnterTransitionStartDelayMillis,
+    contentTransformForward: ContentTransform =
+        CarouselItemDefaults.contentTransformForward,
     content: (@Composable () -> Unit) = { SampleButton("Play $index") },
 ) {
-
     CarouselItem(
         modifier = modifier,
-        overlayEnterTransitionStartDelayMillis = overlayRenderWaitTime,
+        contentTransformForward = contentTransformForward,
         background = {
             Box(
                 modifier = Modifier
@@ -809,9 +848,10 @@
                     .background(Color.Red)
                     .border(2.dp, Color.Blue)
             )
-        },
-        overlay = content
-    )
+        }
+    ) {
+        content()
+    }
 }
 
 @Composable
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
index 70c5480..442c810 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/Carousel.kt
@@ -21,8 +21,7 @@
 import android.view.KeyEvent.KEYCODE_DPAD_RIGHT
 import androidx.compose.animation.AnimatedContent
 import androidx.compose.animation.AnimatedVisibilityScope
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.ContentTransform
 import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.core.tween
 import androidx.compose.animation.fadeIn
@@ -43,6 +42,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
 import androidx.compose.runtime.setValue
 import androidx.compose.runtime.snapshotFlow
 import androidx.compose.ui.Alignment
@@ -80,10 +80,13 @@
  * @param modifier Modifier applied to the Carousel.
  * @param slideCount total number of slides present in the carousel.
  * @param carouselState state associated with this carousel.
- * @param timeToDisplaySlideMillis duration for which slide should be visible before moving to
+ * @param autoScrollDurationMillis duration for which slide should be visible before moving to
  * the next slide.
- * @param enterTransition transition used to bring a slide into view.
- * @param exitTransition transition used to remove a slide from view.
+ * @param contentTransformForward animation transform applied when we are moving forward in the
+ * carousel while scrolling
+ * @param contentTransformBackward animation transform applied when we are moving backward in the
+ * carousel while scrolling
+ * in the next slide
  * @param carouselIndicator indicator showing the position of the current slide among all slides.
  * @param content defines the slides for a given index.
  */
@@ -95,9 +98,9 @@
     slideCount: Int,
     modifier: Modifier = Modifier,
     carouselState: CarouselState = remember { CarouselState() },
-    timeToDisplaySlideMillis: Long = CarouselDefaults.TimeToDisplaySlideMillis,
-    enterTransition: EnterTransition = CarouselDefaults.EnterTransition,
-    exitTransition: ExitTransition = CarouselDefaults.ExitTransition,
+    autoScrollDurationMillis: Long = CarouselDefaults.TimeToDisplaySlideMillis,
+    contentTransformForward: ContentTransform = CarouselDefaults.contentTransform,
+    contentTransformBackward: ContentTransform = CarouselDefaults.contentTransform,
     carouselIndicator:
     @Composable BoxScope.() -> Unit = {
         CarouselDefaults.IndicatorRow(
@@ -108,7 +111,7 @@
                 .padding(16.dp),
         )
     },
-    content: @Composable (index: Int) -> Unit
+    content: @Composable CarouselScope.(index: Int) -> Unit
 ) {
     CarouselStateUpdater(carouselState, slideCount)
     var focusState: FocusState? by remember { mutableStateOf(null) }
@@ -118,7 +121,7 @@
     var isAutoScrollActive by remember { mutableStateOf(false) }
 
     AutoScrollSideEffect(
-        timeToDisplaySlideMillis = timeToDisplaySlideMillis,
+        autoScrollDurationMillis = autoScrollDurationMillis,
         slideCount = slideCount,
         carouselState = carouselState,
         doAutoScroll = shouldPerformAutoScroll(focusState),
@@ -146,8 +149,14 @@
     ) {
         AnimatedContent(
             targetState = carouselState.activeSlideIndex,
-            transitionSpec = { enterTransition.with(exitTransition) }
-        ) {
+            transitionSpec = {
+                if (carouselState.isMovingBackward) {
+                    contentTransformBackward
+                } else {
+                    contentTransformForward
+                }
+            }
+        ) { activeSlideIndex ->
             LaunchedEffect(Unit) {
                 this@AnimatedContent.onAnimationCompletion {
                     // Outer box is focused
@@ -157,7 +166,14 @@
                     }
                 }
             }
-            content.invoke(it)
+            // it is possible for the slideCount to have changed during the transition.
+            // This can cause the slideIndex to be greater than or equal to slideCount and cause
+            // IndexOutOfBoundsException. Guarding against this by checking against slideCount
+            // before invoking.
+            if (slideCount > 0) {
+                CarouselScope(carouselState = carouselState)
+                    .content(if (activeSlideIndex < slideCount) activeSlideIndex else 0)
+            }
         }
         this.carouselIndicator()
     }
@@ -180,22 +196,24 @@
 @OptIn(ExperimentalTvMaterial3Api::class)
 @Composable
 private fun AutoScrollSideEffect(
-    timeToDisplaySlideMillis: Long,
+    autoScrollDurationMillis: Long,
     slideCount: Int,
     carouselState: CarouselState,
     doAutoScroll: Boolean,
     onAutoScrollChange: (isAutoScrollActive: Boolean) -> Unit = {},
 ) {
+    // Needed to ensure that the code within LaunchedEffect receives updates to the slideCount.
+    val updatedSlideCount by rememberUpdatedState(newValue = slideCount)
     if (doAutoScroll) {
         LaunchedEffect(carouselState) {
             while (true) {
                 yield()
-                delay(timeToDisplaySlideMillis)
+                delay(autoScrollDurationMillis)
                 if (carouselState.activePauseHandlesCount > 0) {
                     snapshotFlow { carouselState.activePauseHandlesCount }
                         .first { pauseHandleCount -> pauseHandleCount == 0 }
                 }
-                carouselState.moveToNextSlide(slideCount)
+                carouselState.moveToNextSlide(updatedSlideCount)
             }
         }
     }
@@ -301,6 +319,13 @@
         internal set
 
     /**
+     * Tracks whether we are scrolling backward in the Carousel. By default, we are moving forward
+     * because of auto-scroll
+     */
+    internal var isMovingBackward = false
+        private set
+
+    /**
      * Pauses the auto-scrolling behaviour of Carousel.
      * The pause request is ignored if [slideIndex] is not the current slide that is visible.
      * Returns a [ScrollPauseHandle] that can be used to resume
@@ -320,6 +345,8 @@
         // No slides available for carousel
         if (slideCount == 0) return
 
+        isMovingBackward = true
+
         // Go to previous slide
         activeSlideIndex = floorMod(activeSlideIndex - 1, slideCount)
     }
@@ -328,6 +355,8 @@
         // No slides available for carousel
         if (slideCount == 0) return
 
+        isMovingBackward = false
+
         // Go to next slide
         activeSlideIndex = floorMod(activeSlideIndex + 1, slideCount)
     }
@@ -379,14 +408,13 @@
     const val TimeToDisplaySlideMillis: Long = 5000
 
     /**
-     * Default transition used to bring the slide into view
+     * Transition applied when bringing it into view and removing it from the view
      */
-    val EnterTransition: EnterTransition = fadeIn(animationSpec = tween(100))
-
-    /**
-     * Default transition used to remove the slide from view
-     */
-    val ExitTransition: ExitTransition = fadeOut(animationSpec = tween(100))
+    @OptIn(ExperimentalAnimationApi::class)
+    val contentTransform: ContentTransform
+    @Composable get() =
+        fadeIn(animationSpec = tween(100))
+            .with(fadeOut(animationSpec = tween(100)))
 
     /**
      * An indicator showing the position of the current active slide among the slides of the
@@ -407,7 +435,7 @@
         spacing: Dp = 8.dp,
         indicator: @Composable (isActive: Boolean) -> Unit = { isActive ->
             val activeColor = Color.White
-            val inactiveColor = activeColor.copy(alpha = 0.5f)
+            val inactiveColor = activeColor.copy(alpha = 0.3f)
             Box(
                 modifier = Modifier
                     .size(8.dp)
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
index 96114ca..fff09aa 100644
--- a/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselItem.kt
@@ -16,137 +16,149 @@
 
 package androidx.tv.material3
 
-import android.view.KeyEvent
 import androidx.compose.animation.AnimatedVisibility
-import androidx.compose.animation.EnterTransition
-import androidx.compose.animation.ExitTransition
-import androidx.compose.animation.core.MutableTransitionState
+import androidx.compose.animation.ContentTransform
+import androidx.compose.animation.ExperimentalAnimationApi
 import androidx.compose.animation.slideInHorizontally
 import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.with
 import androidx.compose.foundation.focusable
 import androidx.compose.foundation.layout.Box
 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.runtime.snapshotFlow
-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.FocusState
 import androidx.compose.ui.focus.onFocusChanged
-import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
-import androidx.compose.ui.input.key.key
-import androidx.compose.ui.input.key.nativeKeyCode
 import androidx.compose.ui.input.key.onKeyEvent
-import androidx.compose.ui.input.key.type
 import androidx.compose.ui.platform.LocalFocusManager
-import kotlinx.coroutines.delay
-import kotlinx.coroutines.flow.first
+import androidx.compose.ui.platform.LocalLayoutDirection
+import androidx.compose.ui.unit.LayoutDirection
+import androidx.tv.material3.KeyEventPropagation.ContinuePropagation
 
 /**
  * This composable is intended for use in Carousel.
  * A composable that has
  * - a [background] layer that is rendered as soon as the composable is visible.
- * - an [overlay] layer that is rendered after a delay of
- *   [overlayEnterTransitionStartDelayMillis].
+ * - a [content] layer that is rendered on top of the [background]
  *
- * @param modifier modifier applied to the CarouselItem.
- * @param overlayEnterTransitionStartDelayMillis time between the rendering of the
- * background and the overlay.
- * @param overlayEnterTransition animation used to bring the overlay into view.
- * @param overlayExitTransition animation used to remove the overlay from view.
- * @param background composable defining the background of the slide.
- * @param overlay composable defining the content overlaid on the background.
+ * @param background composable defining the background of the slide
+ * @param slideIndex current active slide index of the carousel
+ * @param modifier modifier applied to the CarouselItem
+ * @param contentTransform content transform to be applied to the content of the slide when
+ * scrolling
+ * @param content composable defining the content displayed on top of the background
  */
 @Suppress("IllegalExperimentalApiUsage")
-@OptIn(ExperimentalComposeUiApi::class)
+@OptIn(ExperimentalAnimationApi::class, ExperimentalComposeUiApi::class)
 @ExperimentalTvMaterial3Api
 @Composable
-fun CarouselItem(
-    background: @Composable () -> Unit,
+internal fun CarouselItem(
+    slideIndex: Int,
     modifier: Modifier = Modifier,
-    overlayEnterTransitionStartDelayMillis: Long =
-        CarouselItemDefaults.OverlayEnterTransitionStartDelayMillis,
-    overlayEnterTransition: EnterTransition = CarouselItemDefaults.OverlayEnterTransition,
-    overlayExitTransition: ExitTransition = CarouselItemDefaults.OverlayExitTransition,
-    overlay: @Composable () -> Unit
+    background: @Composable () -> Unit = {},
+    contentTransform: ContentTransform =
+        CarouselItemDefaults.contentTransformForward,
+    content: @Composable () -> Unit,
 ) {
-    val overlayVisible = remember { MutableTransitionState(initialState = false) }
     var containerBoxFocusState: FocusState? by remember { mutableStateOf(null) }
     val focusManager = LocalFocusManager.current
     var exitFocus by remember { mutableStateOf(false) }
 
-    LaunchedEffect(overlayVisible) {
-        overlayVisible.onAnimationCompletion {
-            // slide has loaded completely.
-            if (containerBoxFocusState?.isFocused == true) {
-                focusManager.moveFocus(FocusDirection.Enter)
-            }
-        }
+    var isVisible by remember { mutableStateOf(false) }
+
+    DisposableEffect(slideIndex) {
+        isVisible = true
+        onDispose { isVisible = false }
     }
 
     // This box holds the focus until the overlay animation completes
-    Box(modifier = modifier
-        .onKeyEvent {
-            exitFocus = it.key.nativeKeyCode == KeyEvent.KEYCODE_BACK && it.type == KeyDown
-            false
-        }
-        .onFocusChanged {
-            containerBoxFocusState = it
-            if (it.isFocused && exitFocus) {
-                focusManager.moveFocus(FocusDirection.Exit)
-                exitFocus = false
-            } else if (it.isFocused && overlayVisible.isIdle && overlayVisible.currentState) {
-                focusManager.moveFocus(FocusDirection.Enter)
+    Box(
+        modifier = modifier
+            .onKeyEvent {
+                exitFocus = it.isBackPress() && it.isTypeKeyDown()
+                ContinuePropagation
             }
-        }
-        .focusable()
+            .onFocusChanged {
+                containerBoxFocusState = it
+                if (it.isFocused && exitFocus) {
+                    focusManager.moveFocus(FocusDirection.Exit)
+                    exitFocus = false
+                }
+            }
+            .focusable()
     ) {
         background()
 
-        LaunchedEffect(overlayVisible) {
-            // After the delay, set overlay-visibility to true and trigger the animation to show the
-            // overlay.
-            delay(overlayEnterTransitionStartDelayMillis)
-            overlayVisible.targetState = true
-        }
-
         AnimatedVisibility(
-            modifier = Modifier.align(Alignment.BottomStart),
-            visibleState = overlayVisible,
-            enter = overlayEnterTransition,
-            exit = overlayExitTransition
+            visible = isVisible,
+            enter = contentTransform.targetContentEnter,
+            exit = contentTransform.initialContentExit,
         ) {
-            overlay.invoke()
+            LaunchedEffect(transition.isRunning, containerBoxFocusState?.isFocused) {
+                if (!transition.isRunning && containerBoxFocusState?.isFocused == true) {
+                    focusManager.moveFocus(FocusDirection.Enter)
+                }
+            }
+            content.invoke()
         }
     }
 }
 
-private suspend fun MutableTransitionState<Boolean>.onAnimationCompletion(
-    action: suspend () -> Unit
-) {
-    snapshotFlow { isIdle && currentState }.first { it }
-    action.invoke()
-}
-
 @ExperimentalTvMaterial3Api
 object CarouselItemDefaults {
     /**
-     * Default delay between the background being rendered and the overlay being rendered.
+     * Transform the content from right to left
      */
-    const val OverlayEnterTransitionStartDelayMillis: Long = 200
+    // Keeping this as public so that users can access it directly without the isLTR helper
+    @Suppress("IllegalExperimentalApiUsage")
+    @OptIn(ExperimentalAnimationApi::class)
+    val contentTransformRightToLeft: ContentTransform
+        @Composable get() =
+            slideInHorizontally { it * 4 }
+                .with(slideOutHorizontally { it * 4 })
 
     /**
-     * Default transition to bring the overlay into view.
+     * Transform the content from left to right
      */
-    val OverlayEnterTransition: EnterTransition = slideInHorizontally(initialOffsetX = { it * 4 })
+    // Keeping this as public so that users can access it directly without the isLTR helper
+    @Suppress("IllegalExperimentalApiUsage")
+    @OptIn(ExperimentalAnimationApi::class)
+    val contentTransformLeftToRight: ContentTransform
+        @Composable get() =
+            slideInHorizontally()
+                .with(slideOutHorizontally())
 
     /**
-     * Default transition to remove overlay from view.
+     * Content transform applied when moving forward taking isLTR into account
      */
-    val OverlayExitTransition: ExitTransition = slideOutHorizontally()
+    @Suppress("IllegalExperimentalApiUsage")
+    @OptIn(ExperimentalAnimationApi::class)
+    val contentTransformForward
+        @Composable get() =
+            if (isLtr())
+                contentTransformRightToLeft
+            else
+                contentTransformLeftToRight
+
+    /**
+     * Content transform applied when moving backward taking isLTR into account
+     */
+    @Suppress("IllegalExperimentalApiUsage")
+    @OptIn(ExperimentalAnimationApi::class)
+    val contentTransformBackward
+        @Composable get() =
+            if (isLtr())
+                contentTransformLeftToRight
+            else
+                contentTransformRightToLeft
 }
+
+@Composable
+private fun isLtr() = LocalLayoutDirection.current == LayoutDirection.Ltr
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/CarouselScope.kt b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselScope.kt
new file mode 100644
index 0000000..3cea56b
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/CarouselScope.kt
@@ -0,0 +1,69 @@
+/*
+ * Copyright 2023 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.ContentTransform
+import androidx.compose.animation.ExperimentalAnimationApi
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+
+/**
+ * CarouselScope provides a [CarouselScope.CarouselItem] function which you can use to
+ * provide the slide's animation, background and the inner content.
+ */
+@ExperimentalTvMaterial3Api
+class CarouselScope @OptIn(ExperimentalTvMaterial3Api::class)
+internal constructor(private val carouselState: CarouselState) {
+    /**
+     * [CarouselScope.CarouselItem] can be used to define a slide's animation, background, and
+     * content. Using this is optional and you can choose to define your own CarouselItem from
+     * scratch
+     *
+     * @param modifier modifier applied to the CarouselItem
+     * @param background composable defining the background of the slide
+     * @param contentTransformForward content transform to be applied to the content of the slide
+     * when scrolling forward in the carousel
+     * @param contentTransformBackward content transform to be applied to the content of the slide
+     * when scrolling backward in the carousel
+     * @param content composable defining the content displayed on top of the background
+     */
+    @Composable
+    @Suppress("IllegalExperimentalApiUsage")
+    @OptIn(ExperimentalAnimationApi::class)
+    @ExperimentalTvMaterial3Api
+    fun CarouselItem(
+        modifier: Modifier = Modifier,
+        background: @Composable () -> Unit = {},
+        contentTransformForward: ContentTransform =
+            CarouselItemDefaults.contentTransformForward,
+        contentTransformBackward: ContentTransform =
+            CarouselItemDefaults.contentTransformBackward,
+        content: @Composable () -> Unit
+    ) {
+        CarouselItem(
+            background = background,
+            slideIndex = carouselState.activeSlideIndex,
+            contentTransform =
+            if (carouselState.isMovingBackward)
+                contentTransformBackward
+            else
+                contentTransformForward,
+            modifier = modifier,
+            content = content,
+        )
+    }
+}
diff --git a/tv/tv-material/src/main/java/androidx/tv/material3/KeyEventUtils.kt b/tv/tv-material/src/main/java/androidx/tv/material3/KeyEventUtils.kt
new file mode 100644
index 0000000..d13b221
--- /dev/null
+++ b/tv/tv-material/src/main/java/androidx/tv/material3/KeyEventUtils.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.tv.material3
+
+import androidx.compose.ui.input.key.KeyEvent
+import androidx.compose.ui.input.key.KeyEventType.Companion.KeyDown
+import androidx.compose.ui.input.key.key
+import androidx.compose.ui.input.key.nativeKeyCode
+import androidx.compose.ui.input.key.type
+
+/**
+ * Checks if the `Back` key is pressed
+ */
+internal fun KeyEvent.isBackPress() = key.nativeKeyCode == android.view.KeyEvent.KEYCODE_BACK
+
+/**
+ * Checks if the keyEventType is `KeyDown`
+ */
+internal fun KeyEvent.isTypeKeyDown() = type == KeyDown
\ No newline at end of file
diff --git a/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/FakeDragTest.kt b/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/FakeDragTest.kt
index 7bae47b..95aee13 100644
--- a/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/FakeDragTest.kt
+++ b/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/FakeDragTest.kt
@@ -44,6 +44,7 @@
 import androidx.viewpager2.widget.ViewPager2.SCROLL_STATE_DRAGGING
 import org.hamcrest.CoreMatchers.equalTo
 import org.junit.Before
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -94,6 +95,7 @@
         selectOrientation(config.orientation)
     }
 
+    @Ignore // b/266477436
     @Test
     fun testFakeDragging() {
         // test if ViewPager2 goes to the next page when fake dragging
diff --git a/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/MarginPageTransformerTest.kt b/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/MarginPageTransformerTest.kt
index 24d9b2c..db37dc6 100644
--- a/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/MarginPageTransformerTest.kt
+++ b/viewpager2/integration-tests/testapp/src/androidTest/java/androidx/viewpager2/integration/testapp/test/MarginPageTransformerTest.kt
@@ -55,6 +55,7 @@
         testMargin(null)
     }
 
+    @Ignore // b/266476890
     @Test
     fun testMargin_offscreenLimit_default() {
         testMargin(ViewPager2.OFFSCREEN_PAGE_LIMIT_DEFAULT)
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
index 8273c04..1879ee12 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/DragWhileSmoothScrollTest.kt
@@ -35,6 +35,7 @@
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.greaterThan
 import org.junit.Assert.fail
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -67,6 +68,7 @@
 
     private lateinit var test: Context
 
+    @Ignore // b/266613081
     @Test
     fun test() {
         // given
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
index 2492604..3e6ff55 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentLifecycleTest.kt
@@ -24,6 +24,7 @@
 import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
 import org.hamcrest.CoreMatchers.equalTo
 import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.util.concurrent.CountDownLatch
@@ -83,6 +84,7 @@
         }
     }
 
+    @Ignore // b/266478005
     @Test
     fun test_setCurrentItem_offscreenPageLimit_default() {
         test_setCurrentItem_offscreenPageLimit(OFFSCREEN_PAGE_LIMIT_DEFAULT)
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
index eca228f..5b49e68 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/FragmentTransactionCallbackTest.kt
@@ -31,6 +31,7 @@
 import androidx.viewpager2.widget.ViewPager2.ORIENTATION_HORIZONTAL
 import org.hamcrest.MatcherAssert.assertThat
 import org.hamcrest.Matchers.equalTo
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.util.concurrent.CountDownLatch
@@ -224,6 +225,7 @@
         }
     }
 
+    @Ignore // b/266975014
     @Test
     fun test_fragmentSaveSateCallback() {
         setUpTest(ORIENTATION_HORIZONTAL).apply {
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
index b18447e..355402c 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/PageChangeCallbackTest.kt
@@ -46,6 +46,7 @@
 import org.hamcrest.Matchers.greaterThan
 import org.hamcrest.Matchers.greaterThanOrEqualTo
 import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -872,6 +873,7 @@
         )
     }
 
+    @Ignore // b/266613027
     @Test
     fun test_getScrollState() {
         val test = setUpTest(config.orientation)
@@ -1087,6 +1089,7 @@
         recorder.assertAllPagesSelected(testPages.flatMap { listOf(it, it + 1) })
     }
 
+    @Ignore // b/266613027
     @Test
     fun test_setCurrentItemWhileScrolling_maxIntItems() {
         val test = setUpTest(config.orientation)
diff --git a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
index 49debd1..fc0b05d1 100644
--- a/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
+++ b/viewpager2/viewpager2/src/androidTest/java/androidx/viewpager2/widget/SetItemWhileScrollInProgressTest.kt
@@ -32,6 +32,7 @@
 import org.hamcrest.CoreMatchers.not
 import org.hamcrest.CoreMatchers.nullValue
 import org.hamcrest.MatcherAssert.assertThat
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
@@ -133,6 +134,7 @@
         fun spec(): List<TestConfig> = createTestSet()
     }
 
+    @Ignore // b/266974735
     @Test
     fun test() {
         config.apply {
diff --git a/wear/compose/compose-navigation/build.gradle b/wear/compose/compose-navigation/build.gradle
index b85810f..4d48f3b 100644
--- a/wear/compose/compose-navigation/build.gradle
+++ b/wear/compose/compose-navigation/build.gradle
@@ -29,14 +29,17 @@
     api(project(":compose:runtime:runtime"))
     api("androidx.navigation:navigation-runtime:2.4.0")
     api(project(":wear:compose:compose-material"))
+    api(project(":activity:activity-compose"))
     api(project(":lifecycle:lifecycle-viewmodel-compose"))
 
     implementation(libs.kotlinStdlib)
+    implementation(project(":navigation:navigation-common"))
     implementation("androidx.navigation:navigation-compose:2.4.0")
     implementation("androidx.profileinstaller:profileinstaller:1.2.0")
 
     androidTestImplementation(project(":compose:test-utils"))
     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"))
diff --git a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
index e35c959..4ad9e64 100644
--- a/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
+++ b/wear/compose/compose-navigation/src/androidTest/kotlin/androidx/wear/compose/navigation/SwipeDismissableNavHostTest.kt
@@ -45,6 +45,7 @@
 import androidx.compose.ui.test.swipeRight
 import androidx.compose.ui.unit.dp
 import androidx.lifecycle.Lifecycle
+import androidx.lifecycle.LifecycleOwner
 import androidx.lifecycle.testing.TestLifecycleOwner
 import androidx.navigation.NavBackStackEntry
 import androidx.navigation.NavHostController
@@ -99,12 +100,11 @@
 
     @Test
     fun navigates_back_to_previous_level_with_back_button() {
-        val lifecycleOwner = TestLifecycleOwner()
         val onBackPressedDispatcher = OnBackPressedDispatcher()
-        val dispatcherOwner = object : OnBackPressedDispatcherOwner {
-            override fun getLifecycle() = lifecycleOwner.lifecycle
-            override fun getOnBackPressedDispatcher() = onBackPressedDispatcher
-        }
+        val dispatcherOwner =
+            object : OnBackPressedDispatcherOwner, LifecycleOwner by TestLifecycleOwner() {
+                override val onBackPressedDispatcher = onBackPressedDispatcher
+            }
         lateinit var navController: NavHostController
 
         rule.setContentWithTheme {
diff --git a/wear/watchface/watchface-client/api/current.ignore b/wear/watchface/watchface-client/api/current.ignore
index 43ce51a..8c1c73c 100644
--- a/wear/watchface/watchface-client/api/current.ignore
+++ b/wear/watchface/watchface-client/api/current.ignore
@@ -1,3 +1,23 @@
 // Baseline format: 1.0
 AddedAbstractMethod: androidx.wear.watchface.client.WatchFaceMetadataClient#getUserStyleFlavors():
     Added method androidx.wear.watchface.client.WatchFaceMetadataClient.getUserStyleFlavors()
+
+
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getComplicationSlotsState():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getComplicationSlotsState added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getPreviewReferenceInstant():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getPreviewReferenceInstant added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getUserStyleSchema():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getUserStyleSchema added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getComplicationSlotsState():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getComplicationSlotsState added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getContentDescriptionLabels():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getContentDescriptionLabels added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getInstanceId():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getInstanceId added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getOverlayStyle():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getOverlayStyle added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getPreviewReferenceInstant():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getPreviewReferenceInstant added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getUserStyleSchema():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getUserStyleSchema added thrown exception android.os.RemoteException
diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt
index aba37f5..4e6173b 100644
--- a/wear/watchface/watchface-client/api/current.txt
+++ b/wear/watchface/watchface-client/api/current.txt
@@ -106,10 +106,10 @@
   public interface HeadlessWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
     method public default static androidx.wear.watchface.client.HeadlessWatchFaceClient createFromBundle(android.os.Bundle bundle);
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
     method public default androidx.wear.watchface.style.UserStyleFlavors getUserStyleFlavors();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default byte[] getUserStyleSchemaDigestHash() throws android.os.RemoteException;
     method @AnyThread public boolean isConnectionAlive();
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener);
@@ -135,12 +135,12 @@
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
     method public void addOnWatchFaceReadyListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default Integer? getComplicationIdAt(@Px int x, @Px int y) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method public default boolean isComplicationDisplayPolicySupported();
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
diff --git a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
index f0ae340..b48fa19 100644
--- a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
@@ -110,10 +110,10 @@
   public interface HeadlessWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
     method public default static androidx.wear.watchface.client.HeadlessWatchFaceClient createFromBundle(android.os.Bundle bundle);
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
     method public default androidx.wear.watchface.style.UserStyleFlavors getUserStyleFlavors();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default byte[] getUserStyleSchemaDigestHash() throws android.os.RemoteException;
     method @AnyThread public boolean isConnectionAlive();
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener);
@@ -140,12 +140,12 @@
     method @androidx.wear.watchface.client.WatchFaceClientExperimental public default void addOnWatchFaceColorsListener(java.util.concurrent.Executor executor, java.util.function.Consumer<androidx.wear.watchface.WatchFaceColors> listener);
     method public void addOnWatchFaceReadyListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default Integer? getComplicationIdAt(@Px int x, @Px int y) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method public default boolean isComplicationDisplayPolicySupported();
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
diff --git a/wear/watchface/watchface-client/api/restricted_current.ignore b/wear/watchface/watchface-client/api/restricted_current.ignore
index 43ce51a..8c1c73c 100644
--- a/wear/watchface/watchface-client/api/restricted_current.ignore
+++ b/wear/watchface/watchface-client/api/restricted_current.ignore
@@ -1,3 +1,23 @@
 // Baseline format: 1.0
 AddedAbstractMethod: androidx.wear.watchface.client.WatchFaceMetadataClient#getUserStyleFlavors():
     Added method androidx.wear.watchface.client.WatchFaceMetadataClient.getUserStyleFlavors()
+
+
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getComplicationSlotsState():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getComplicationSlotsState added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getPreviewReferenceInstant():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getPreviewReferenceInstant added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.HeadlessWatchFaceClient#getUserStyleSchema():
+    Method androidx.wear.watchface.client.HeadlessWatchFaceClient.getUserStyleSchema added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getComplicationSlotsState():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getComplicationSlotsState added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getContentDescriptionLabels():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getContentDescriptionLabels added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getInstanceId():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getInstanceId added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getOverlayStyle():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getOverlayStyle added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getPreviewReferenceInstant():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getPreviewReferenceInstant added thrown exception android.os.RemoteException
+ChangedThrows: androidx.wear.watchface.client.InteractiveWatchFaceClient#getUserStyleSchema():
+    Method androidx.wear.watchface.client.InteractiveWatchFaceClient.getUserStyleSchema added thrown exception android.os.RemoteException
diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt
index aba37f5..4e6173b 100644
--- a/wear/watchface/watchface-client/api/restricted_current.txt
+++ b/wear/watchface/watchface-client/api/restricted_current.txt
@@ -106,10 +106,10 @@
   public interface HeadlessWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
     method public default static androidx.wear.watchface.client.HeadlessWatchFaceClient createFromBundle(android.os.Bundle bundle);
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
     method public default androidx.wear.watchface.style.UserStyleFlavors getUserStyleFlavors();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default byte[] getUserStyleSchemaDigestHash() throws android.os.RemoteException;
     method @AnyThread public boolean isConnectionAlive();
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.HeadlessWatchFaceClient.ClientDisconnectListener listener);
@@ -135,12 +135,12 @@
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
     method public void addOnWatchFaceReadyListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default Integer? getComplicationIdAt(@Px int x, @Px int y) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant();
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema();
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.Map<java.lang.Integer,androidx.wear.watchface.client.ComplicationSlotState> getComplicationSlotsState() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.util.List<androidx.wear.watchface.ContentDescriptionLabel> getContentDescriptionLabels() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public String getInstanceId() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default androidx.wear.watchface.client.OverlayStyle getOverlayStyle() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public java.time.Instant getPreviewReferenceInstant() throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.style.UserStyleSchema getUserStyleSchema() throws android.os.RemoteException;
     method public default boolean isComplicationDisplayPolicySupported();
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
diff --git a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
index bc848ab..4742770 100644
--- a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
+++ b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataSourceServiceTest.java
@@ -32,13 +32,14 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.wear.protolayout.expression.DynamicBuilders;
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
 import androidx.wear.watchface.complications.data.ComplicationData;
 import androidx.wear.watchface.complications.data.ComplicationText;
 import androidx.wear.watchface.complications.data.ComplicationType;
 import androidx.wear.watchface.complications.data.LongTextComplicationData;
 import androidx.wear.watchface.complications.data.PlainComplicationText;
-import androidx.wear.watchface.complications.data.StringExpression;
-import androidx.wear.watchface.complications.data.StringExpressionComplicationText;
+import androidx.wear.watchface.complications.data.ComplicationTextExpression;
 
 import org.junit.After;
 import org.junit.Before;
@@ -195,8 +196,9 @@
             throws Exception {
         mService.responseData =
                 new LongTextComplicationData.Builder(
-                        new StringExpressionComplicationText(
-                                new StringExpression(new byte[]{1, 2})),
+                        new ComplicationTextExpression(
+                                DynamicString.constant("hello").concat(
+                                        DynamicString.constant(" world"))),
                         ComplicationText.EMPTY)
                         .build();
 
@@ -209,8 +211,9 @@
         verify(mRemoteManager).updateComplicationData(
                 eq(123),
                 eq(new LongTextComplicationData.Builder(
-                        new StringExpressionComplicationText(
-                                new StringExpression(new byte[]{1, 2})),
+                        new ComplicationTextExpression(
+                                DynamicString.constant("hello").concat(
+                                        DynamicString.constant(" world"))),
                         ComplicationText.EMPTY)
                         .build()
                         .asWireComplicationData()));
@@ -222,8 +225,9 @@
             throws Exception {
         mService.responseData =
                 new LongTextComplicationData.Builder(
-                        new StringExpressionComplicationText(
-                                new StringExpression(new byte[]{1, 2})),
+                        new ComplicationTextExpression(
+                                DynamicString.constant("hello").concat(
+                                        DynamicString.constant(" world"))),
                         ComplicationText.EMPTY)
                         .build();
 
@@ -236,9 +240,10 @@
         verify(mRemoteManager).updateComplicationData(
                 eq(123),
                 eq(new LongTextComplicationData.Builder(
-                        // TODO(b/260065006): Verify that it is actually evaluated.
-                        new StringExpressionComplicationText(
-                                new StringExpression(new byte[]{1, 2})),
+                        // TODO(b/260065006): new PlainComplicationText.Builder("hello world")
+                        new ComplicationTextExpression(
+                                DynamicString.constant("hello").concat(
+                                        DynamicString.constant(" world"))),
                         ComplicationText.EMPTY)
                         .build()
                         .asWireComplicationData()));
diff --git a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
index 4dccf73..8ea484e 100644
--- a/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
+++ b/wear/watchface/watchface-complications-data-source/src/test/java/androidx/wear/watchface/complications/datasource/ComplicationDataTimelineTest.java
@@ -132,9 +132,9 @@
         assertThat(TIMELINE_A.toString()).isEqualTo(
                 "ComplicationDataTimeline(defaultComplicationData=ShortTextComplicationData("
                         + "text=ComplicationText{mSurroundingText=Hello, mTimeDependentText=null, "
-                        + "mStringExpression=null}, title=null, monochromaticImage=null, "
+                        + "mExpression=null}, title=null, monochromaticImage=null, "
                         + "smallImage=null, contentDescription=ComplicationText{"
-                        + "mSurroundingText=, mTimeDependentText=null, mStringExpression=null}, "
+                        + "mSurroundingText=, mTimeDependentText=null, mExpression=null}, "
                         + "tapActionLostDueToSerialization=false, tapAction=null, "
                         + "validTimeRange=TimeRange(startDateTimeMillis="
                         + "-1000000000-01-01T00:00:00Z, endDateTimeMillis="
@@ -143,10 +143,10 @@
                         + "TimelineEntry(validity=TimeInterval(start=1970-01-02T03:46:40Z, "
                         + "end=1970-01-03T07:33:20Z), complicationData=ShortTextComplicationData("
                         + "text=ComplicationText{mSurroundingText=Updated, "
-                        + "mTimeDependentText=null, mStringExpression=null}, title=null, "
+                        + "mTimeDependentText=null, mExpression=null}, title=null, "
                         + "monochromaticImage=null, smallImage=null, "
                         + "contentDescription=ComplicationText{mSurroundingText=, "
-                        + "mTimeDependentText=null, mStringExpression=null}, "
+                        + "mTimeDependentText=null, mExpression=null}, "
                         + "tapActionLostDueToSerialization=false, tapAction=null, "
                         + "validTimeRange=TimeRange(startDateTimeMillis="
                         + "-1000000000-01-01T00:00:00Z, endDateTimeMillis="
diff --git a/wear/watchface/watchface-complications-data/api/current.txt b/wear/watchface/watchface-complications-data/api/current.txt
index c657cfb..40675d8 100644
--- a/wear/watchface/watchface-complications-data/api/current.txt
+++ b/wear/watchface/watchface-complications-data/api/current.txt
@@ -94,9 +94,6 @@
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
   }
 
-  public final class FloatExpressionKt {
-  }
-
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp();
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
diff --git a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
index 0bb17ae..d49c322 100644
--- a/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-complications-data/api/public_plus_experimental_current.txt
@@ -97,9 +97,6 @@
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
   }
 
-  public final class FloatExpressionKt {
-  }
-
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp();
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
diff --git a/wear/watchface/watchface-complications-data/api/restricted_current.txt b/wear/watchface/watchface-complications-data/api/restricted_current.txt
index f0e643f..58155a5 100644
--- a/wear/watchface/watchface-complications-data/api/restricted_current.txt
+++ b/wear/watchface/watchface-complications-data/api/restricted_current.txt
@@ -94,9 +94,6 @@
     field public static final androidx.wear.watchface.complications.data.ComplicationType TYPE;
   }
 
-  public final class FloatExpressionKt {
-  }
-
   @RequiresApi(android.os.Build.VERSION_CODES.TIRAMISU) public final class GoalProgressComplicationData extends androidx.wear.watchface.complications.data.ComplicationData {
     method public androidx.wear.watchface.complications.data.ColorRamp? getColorRamp();
     method public androidx.wear.watchface.complications.data.ComplicationText? getContentDescription();
diff --git a/wear/watchface/watchface-complications-data/build.gradle b/wear/watchface/watchface-complications-data/build.gradle
index 953db5b..3b9d23c 100644
--- a/wear/watchface/watchface-complications-data/build.gradle
+++ b/wear/watchface/watchface-complications-data/build.gradle
@@ -30,6 +30,11 @@
     api("androidx.versionedparcelable:versionedparcelable:1.1.0")
     api(libs.kotlinStdlib)
     api(libs.kotlinCoroutinesAndroid)
+
+    // TODO(b/267170061): Use released protolayout-expression, remove protolayout-proto.
+    api(project(":wear:protolayout:protolayout-expression"))
+    implementation(project(":wear:protolayout:protolayout-proto"))
+
     implementation("androidx.core:core:1.1.0")
     implementation("androidx.preference:preference:1.1.0")
     implementation("androidx.annotation:annotation:1.2.0")
@@ -44,6 +49,7 @@
     testImplementation(libs.junit)
     testImplementation(libs.kotlinTest)
     testImplementation(libs.testParameterInjector)
+    testImplementation(libs.guavaTestlib)
 
     annotationProcessor(project(":versionedparcelable:versionedparcelable-compiler"))
 }
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
index 41954c9..813758c 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationData.kt
@@ -30,12 +30,11 @@
 import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicies
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicy
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicy
-import androidx.wear.watchface.complications.data.FloatExpression
-import androidx.wear.watchface.complications.data.toFloatExpression
 import androidx.wear.watchface.utility.iconEquals
 import androidx.wear.watchface.utility.iconHashCode
 import java.io.IOException
@@ -43,6 +42,7 @@
 import java.io.ObjectInputStream
 import java.io.ObjectOutputStream
 import java.io.Serializable
+import java.util.Arrays
 import java.util.Objects
 
 /**
@@ -179,7 +179,7 @@
             }
             if (isFieldValidForType(FIELD_VALUE_EXPRESSION, type)) {
                 oos.writeNullable(complicationData.rangedValueExpression) {
-                    oos.writeByteArray(it.asByteArray())
+                    oos.writeByteArray(it.toDynamicFloatByteArray())
                 }
             }
             if (isFieldValidForType(FIELD_VALUE_TYPE, type)) {
@@ -607,10 +607,11 @@
      * Valid only if the type of this complication data is [TYPE_RANGED_VALUE] and
      * [TYPE_GOAL_PROGRESS].
      */
-    val rangedValueExpression: FloatExpression?
+    val rangedValueExpression: DynamicFloat?
         get() {
             checkFieldValidForTypeWithoutThrowingException(FIELD_VALUE_EXPRESSION, type)
-            return fields.getByteArray(FIELD_VALUE_EXPRESSION)?.toFloatExpression()
+            return fields.getByteArray(FIELD_VALUE_EXPRESSION)
+                ?.let { DynamicFloat.fromByteArray(it) }
         }
 
     /**
@@ -1185,7 +1186,8 @@
             equalsWithoutExpressions(other) &&
             (!isFieldValidForType(FIELD_VALUE, type) || rangedValue == other.rangedValue) &&
             (!isFieldValidForType(FIELD_VALUE_EXPRESSION, type) ||
-                rangedValueExpression == other.rangedValueExpression) &&
+                rangedValueExpression?.toDynamicFloatByteArray() contentEquals
+                other.rangedValueExpression?.toDynamicFloatByteArray()) &&
             (!isFieldValidForType(FIELD_SHORT_TITLE, type) || shortTitle == other.shortTitle) &&
             (!isFieldValidForType(FIELD_SHORT_TEXT, type) || shortText == other.shortText) &&
             (!isFieldValidForType(FIELD_LONG_TITLE, type) || longTitle == other.longTitle) &&
@@ -1202,7 +1204,8 @@
             ) {
                 !isFieldValidForType(FIELD_VALUE, type) || rangedValue == other.rangedValue
             } else {
-                rangedValueExpression == other.rangedValueExpression
+                rangedValueExpression?.toDynamicFloatByteArray() contentEquals
+                    other.rangedValueExpression?.toDynamicFloatByteArray()
             } &&
             (!isFieldValidForType(FIELD_SHORT_TITLE, type) ||
                 shortTitle equalsUnevaluated other.shortTitle) &&
@@ -1219,11 +1222,14 @@
                     ((placeholder != null && other.placeholder != null) &&
                         placeholder!! equalsUnevaluated other.placeholder!!)))
 
-    private infix fun ComplicationText?.equalsUnevaluated(other: ComplicationText?): Boolean =
-        (this == null && other == null) ||
-            ((this != null && other != null) &&
-                if (stringExpression == null) equals(other)
-                else stringExpression == other.stringExpression)
+    private infix fun ComplicationText?.equalsUnevaluated(other: ComplicationText?): Boolean {
+        if (this == null && other == null) return true
+        if (this == null || other == null) return false
+        // Both are non-null.
+        if (stringExpression == null) return equals(other)
+        return stringExpression?.toDynamicStringByteArray() contentEquals
+            other.stringExpression?.toDynamicStringByteArray()
+    }
 
     private fun equalsWithoutExpressions(other: ComplicationData): Boolean =
         this === other || (
@@ -1305,7 +1311,11 @@
         if (isFieldValidForType(EXP_FIELD_LIST_ENTRIES, type)) listEntries else null,
         if (isFieldValidForType(FIELD_DATA_SOURCE, type)) dataSource else null,
         if (isFieldValidForType(FIELD_VALUE, type)) rangedValue else null,
-        if (isFieldValidForType(FIELD_VALUE_EXPRESSION, type)) rangedValueExpression else null,
+        if (isFieldValidForType(FIELD_VALUE_EXPRESSION, type)) {
+            Arrays.hashCode(rangedValueExpression?.toDynamicFloatByteArray())
+        } else {
+            null
+        },
         if (isFieldValidForType(FIELD_VALUE_TYPE, type)) rangedValueType else null,
         if (isFieldValidForType(FIELD_MIN_VALUE, type)) rangedMinValue else null,
         if (isFieldValidForType(FIELD_MAX_VALUE, type)) rangedMaxValue else null,
@@ -1467,8 +1477,8 @@
          *
          * @throws IllegalStateException if this field is not valid for the complication type
          */
-        fun setRangedValueExpression(value: FloatExpression?) =
-            apply { putOrRemoveField(FIELD_VALUE_EXPRESSION, value?.asByteArray()) }
+        fun setRangedValueExpression(value: DynamicFloat?) =
+            apply { putOrRemoveField(FIELD_VALUE_EXPRESSION, value?.toDynamicFloatByteArray()) }
 
         /**
          * Sets the *value type* field which provides meta data about the value. This is
diff --git a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationText.java b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationText.java
index 58f43c9..cefbd65 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationText.java
+++ b/wear/watchface/watchface-complications-data/src/main/java/android/support/wearable/complications/ComplicationText.java
@@ -36,8 +36,7 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.annotation.VisibleForTesting;
-import androidx.wear.watchface.complications.data.StringExpression;
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString;
 
 import java.io.IOException;
 import java.io.InvalidObjectException;
@@ -46,6 +45,7 @@
 import java.io.Serializable;
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
+import java.util.Arrays;
 import java.util.Objects;
 import java.util.TimeZone;
 import java.util.concurrent.TimeUnit;
@@ -76,17 +76,32 @@
             return false;
         }
         ComplicationText that = (ComplicationText) o;
-        if (!Objects.equals(mStringExpression, that.mStringExpression)) {
-            return false;
+        if (mExpression == null) {
+            if (that.mExpression != null) {
+                return false;
+            }
+        } else {
+            if (that.mExpression == null) {
+                return false;
+            } else if (
+                    !Arrays.equals(mExpression.toDynamicStringByteArray(),
+                            that.mExpression.toDynamicStringByteArray())
+            ) {
+                return false;
+            }
         }
         if (!Objects.equals(mTimeDependentText, that.mTimeDependentText)) {
             return false;
         }
         if (mSurroundingText == null) {
-            return that.mSurroundingText == null;
-        } else {
             if (that.mSurroundingText != null) {
-                return mSurroundingText.toString().contentEquals(that.mSurroundingText);
+                return false;
+            }
+        } else {
+            if (that.mSurroundingText == null) {
+                return false;
+            } else if (!mSurroundingText.toString().contentEquals(that.mSurroundingText)) {
+                return false;
             }
         }
         return true;
@@ -94,16 +109,20 @@
 
     @Override
     public int hashCode() {
-        return Objects.hash(mSurroundingText, mTimeDependentText, mStringExpression);
+        return Objects.hash(
+                mSurroundingText,
+                mTimeDependentText,
+                mExpression == null ?
+                        null : Arrays.hashCode(mExpression.toDynamicStringByteArray()));
     }
 
     @NonNull
     @Override
     public String toString() {
-        return "ComplicationText{" + "mSurroundingText="
-                + ComplicationData.maybeRedact(mSurroundingText)
-                + ", mTimeDependentText=" + mTimeDependentText + ", mStringExpression="
-                + mStringExpression + "}";
+        return "ComplicationText{"
+                + "mSurroundingText=" + ComplicationData.maybeRedact(mSurroundingText)
+                + ", mTimeDependentText=" + mTimeDependentText
+                + ", mExpression=" + mExpression + "}";
     }
 
     /** @hide */
@@ -274,9 +293,9 @@
     @Nullable
     private final TimeDependentText mTimeDependentText;
 
-    /** A [StringExpression] which will be evaluated by the system on the WatchFace's behalf. */
+    /** A {@link DynamicString} which will be evaluated by the system on the WatchFace's behalf. */
     @Nullable
-    private final StringExpression mStringExpression;
+    private final DynamicString mExpression;
 
     /** Used to replace occurrences of ^1 with time dependent text and ignore ^[2-9]. */
     private final CharSequence[] mTemplateValues =
@@ -290,29 +309,29 @@
     private ComplicationText(
             @Nullable CharSequence surroundingText,
             @Nullable TimeDependentText timeDependentText,
-            @Nullable StringExpression stringExpression) {
+            @Nullable DynamicString expression) {
         mSurroundingText = surroundingText;
         mTimeDependentText = timeDependentText;
-        mStringExpression = stringExpression;
+        mExpression = expression;
         checkFields();
     }
 
     public ComplicationText(@NonNull CharSequence surroundingText) {
-        this(surroundingText, /* timeDependentText = */ null, /* stringExpression = */ null);
+        this(surroundingText, /* timeDependentText = */ null, /* expression = */ null);
     }
 
     public ComplicationText(
             @NonNull CharSequence surroundingText, @NonNull TimeDependentText timeDependentText) {
-        this(surroundingText, timeDependentText, /* stringExpression = */ null);
+        this(surroundingText, timeDependentText, /* expression = */ null);
     }
 
     public ComplicationText(
-            @NonNull CharSequence surroundingText, @NonNull StringExpression stringExpression) {
-        this(surroundingText, /* timeDependentText = */ null, stringExpression);
+            @NonNull CharSequence surroundingText, @NonNull DynamicString expression) {
+        this(surroundingText, /* timeDependentText = */ null, expression);
     }
 
-    public ComplicationText(@NonNull StringExpression stringExpression) {
-        this(/* surroundingText = */ null, /* timeDependentText = */ null, stringExpression);
+    public ComplicationText(@NonNull DynamicString expression) {
+        this(/* surroundingText = */ null, /* timeDependentText = */ null, expression);
     }
 
     private ComplicationText(@NonNull Parcel in) {
@@ -320,9 +339,9 @@
         mSurroundingText = bundle.getCharSequence(KEY_SURROUNDING_STRING);
 
         if (bundle.containsKey(KEY_STRING_EXPRESSION)) {
-            mStringExpression = new StringExpression(bundle.getByteArray(KEY_STRING_EXPRESSION));
+            mExpression = DynamicString.fromByteArray(bundle.getByteArray(KEY_STRING_EXPRESSION));
         } else {
-            mStringExpression = null;
+            mExpression = null;
         }
 
         if (bundle.containsKey(KEY_DIFFERENCE_STYLE)
@@ -355,23 +374,23 @@
     private static class SerializedForm implements Serializable {
         CharSequence mSurroundingText;
         TimeDependentText mTimeDependentText;
-        StringExpression mStringExpression;
+        DynamicString mExpression;
 
         SerializedForm(@Nullable CharSequence surroundingText,
                 @Nullable TimeDependentText timeDependentText,
-                @Nullable StringExpression stringExpression) {
+                @Nullable DynamicString expression) {
             mSurroundingText = surroundingText;
             mTimeDependentText = timeDependentText;
-            mStringExpression = stringExpression;
+            mExpression = expression;
         }
 
         private void writeObject(ObjectOutputStream oos) throws IOException {
             CharSequenceSerializableHelper.writeToStream(mSurroundingText, oos);
             oos.writeObject(mTimeDependentText);
-            if (mStringExpression == null) {
+            if (mExpression == null) {
                 oos.writeInt(0);
             } else {
-                byte[] bytes = mStringExpression.asByteArray();
+                byte[] bytes = mExpression.toDynamicStringByteArray();
                 oos.writeInt(bytes.length);
                 oos.write(bytes);
             }
@@ -382,22 +401,22 @@
             mTimeDependentText = (TimeDependentText) ois.readObject();
             int length = ois.readInt();
             if (length == 0) {
-                mStringExpression = null;
+                mExpression = null;
             } else {
                 byte[] bytes = new byte[length];
                 ois.readFully(bytes);
-                mStringExpression = new StringExpression(bytes);
+                mExpression = DynamicString.fromByteArray(bytes);
             }
         }
 
         @SuppressLint("SyntheticAccessor")
         Object readResolve() {
-            return new ComplicationText(mSurroundingText, mTimeDependentText, mStringExpression);
+            return new ComplicationText(mSurroundingText, mTimeDependentText, mExpression);
         }
     }
 
     Object writeReplace() {
-        return new SerializedForm(mSurroundingText, mTimeDependentText, mStringExpression);
+        return new SerializedForm(mSurroundingText, mTimeDependentText, mExpression);
     }
 
     private void readObject(ObjectInputStream stream) throws InvalidObjectException {
@@ -421,10 +440,9 @@
     }
 
     private void checkFields() {
-        if (mSurroundingText == null && mTimeDependentText == null && mStringExpression == null) {
+        if (mSurroundingText == null && mTimeDependentText == null && mExpression == null) {
             throw new IllegalStateException(
-                    "One of mSurroundingText, mTimeDependentText and mStringExpression must be"
-                            + " non-null");
+                    "One of mSurroundingText, mTimeDependentText and mExpression must be non-null");
         }
     }
 
@@ -439,8 +457,8 @@
         Bundle bundle = new Bundle();
         bundle.putCharSequence(KEY_SURROUNDING_STRING, mSurroundingText);
 
-        if (mStringExpression != null) {
-            bundle.putByteArray(KEY_STRING_EXPRESSION, mStringExpression.asByteArray());
+        if (mExpression != null) {
+            bundle.putByteArray(KEY_STRING_EXPRESSION, mExpression.toDynamicStringByteArray());
         }
 
         if (mTimeDependentText != null) {
@@ -476,7 +494,7 @@
     @NonNull
     @RestrictTo(RestrictTo.Scope.LIBRARY)
     public TimeDependentText getTimeDependentText() {
-        if (mStringExpression != null) {
+        if (mExpression != null) {
             throw new UnsupportedOperationException("getTimeDependentText not supported for "
                     + "StringExpressions");
         }
@@ -500,7 +518,7 @@
     @NonNull
     @Override
     public CharSequence getTextAt(@NonNull Resources resources, long dateTimeMillis) {
-        if (mStringExpression != null && mTimeDependentText == null && mSurroundingText == null) {
+        if (mExpression != null && mTimeDependentText == null && mSurroundingText == null) {
             throw new UnsupportedOperationException("getTextAt not supported for "
                     + "StringExpressions");
         }
@@ -533,10 +551,10 @@
         return mSurroundingText;
     }
 
-    /** Returns the {@link StringExpression} to be evaluated to display this text. */
+    /** Returns the {@link DynamicString} to be evaluated to display this text. */
     @Nullable
-    public StringExpression getStringExpression() {
-        return mStringExpression;
+    public DynamicString getStringExpression() {
+        return mExpression;
     }
 
     /** Whether or not this is a placeholder. */
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
index 21f4976..444a7d1 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Data.kt
@@ -31,6 +31,7 @@
 import androidx.annotation.IntDef
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
 import androidx.wear.watchface.complications.data.GoalProgressComplicationData.Companion.PLACEHOLDER
 import androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion.PLACEHOLDER
 import androidx.wear.watchface.complications.data.RangedValueComplicationData.Companion.TYPE_RATING
@@ -847,7 +848,7 @@
 public class RangedValueComplicationData internal constructor(
     public val value: Float,
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    valueExpression: FloatExpression?,
+    valueExpression: DynamicFloat?,
     public val min: Float,
     public val max: Float,
     public val monochromaticImage: MonochromaticImage?,
@@ -873,13 +874,13 @@
     displayPolicy = displayPolicy
 ) {
     /**
-     * The [FloatExpression] optionally set by the data source. If present the system will
+     * The [DynamicFloat] optionally set by the data source. If present the system will
      * dynamically evaluate this and store the result in [value]. Watch faces can typically ignore
      * this field.
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public val valueExpression: FloatExpression? = valueExpression
+    public val valueExpression: DynamicFloat? = valueExpression
 
     /** @hide */
     @IntDef(value = [TYPE_UNDEFINED, TYPE_RATING, TYPE_PERCENTAGE])
@@ -896,7 +897,7 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public constructor(
         private val value: Float,
-        private val valueExpression: FloatExpression?,
+        private val valueExpression: DynamicFloat?,
         private val min: Float,
         private val max: Float,
         private var contentDescription: ComplicationText
@@ -920,9 +921,9 @@
         ) : this(value, valueExpression = null, min, max, contentDescription)
 
         /**
-         * Creates a [Builder] for a [RangedValueComplicationData] with a [FloatExpression] value.
+         * Creates a [Builder] for a [RangedValueComplicationData] with a [DynamicFloat] value.
          *
-         * @param valueExpression The [FloatExpression] of the ranged complication which will be
+         * @param valueExpression The [DynamicFloat] of the ranged complication which will be
          * evaluated into a value dynamically, and should be in the range [[min]] .. [[max]]. The
          * semantic meaning of value can be specified via [setValueType].
          * @param min The minimum value. For [TYPE_PERCENTAGE] this must be 0f.
@@ -934,7 +935,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public constructor(
-            valueExpression: FloatExpression,
+            valueExpression: DynamicFloat,
             min: Float,
             max: Float,
             contentDescription: ComplicationText
@@ -1203,7 +1204,7 @@
 internal constructor(
     public val value: Float,
     @Suppress("OPT_IN_MARKER_ON_WRONG_TARGET")
-    valueExpression: FloatExpression?,
+    valueExpression: DynamicFloat?,
     public val targetValue: Float,
     public val monochromaticImage: MonochromaticImage?,
     public val smallImage: SmallImage?,
@@ -1227,13 +1228,13 @@
     displayPolicy = displayPolicy
 ) {
     /**
-     * The [FloatExpression] optionally set by the data source. If present the system will
+     * The [DynamicFloat] optionally set by the data source. If present the system will
      * dynamically evaluate this and store the result in [value]. Watch faces can typically ignore
      * this field.
      * @hide
      */
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public val valueExpression: FloatExpression? = valueExpression
+    public val valueExpression: DynamicFloat? = valueExpression
 
     /**
      * Builder for [GoalProgressComplicationData].
@@ -1247,7 +1248,7 @@
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
     public constructor(
         private val value: Float,
-        private val valueExpression: FloatExpression?,
+        private val valueExpression: DynamicFloat?,
         private val targetValue: Float,
         private var contentDescription: ComplicationText
     ) : BaseBuilder<Builder, GoalProgressComplicationData>() {
@@ -1266,9 +1267,9 @@
         ) : this(value, valueExpression = null, targetValue, contentDescription)
 
         /**
-         * Creates a [Builder] for a [GoalProgressComplicationData] with a [FloatExpression] value.
+         * Creates a [Builder] for a [GoalProgressComplicationData] with a [DynamicFloat] value.
          *
-         * @param valueExpression The [FloatExpression] of the goal complication which will be
+         * @param valueExpression The [DynamicFloat] of the goal complication which will be
          * evaluated into a value dynamically, and should be >= 0.
          * @param targetValue The target value. This must be less than [Float.MAX_VALUE].
          * @param contentDescription Localized description for use by screen readers. Please do not
@@ -1277,7 +1278,7 @@
          */
         @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
         public constructor(
-            valueExpression: FloatExpression,
+            valueExpression: DynamicFloat,
             targetValue: Float,
             contentDescription: ComplicationText
         ) : this(
@@ -1744,7 +1745,7 @@
          * large  number of elements we likely won't be able to render them properly because the
          * individual elements will be too small on screen. */
         @JvmStatic
-        public fun getMaxElements() = 20
+        public fun getMaxElements() = 7
     }
 }
 
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/FloatExpression.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/FloatExpression.kt
deleted file mode 100644
index 5d2215e..0000000
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/FloatExpression.kt
+++ /dev/null
@@ -1,49 +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.watchface.complications.data
-
-import androidx.annotation.RestrictTo
-
-/**
- * Placeholder for FloatExpression implementation by tiles.
- * @hide
- */
-// TODO(b/260065006): Replace this with the real implementation.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-abstract class FloatExpression {
-    abstract fun asByteArray(): ByteArray
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        // Not checking for exact same class because it's not implemented yet.
-        if (other !is FloatExpression) return false
-        return asByteArray().contentEquals(other.asByteArray())
-    }
-
-    override fun hashCode() = asByteArray().contentHashCode()
-
-    override fun toString() = "FloatExpressionPlaceholder${asByteArray().contentToString()}"
-}
-
-/**
- * Placeholder parser for [FloatExpression] from [ByteArray].
- * @hide
- */
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-fun ByteArray.toFloatExpression() = object : FloatExpression() {
-    override fun asByteArray() = this@toFloatExpression
-}
\ No newline at end of file
diff --git a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
index 5887dd6..22f7fc7 100644
--- a/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
+++ b/wear/watchface/watchface-complications-data/src/main/java/androidx/wear/watchface/complications/data/Text.kt
@@ -34,6 +34,7 @@
 import android.text.style.TypefaceSpan
 import android.text.style.UnderlineSpan
 import androidx.annotation.RestrictTo
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
 import java.time.Instant
 import java.util.concurrent.TimeUnit
 
@@ -628,43 +629,15 @@
     DelegatingTimeDependentText(this)
 
 /**
- * This is a placeholder for the tiles StringExpression which isn't currently available. We'll
- * remove this later in favor of the real thing.
- * @hide
- */
-// TODO(b/260065006): Remove this in favor of the real thing when available.
-@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class StringExpression(private val expression: ByteArray) {
-    fun asByteArray() = expression
-
-    override fun toString(): String {
-        return "StringExpression(expression=${expression.contentToString()})"
-    }
-
-    override fun equals(other: Any?): Boolean {
-        if (this === other) return true
-        if (javaClass != other?.javaClass) return false
-
-        other as StringExpression
-
-        if (!expression.contentEquals(other.expression)) return false
-
-        return true
-    }
-
-    override fun hashCode() = expression.contentHashCode()
-}
-
-/**
- * A [ComplicationText] where the system evaluates a [StringExpression] on behalf of the watch face.
+ * A [ComplicationText] where the system evaluates a [DynamicString] on behalf of the watch face.
  * By the time this reaches the watch face's Renderer, it'll have been converted to a plain
  * ComplicationText.
  *
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public class StringExpressionComplicationText(
-    public val expression: StringExpression
+public class ComplicationTextExpression(
+    public val expression: DynamicString
 ) : ComplicationText {
     private val delegate = DelegatingComplicationText(WireComplicationText(expression))
 
@@ -695,7 +668,7 @@
         if (this === other) return true
         if (javaClass != other?.javaClass) return false
 
-        other as StringExpressionComplicationText
+        other as ComplicationTextExpression
 
         if (delegate != other.delegate) return false
 
diff --git a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataEqualityTest.kt b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataEqualityTest.kt
index bde19c8..9dd28c8 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataEqualityTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataEqualityTest.kt
@@ -24,11 +24,11 @@
 import android.support.wearable.complications.ComplicationText.plainText
 import android.util.Log
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
 import androidx.wear.watchface.complications.data.ComplicationDisplayPolicies
 import androidx.wear.watchface.complications.data.ComplicationPersistencePolicies
-import androidx.wear.watchface.complications.data.FloatExpression
 import androidx.wear.watchface.complications.data.SharedRobolectricTestRunner
-import androidx.wear.watchface.complications.data.StringExpression
 import com.google.common.truth.Expect
 import kotlin.random.Random
 import org.junit.Before
@@ -88,20 +88,8 @@
             { setRangedValue(2f) },
         ),
         RANGED_VALUE_EXPRESSION(
-            {
-                setRangedValueExpression(
-                    object : FloatExpression() {
-                        override fun asByteArray() = byteArrayOf(1, 2)
-                    }
-                )
-            },
-            {
-                setRangedValueExpression(
-                    object : FloatExpression() {
-                        override fun asByteArray() = byteArrayOf(3, 4)
-                    }
-                )
-            },
+            { setRangedValueExpression(DynamicFloat.constant(1.2f)) },
+            { setRangedValueExpression(DynamicFloat.constant(3.4f)) },
         ),
         RANGED_VALUE_TYPE(
             { setRangedValueType(1) },
@@ -352,19 +340,11 @@
         RANGED_VALUE_EXPRESSION(
             {
                 setRangedValue(Random.nextFloat()) // Ignored when there's an expression.
-                    .setRangedValueExpression(
-                        object : FloatExpression() {
-                            override fun asByteArray() = byteArrayOf(1, 2)
-                        }
-                    )
+                    .setRangedValueExpression(DynamicFloat.constant(1.2f))
             },
             {
                 setRangedValue(Random.nextFloat()) // Ignored when there's an expression.
-                    .setRangedValueExpression(
-                        object : FloatExpression() {
-                            override fun asByteArray() = byteArrayOf(3, 4)
-                        }
-                    )
+                    .setRangedValueExpression(DynamicFloat.constant(3.4f))
             },
         ),
 
@@ -378,7 +358,7 @@
                 setShortTitle(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(1, 2))
+                        DynamicString.constant("1")
                     )
                 )
             },
@@ -386,7 +366,7 @@
                 setShortTitle(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(3, 4))
+                        DynamicString.constant("2")
                     )
                 )
             },
@@ -402,7 +382,7 @@
                 setShortText(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(1, 2))
+                        DynamicString.constant("1")
                     )
                 )
             },
@@ -410,7 +390,7 @@
                 setShortText(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(3, 4))
+                        DynamicString.constant("2")
                     )
                 )
             },
@@ -426,7 +406,7 @@
                 setLongTitle(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(1, 2))
+                        DynamicString.constant("1")
                     )
                 )
             },
@@ -434,7 +414,7 @@
                 setLongTitle(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(3, 4))
+                        DynamicString.constant("2")
                     )
                 )
             },
@@ -450,7 +430,7 @@
                 setLongText(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(1, 2))
+                        DynamicString.constant("1")
                     )
                 )
             },
@@ -458,7 +438,7 @@
                 setLongText(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(3, 4))
+                        DynamicString.constant("2")
                     )
                 )
             },
@@ -474,7 +454,7 @@
                 setContentDescription(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(1, 2))
+                        DynamicString.constant("1")
                     )
                 )
             },
@@ -482,7 +462,7 @@
                 setContentDescription(
                     ComplicationText(
                         Random.nextInt().toString(), // Ignored when there's an expression.
-                        StringExpression(byteArrayOf(3, 4))
+                        DynamicString.constant("2")
                     )
                 )
             },
@@ -500,7 +480,7 @@
                         .setShortText(
                             ComplicationText(
                                 Random.nextInt().toString(), // Ignored when there's an expression.
-                                StringExpression(byteArrayOf(1, 2))
+                                DynamicString.constant("1")
                             )
                         )
                         .build()
@@ -512,7 +492,7 @@
                         .setShortText(
                             ComplicationText(
                                 Random.nextInt().toString(), // Ignored when there's an expression.
-                                StringExpression(byteArrayOf(3, 4))
+                                DynamicString.constant("2")
                             )
                         )
                         .build()
diff --git a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
index a090595..617d51a 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationDataTest.kt
@@ -24,8 +24,8 @@
 import android.support.wearable.complications.ComplicationText.TimeFormatBuilder
 import android.support.wearable.complications.ComplicationText.plainText
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
 import androidx.wear.watchface.complications.data.SharedRobolectricTestRunner
-import androidx.wear.watchface.complications.data.toFloatExpression
 import com.google.common.truth.Truth.assertThat
 import org.junit.Assert
 import org.junit.Assert.assertThrows
@@ -100,7 +100,7 @@
         // GIVEN complication data of the RANGED_VALUE type created by the Builder...
         val data =
             ComplicationData.Builder(ComplicationData.TYPE_RANGED_VALUE)
-                .setRangedValueExpression(byteArrayOf(42, 107).toFloatExpression())
+                .setRangedValueExpression(DynamicFloat.constant(20f))
                 .setRangedMinValue(5f)
                 .setRangedMaxValue(150f)
                 .setShortTitle(plainText("title"))
@@ -109,7 +109,8 @@
 
         // WHEN the relevant getters are called on the resulting data
         // THEN the correct values are returned.
-        assertThat(data.rangedValueExpression!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
+        assertThat(data.rangedValueExpression?.toDynamicFloatByteArray())
+            .isEqualTo(DynamicFloat.constant(20f).toDynamicFloatByteArray())
         Assert.assertEquals(data.rangedMinValue, 5f, 0f)
         Assert.assertEquals(data.rangedMaxValue, 150f, 0f)
         assertThat(data.shortTitle!!.getTextAt(mResources, 0)).isEqualTo("title")
diff --git a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationTextTest.kt b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationTextTest.kt
index 3b3e6b9..331b996 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationTextTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/android/support/wearable/complications/ComplicationTextTest.kt
@@ -18,11 +18,13 @@
 
 import android.content.Context
 import android.os.Parcel
+import android.support.wearable.complications.ComplicationText.FORMAT_STYLE_DEFAULT
 import android.support.wearable.complications.ComplicationText.TimeDifferenceBuilder
 import android.support.wearable.complications.ComplicationText.TimeFormatBuilder
 import androidx.test.core.app.ApplicationProvider
-import androidx.wear.watchface.complications.data.StringExpression
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
 import androidx.wear.watchface.complications.data.SharedRobolectricTestRunner
+import com.google.common.testing.EqualsTester
 import com.google.common.truth.Truth
 import org.junit.Assert
 import org.junit.Test
@@ -36,6 +38,33 @@
     private val mResources = ApplicationProvider.getApplicationContext<Context>().resources
 
     @Test
+    public fun testEquality() {
+        fun dup(builder: () -> ComplicationText) = arrayOf(builder(), builder())
+        fun timeFormat(value: String) = TimeFormatText(value, FORMAT_STYLE_DEFAULT, null)
+
+        // Verifying all possible constructors, with duplicate values and different values for each
+        // constructor argument.
+        EqualsTester()
+            .addEqualityGroup(dup { ComplicationText("surrounding") })
+            .addEqualityGroup(dup { ComplicationText("surrounding 2") })
+            .addEqualityGroup(dup { ComplicationText("surrounding", timeFormat("%h")) })
+            .addEqualityGroup(dup { ComplicationText("surrounding 2", timeFormat("%h")) })
+            .addEqualityGroup(dup { ComplicationText("surrounding", timeFormat("%m")) })
+            .addEqualityGroup(dup { ComplicationText(DynamicString.constant("expression")) })
+            .addEqualityGroup(dup { ComplicationText(DynamicString.constant("expression 2")) })
+            .addEqualityGroup(
+                dup { ComplicationText("surrounding", DynamicString.constant("expression")) }
+            )
+            .addEqualityGroup(
+                dup { ComplicationText("surrounding 2", DynamicString.constant("expression")) }
+            )
+            .addEqualityGroup(
+                dup { ComplicationText("surrounding", DynamicString.constant("expression 2")) }
+            )
+            .testEquals()
+    }
+
+    @Test
     public fun testPlainText() {
         // GIVEN ComplicationText of the plain string type
         val complicationText =
@@ -803,7 +832,7 @@
 
     @Test
     public fun stringExpressionToParcelRoundTrip() {
-        val text = ComplicationText(StringExpression(byteArrayOf(1, 2, 3)))
+        val text = ComplicationText(DynamicString.constant("hello"))
 
         Truth.assertThat(text.toParcelRoundTrip()).isEqualTo(text)
     }
@@ -812,7 +841,7 @@
     public fun getTextAt_ignoresStringExpressionIfSurroundingStringPresent() {
         val text = ComplicationText(
             "hello" as CharSequence,
-            StringExpression(byteArrayOf(1, 2, 3))
+            DynamicString.constant("world")
         )
 
         Truth.assertThat(text.getTextAt(mResources, 132456789).toString())
diff --git a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
index 5ca6603..0ed46d2 100644
--- a/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
+++ b/wear/watchface/watchface-complications-data/src/test/java/androidx/wear/watchface/complications/data/DataTest.kt
@@ -32,6 +32,8 @@
 import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.test.core.app.ApplicationProvider
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicFloat
+import androidx.wear.protolayout.expression.DynamicBuilders.DynamicString
 import com.google.common.truth.Truth.assertThat
 import com.google.testing.junit.testparameterinjector.TestParameter
 import com.google.testing.junit.testparameterinjector.TestParameterInjector
@@ -151,11 +153,11 @@
 
         assertThat(data.toString()).isEqualTo(
             "ShortTextComplicationData(text=ComplicationText{mSurroundingText=text, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=ComplicationText{" +
-                "mSurroundingText=title, mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, title=ComplicationText{" +
+                "mSurroundingText=title, mTimeDependentText=null, mExpression=null}, " +
                 "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
+                "mExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=1, displayPolicy=1)"
@@ -204,13 +206,13 @@
 
         assertThat(data.toString()).isEqualTo(
             "ShortTextComplicationData(text=ComplicationText{mSurroundingText=text, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=ComplicationText{" +
-                "mSurroundingText=title, mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, title=ComplicationText{" +
+                "mSurroundingText=title, mTimeDependentText=null, mExpression=null}, " +
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
+                "mExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
@@ -248,11 +250,11 @@
 
         assertThat(data.toString()).isEqualTo(
             "LongTextComplicationData(text=ComplicationText{mSurroundingText=text, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=ComplicationText{" +
-                "mSurroundingText=title, mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, title=ComplicationText{" +
+                "mSurroundingText=title, mTimeDependentText=null, mExpression=null}, " +
                 "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
@@ -301,13 +303,13 @@
 
         assertThat(data.toString()).isEqualTo(
             "LongTextComplicationData(text=ComplicationText{mSurroundingText=text, " +
-                "mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, " +
                 "title=ComplicationText{mSurroundingText=title, mTimeDependentText=null, " +
-                "mStringExpression=null}, monochromaticImage=MonochromaticImage(" +
+                "mExpression=null}, monochromaticImage=MonochromaticImage(" +
                 "image=Icon(typ=URI uri=someuri), ambientImage=null), smallImage=SmallImage(" +
                 "image=Icon(typ=URI uri=someuri2), type=PHOTO, ambientImage=null), " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -353,9 +355,9 @@
             "RangedValueComplicationData(value=95.0, valueExpression=null, valueType=0, " +
                 "min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=battery, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, colorRamp=null, persistencePolicy=0, " +
@@ -366,7 +368,7 @@
     @Test
     public fun rangedValueComplicationData_withValueExpression() {
         val data = RangedValueComplicationData.Builder(
-            valueExpression = byteArrayOf(42, 107).toFloatExpression(),
+            valueExpression = DynamicFloat.constant(20f),
             min = 5f,
             max = 100f,
             contentDescription = "content description".complicationText
@@ -377,7 +379,7 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationData.Builder(WireComplicationData.TYPE_RANGED_VALUE)
-                    .setRangedValueExpression(byteArrayOf(42, 107).toFloatExpression())
+                    .setRangedValueExpression(DynamicFloat.constant(20f))
                     .setRangedValue(5f) // min as a sensible default
                     .setRangedValueType(RangedValueComplicationData.TYPE_UNDEFINED)
                     .setRangedMinValue(5f)
@@ -393,7 +395,8 @@
         val deserialized = serializeAndDeserialize(data) as RangedValueComplicationData
         assertThat(deserialized.max).isEqualTo(100f)
         assertThat(deserialized.min).isEqualTo(5f)
-        assertThat(deserialized.valueExpression!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
+        assertThat(deserialized.valueExpression?.toDynamicFloatByteArray())
+            .isEqualTo(DynamicFloat.constant(20f).toDynamicFloatByteArray())
         assertThat(deserialized.value).isEqualTo(5f) // min as a sensible default
         assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
             .isEqualTo("content description")
@@ -402,12 +405,12 @@
 
         assertThat(data.toString()).isEqualTo(
             "RangedValueComplicationData(value=5.0, " +
-                "valueExpression=FloatExpressionPlaceholder[42, 107], valueType=0, min=5.0, " +
+                "valueExpression=FixedFloat{value=20.0}, valueType=0, min=5.0, " +
                 "max=100.0, monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=battery, mTimeDependentText=null, mStringExpression=null}, " +
+                "mSurroundingText=battery, mTimeDependentText=null, mExpression=null}, " +
                 "text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), dataSource=" +
                 "ComponentInfo{com.pkg_a/com.a}, colorRamp=null, persistencePolicy=0, " +
@@ -421,9 +424,7 @@
             value = 95f, min = 0f, max = 100f,
             contentDescription = "content description".complicationText
         )
-            .setTitle(
-                StringExpressionComplicationText(StringExpression(byteArrayOf(1, 2, 3, 4, 5)))
-            )
+            .setTitle(ComplicationTextExpression(DynamicString.constant("title")))
             .setDataSource(dataSource)
             .build()
         ParcelableSubject.assertThat(data.asWireComplicationData())
@@ -433,9 +434,7 @@
                     .setRangedValueType(RangedValueComplicationData.TYPE_UNDEFINED)
                     .setRangedMinValue(0f)
                     .setRangedMaxValue(100f)
-                    .setShortTitle(
-                        WireComplicationText(StringExpression(byteArrayOf(1, 2, 3, 4, 5)))
-                    )
+                    .setShortTitle(WireComplicationText(DynamicString.constant("title")))
                     .setContentDescription(WireComplicationText.plainText("content description"))
                     .setDataSource(dataSource)
                     .setPersistencePolicy(ComplicationPersistencePolicies.CACHING_ALLOWED)
@@ -454,9 +453,9 @@
             "RangedValueComplicationData(value=95.0, valueExpression=null, valueType=0, " +
                 "min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=(null), mTimeDependentText=null, " +
-                "mStringExpression=StringExpression(expression=[1, 2, 3, 4, 5])}, text=null, " +
+                "mExpression=FixedString{value=title}}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -516,9 +515,9 @@
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=battery," +
-                " mTimeDependentText=null, mStringExpression=null}, text=null, " +
+                " mTimeDependentText=null, mExpression=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -562,9 +561,9 @@
             "GoalProgressComplicationData(value=1200.0, valueExpression=null, " +
                 "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=steps, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, colorRamp=null, persistencePolicy=0, " +
@@ -575,7 +574,7 @@
     @Test
     public fun goalProgressComplicationData_withValueExpression() {
         val data = GoalProgressComplicationData.Builder(
-            valueExpression = byteArrayOf(42, 107).toFloatExpression(),
+            valueExpression = DynamicFloat.constant(10f),
             targetValue = 10000f,
             contentDescription = "content description".complicationText
         )
@@ -585,7 +584,7 @@
         ParcelableSubject.assertThat(data.asWireComplicationData())
             .hasSameSerializationAs(
                 WireComplicationData.Builder(WireComplicationData.TYPE_GOAL_PROGRESS)
-                    .setRangedValueExpression(byteArrayOf(42, 107).toFloatExpression())
+                    .setRangedValueExpression(DynamicFloat.constant(10f))
                     .setRangedValue(0f) // sensible default
                     .setTargetValue(10000f)
                     .setShortTitle(WireComplicationText.plainText("steps"))
@@ -597,7 +596,8 @@
             )
         testRoundTripConversions(data)
         val deserialized = serializeAndDeserialize(data) as GoalProgressComplicationData
-        assertThat(deserialized.valueExpression!!.asByteArray()).isEqualTo(byteArrayOf(42, 107))
+        assertThat(deserialized.valueExpression?.toDynamicFloatByteArray())
+            .isEqualTo(DynamicFloat.constant(10f).toDynamicFloatByteArray())
         assertThat(deserialized.value).isEqualTo(0f) // sensible default
         assertThat(deserialized.targetValue).isEqualTo(10000f)
         assertThat(deserialized.contentDescription!!.getTextAt(resources, Instant.EPOCH))
@@ -607,11 +607,11 @@
 
         assertThat(data.toString()).isEqualTo(
             "GoalProgressComplicationData(value=0.0, valueExpression=" +
-                "FloatExpressionPlaceholder[42, 107], targetValue=10000.0, " +
+                "FixedFloat{value=10.0}, targetValue=10000.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=steps, mTimeDependentText=null, mStringExpression=null}, " +
+                "mSurroundingText=steps, mTimeDependentText=null, mExpression=null}, " +
                 "text=null, contentDescription=ComplicationText{mSurroundingText=content " +
-                "description, mTimeDependentText=null, mStringExpression=null}), " +
+                "description, mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange(" +
                 "startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -657,9 +657,9 @@
             "GoalProgressComplicationData(value=1200.0, valueExpression=null, " +
                 "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=steps, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, colorRamp=ColorRamp(colors=[-65536, " +
@@ -717,9 +717,9 @@
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=URI uri=someuri), " +
                 "ambientImage=null), smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), " +
                 "type=PHOTO, ambientImage=null), title=ComplicationText{mSurroundingText=steps, " +
-                "mTimeDependentText=null, mStringExpression=null}, text=null, " +
+                "mTimeDependentText=null, mExpression=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -768,9 +768,9 @@
             "RangedValueComplicationData(value=95.0, valueExpression=null, " +
                 "valueType=0, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=ComplicationText{" +
-                "mSurroundingText=battery, mTimeDependentText=null, mStringExpression=null}, " +
+                "mSurroundingText=battery, mTimeDependentText=null, mExpression=null}, " +
                 "text=null, contentDescription=ComplicationText{mSurroundingText=content " +
-                "description, mTimeDependentText=null, mStringExpression=null}), " +
+                "description, mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -839,9 +839,9 @@
                 " Element(color=-16711936, weight=1.0), Element(color=-16776961, weight=2.0), " +
                 "elementBackgroundColor=-7829368, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=calories, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
@@ -906,9 +906,9 @@
                 "image=Icon(typ=URI uri=someuri), ambientImage=null), " +
                 "smallImage=SmallImage(image=Icon(typ=URI uri=someuri2), type=PHOTO, " +
                 "ambientImage=null), title=ComplicationText{mSurroundingText=calories, " +
-                "mTimeDependentText=null, mStringExpression=null}, text=null, " +
+                "mTimeDependentText=null, mExpression=null}, text=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -943,7 +943,7 @@
             "MonochromaticImageComplicationData(monochromaticImage=MonochromaticImage(" +
                 "image=Icon(typ=URI uri=someuri), ambientImage=null), contentDescription=" +
                 "ComplicationText{mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, displayPolicy=0)"
@@ -980,7 +980,7 @@
             "SmallImageComplicationData(smallImage=SmallImage(image=Icon(" +
                 "typ=URI uri=someuri), type=PHOTO, ambientImage=null), " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -1048,7 +1048,7 @@
         assertThat(data.toString()).isEqualTo(
             "PhotoImageComplicationData(photoImage=Icon(typ=URI uri=someuri), " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -1078,7 +1078,7 @@
 
         assertThat(data.toString()).isEqualTo(
             "NoPermissionComplicationData(text=ComplicationText{mSurroundingText=needs location, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=null, " +
+                "mTimeDependentText=null, mExpression=null}, title=null, " +
                 "monochromaticImage=null, smallImage=null, tapActionLostDueToSerialization=false," +
                 " tapAction=null, validTimeRange=TimeRange(startDateTimeMillis=" +
                 "-1000000000-01-01T00:00:00Z, endDateTimeMillis=" +
@@ -1115,7 +1115,7 @@
         assertThat(data.toString()).isEqualTo(
             "NoPermissionComplicationData(text=ComplicationText{" +
                 "mSurroundingText=needs location, mTimeDependentText=null, " +
-                "mStringExpression=null}, title=null, monochromaticImage=MonochromaticImage(" +
+                "mExpression=null}, title=null, monochromaticImage=MonochromaticImage(" +
                 "image=Icon(typ=URI uri=someuri), ambientImage=null), smallImage=SmallImage(" +
                 "image=Icon(typ=URI uri=someuri2), type=PHOTO, ambientImage=null), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange(" +
@@ -1165,13 +1165,13 @@
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=ShortTextComplicationData(text=" +
                 "ComplicationText{mSurroundingText=__placeholder__, mTimeDependentText=null, " +
-                "mStringExpression=null}, title=ComplicationText{" +
+                "mExpression=null}, title=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null, " +
-                "mStringExpression=null}, monochromaticImage=MonochromaticImage(" +
+                "mExpression=null}, monochromaticImage=MonochromaticImage(" +
                 "image=Icon(typ=RESOURCE pkg= id=0xffffffff), ambientImage=null), " +
                 "smallImage=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
+                "mExpression=null}, tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, " +
@@ -1216,9 +1216,9 @@
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=LongTextComplicationData(" +
                 "text=ComplicationText{mSurroundingText=text, mTimeDependentText=null, " +
-                "mStringExpression=null}, title=null, monochromaticImage=null, smallImage=null, " +
+                "mExpression=null}, title=null, monochromaticImage=null, smallImage=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -1275,9 +1275,9 @@
                 "value=3.4028235E38, valueExpression=null, valueType=0, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null, " +
-                "mStringExpression=null}, contentDescription=ComplicationText{" +
+                "mExpression=null}, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, colorRamp=null, persistencePolicy=0, " +
@@ -1333,9 +1333,9 @@
                 "value=3.4028235E38, valueExpression=null, targetValue=10000.0, " +
                 "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null, " +
-                "mStringExpression=null}, contentDescription=ComplicationText{" +
+                "mExpression=null}, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, " +
@@ -1396,9 +1396,9 @@
                 "weight=1.0), Element(color=-16776961, weight=2.0), " +
                 "elementBackgroundColor=-7829368, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=calories, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, " +
@@ -1458,9 +1458,9 @@
                 "value=3.4028235E38, valueExpression=null, valueType=1, min=0.0, max=100.0, " +
                 "monochromaticImage=null, smallImage=null, title=null, text=ComplicationText{" +
                 "mSurroundingText=__placeholder__, mTimeDependentText=null, " +
-                "mStringExpression=null}, contentDescription=ComplicationText{" +
+                "mExpression=null}, contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, colorRamp=ColorRamp(colors=[-65536, " +
@@ -1508,7 +1508,7 @@
                 "monochromaticImage=MonochromaticImage(image=Icon(typ=RESOURCE pkg= " +
                 "id=0xffffffff), ambientImage=null), contentDescription=ComplicationText{" +
                 "mSurroundingText=content description, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
                 "dataSource=ComponentInfo{com.pkg_a/com.a}, persistencePolicy=0, " +
@@ -1555,7 +1555,7 @@
             "NoDataComplicationData(placeholder=SmallImageComplicationData(smallImage=" +
                 "SmallImage(image=Icon(typ=RESOURCE pkg= id=0xffffffff), type=ICON, " +
                 "ambientImage=null), contentDescription=ComplicationText{mSurroundingText=" +
-                "content description, mTimeDependentText=null, mStringExpression=null}), " +
+                "content description, mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=TimeRange(" +
                 "startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -1602,7 +1602,7 @@
             "NoDataComplicationData(placeholder=PhotoImageComplicationData(" +
                 "photoImage=Icon(typ=RESOURCE pkg= id=0xffffffff), " +
                 "contentDescription=ComplicationText{mSurroundingText=content description, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(startDateTimeMillis=-1000000000-01-01T00:00:00Z, " +
                 "endDateTimeMillis=+1000000000-12-31T23:59:59.999999999Z), " +
@@ -1714,7 +1714,7 @@
     public fun rangedValueComplicationData_withValueExpression() {
         assertRoundtrip(
             WireComplicationData.Builder(WireComplicationData.TYPE_RANGED_VALUE)
-                .setRangedValueExpression(byteArrayOf(42, 107).toFloatExpression())
+                .setRangedValueExpression(DynamicFloat.constant(10f))
                 .setRangedMinValue(0f)
                 .setRangedMaxValue(100f)
                 .setShortTitle(WireComplicationText.plainText("battery"))
@@ -1763,7 +1763,7 @@
     public fun goalProgressComplicationData_withValueExpression() {
         assertRoundtrip(
             WireComplicationData.Builder(WireComplicationData.TYPE_GOAL_PROGRESS)
-                .setRangedValueExpression(byteArrayOf(42, 107).toFloatExpression())
+                .setRangedValueExpression(DynamicFloat.constant(10f))
                 .setTargetValue(10000f)
                 .setShortTitle(WireComplicationText.plainText("steps"))
                 .setContentDescription(WireComplicationText.plainText("content description"))
@@ -2695,10 +2695,10 @@
 
         assertThat(data.toString()).isEqualTo(
             "ShortTextComplicationData(text=ComplicationText{mSurroundingText=REDACTED, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, title=ComplicationText{" +
+                "mSurroundingText=REDACTED, mTimeDependentText=null, mExpression=null}, " +
                 "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null, mStringExpression=null}, " +
+                "mSurroundingText=REDACTED, mTimeDependentText=null, mExpression=null}, " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(REDACTED), dataSource=null, persistencePolicy=0, " +
                 "displayPolicy=0)"
@@ -2719,10 +2719,10 @@
 
         assertThat(data.toString()).isEqualTo(
             "LongTextComplicationData(text=ComplicationText{mSurroundingText=REDACTED, " +
-                "mTimeDependentText=null, mStringExpression=null}, title=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null, mStringExpression=null}, " +
+                "mTimeDependentText=null, mExpression=null}, title=ComplicationText{" +
+                "mSurroundingText=REDACTED, mTimeDependentText=null, mExpression=null}, " +
                 "monochromaticImage=null, smallImage=null, contentDescription=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null, mStringExpression=null}), " +
+                "mSurroundingText=REDACTED, mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(REDACTED), dataSource=null, persistencePolicy=0, " +
                 "displayPolicy=0)"
@@ -2748,10 +2748,10 @@
             "RangedValueComplicationData(value=REDACTED, valueExpression=REDACTED, " +
                 "valueType=0, min=0.0, max=100.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=ComplicationText{mSurroundingText=REDACTED, " +
-                "mTimeDependentText=null, mStringExpression=null}, contentDescription=" +
+                "mExpression=null}, text=ComplicationText{mSurroundingText=REDACTED, " +
+                "mTimeDependentText=null, mExpression=null}, contentDescription=" +
                 "ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null, " +
-                "mStringExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
+                "mExpression=null}), tapActionLostDueToSerialization=false, tapAction=null," +
                 " validTimeRange=TimeRange(REDACTED), dataSource=null, colorRamp=null, " +
                 "persistencePolicy=0, displayPolicy=0)"
         )
@@ -2775,8 +2775,8 @@
             "GoalProgressComplicationData(value=REDACTED, valueExpression=REDACTED, " +
                 "targetValue=10000.0, monochromaticImage=null, smallImage=null, " +
                 "title=ComplicationText{mSurroundingText=REDACTED, mTimeDependentText=null, " +
-                "mStringExpression=null}, text=null, contentDescription=ComplicationText{" +
-                "mSurroundingText=REDACTED, mTimeDependentText=null, mStringExpression=null}), " +
+                "mExpression=null}, text=null, contentDescription=ComplicationText{" +
+                "mSurroundingText=REDACTED, mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, validTimeRange=" +
                 "TimeRange(REDACTED), dataSource=null, colorRamp=ColorRamp(colors=[-65536, " +
                 "-16711936, -16776961], interpolated=true), persistencePolicy=0, displayPolicy=0)"
@@ -2798,9 +2798,9 @@
         assertThat(data.toString()).isEqualTo(
             "NoDataComplicationData(placeholder=LongTextComplicationData(" +
                 "text=ComplicationText{mSurroundingText=__placeholder__, mTimeDependentText=null," +
-                " mStringExpression=null}, title=null, monochromaticImage=null, smallImage=null, " +
+                " mExpression=null}, title=null, monochromaticImage=null, smallImage=null, " +
                 "contentDescription=ComplicationText{mSurroundingText=REDACTED, " +
-                "mTimeDependentText=null, mStringExpression=null}), " +
+                "mTimeDependentText=null, mExpression=null}), " +
                 "tapActionLostDueToSerialization=false, tapAction=null, " +
                 "validTimeRange=TimeRange(REDACTED), dataSource=null, persistencePolicy=0, " +
                 "displayPolicy=0), tapActionLostDueToSerialization=false, tapAction=null, " +
diff --git a/wear/watchface/watchface-samples-minimal-complications/src/main/java/androidx/wear/watchface/samples/minimal/complications/WatchFaceRenderer.java b/wear/watchface/watchface-samples-minimal-complications/src/main/java/androidx/wear/watchface/samples/minimal/complications/WatchFaceRenderer.java
index 6bc30aa..b0a2ccf 100644
--- a/wear/watchface/watchface-samples-minimal-complications/src/main/java/androidx/wear/watchface/samples/minimal/complications/WatchFaceRenderer.java
+++ b/wear/watchface/watchface-samples-minimal-complications/src/main/java/androidx/wear/watchface/samples/minimal/complications/WatchFaceRenderer.java
@@ -102,7 +102,10 @@
         }
 
         mPaint.setColor(Color.WHITE);
-        int hour = zonedDateTime.getHour() % 12;
+        int hour = zonedDateTime.getHour();
+        if (hour != 12) {
+            hour %= 12;
+        }
         int minute = zonedDateTime.getMinute();
         int second = zonedDateTime.getSecond();
         mTime[0] = DIGITS[hour / 10];
diff --git a/wear/watchface/watchface-samples-minimal-instances/src/main/java/androidx/wear/watchface/samples/minimal/instances/WatchFaceRenderer.java b/wear/watchface/watchface-samples-minimal-instances/src/main/java/androidx/wear/watchface/samples/minimal/instances/WatchFaceRenderer.java
index 8e13885..42488a8 100644
--- a/wear/watchface/watchface-samples-minimal-instances/src/main/java/androidx/wear/watchface/samples/minimal/instances/WatchFaceRenderer.java
+++ b/wear/watchface/watchface-samples-minimal-instances/src/main/java/androidx/wear/watchface/samples/minimal/instances/WatchFaceRenderer.java
@@ -190,7 +190,10 @@
             mPaint.setColor(Color.BLACK);
             canvas.drawRect(rect, mPaint);
             mPaint.setColor(Color.WHITE);
-            int hour = zonedDateTime.getHour() % 12;
+            int hour = zonedDateTime.getHour();
+            if (hour != 12) {
+                hour %= 12;
+            }
             int minute = zonedDateTime.getMinute();
             int second = zonedDateTime.getSecond();
             mTimeText[0] = DIGITS[hour / 10];
@@ -263,7 +266,10 @@
                 @NotNull Canvas canvas, @NotNull Rect rect, @NotNull ZonedDateTime zonedDateTime,
                 RenderParameters renderParameters) {
             boolean isActive = renderParameters.getDrawMode() != DrawMode.AMBIENT;
-            int hour = zonedDateTime.getHour() % 12;
+            int hour = zonedDateTime.getHour();
+            if (hour != 12) {
+                hour %= 12;
+            }
             int minute = zonedDateTime.getMinute();
             int second = zonedDateTime.getSecond();
 
diff --git a/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/WatchFaceRenderer.java b/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/WatchFaceRenderer.java
index 5fe6e4c..e15cdbf 100644
--- a/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/WatchFaceRenderer.java
+++ b/wear/watchface/watchface-samples-minimal-style/src/main/java/androidx/wear/watchface/samples/minimal/style/WatchFaceRenderer.java
@@ -142,7 +142,10 @@
             mPaint.setColor(Color.BLACK);
             canvas.drawRect(rect, mPaint);
             mPaint.setColor(Color.WHITE);
-            int hour = zonedDateTime.getHour() % 12;
+            int hour = zonedDateTime.getHour();
+            if (hour != 12) {
+                hour %= 12;
+            }
             int minute = zonedDateTime.getMinute();
             int second = zonedDateTime.getSecond();
             mTimeText[0] = DIGITS[hour / 10];
@@ -211,7 +214,10 @@
                 @NotNull Canvas canvas, @NotNull Rect rect, @NotNull ZonedDateTime zonedDateTime,
                 RenderParameters renderParameters) {
             boolean isActive = renderParameters.getDrawMode() != DrawMode.AMBIENT;
-            int hour = zonedDateTime.getHour() % 12;
+            int hour = zonedDateTime.getHour();
+            if (hour != 12) {
+                hour %= 12;
+            }
             int minute = zonedDateTime.getMinute();
             int second = zonedDateTime.getSecond();
 
diff --git a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
index fe8e80f..cf9a673 100644
--- a/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
+++ b/wear/watchface/watchface-style/src/main/java/androidx/wear/watchface/style/CurrentUserStyleRepository.kt
@@ -497,6 +497,37 @@
 
             return UserStyleSchema(userStyleSettings)
         }
+
+        internal fun UserStyleSchemaWireFormat.toApiFormat(): List<UserStyleSetting> {
+            val userStyleSettings = mSchema.map {
+                UserStyleSetting.createFromWireFormat(it)
+            }
+            val wireUserStyleSettingsIterator = mSchema.iterator()
+            for (setting in userStyleSettings) {
+                val wireUserStyleSetting = wireUserStyleSettingsIterator.next()
+                wireUserStyleSetting.mOptionChildIndices?.let {
+                    // Unfortunately due to VersionedParcelable limitations, we can not extend the
+                    // Options wire format (extending the contents of a list is not supported!!!).
+                    // This means we need to encode/decode the childSettings in a round about way.
+                    val optionsIterator = setting.options.iterator()
+                    var option: Option? = null
+                    for (childIndex in it) {
+                        if (option == null) {
+                            option = optionsIterator.next()
+                        }
+                        if (childIndex == -1) {
+                            option = null
+                        } else {
+                            val childSettings = option.childSettings as ArrayList
+                            val child = userStyleSettings[childIndex]
+                            childSettings.add(child)
+                            child.hasParent = true
+                        }
+                    }
+                }
+            }
+            return userStyleSettings
+        }
     }
 
     init {
@@ -562,7 +593,9 @@
         }
         for (setting in settings) {
             for (option in setting.options) {
-                validateComplicationSettings(option.childSettings, prevSetting)
+                if (option.childSettings.isNotEmpty()) {
+                    validateComplicationSettings(option.childSettings, prevSetting)
+                }
             }
         }
     }
@@ -570,34 +603,7 @@
     /** @hide */
     @Suppress("Deprecation") // userStyleSettings
     @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public constructor(wireFormat: UserStyleSchemaWireFormat) : this(
-        wireFormat.mSchema.map { UserStyleSetting.createFromWireFormat(it) }
-    ) {
-        val wireUserStyleSettingsIterator = wireFormat.mSchema.iterator()
-        for (userStyle in userStyleSettings) {
-            val wireUserStyleSetting = wireUserStyleSettingsIterator.next()
-            wireUserStyleSetting.mOptionChildIndices?.let {
-                // Unfortunately due to VersionedParcelable limitations, we can not extend the
-                // Options wire format (extending the contents of a list is not supported!!!).
-                // This means we need to encode/decode the childSettings in a round about way.
-                val optionsIterator = userStyle.options.iterator()
-                var option: Option? = null
-                for (childIndex in it) {
-                    if (option == null) {
-                        option = optionsIterator.next()
-                    }
-                    if (childIndex == -1) {
-                        option = null
-                    } else {
-                        val childSettings = option.childSettings as ArrayList
-                        val child = userStyleSettings[childIndex]
-                        childSettings.add(child)
-                        child.hasParent = true
-                    }
-                }
-            }
-        }
-    }
+    public constructor(wireFormat: UserStyleSchemaWireFormat) : this(wireFormat.toApiFormat())
 
     /** @hide */
     @Suppress("Deprecation") // userStyleSettings
diff --git a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
index 4157013..fb68608 100644
--- a/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
+++ b/wear/watchface/watchface-style/src/test/java/androidx/wear/watchface/style/StyleParcelableTest.kt
@@ -349,6 +349,9 @@
                 styleSetting2
             )
         )
+        assertThat(srcSchema.rootUserStyleSettings.map { it.id }).containsExactly(
+            UserStyleSetting.Id("clock_type")
+        )
 
         val parcel = Parcel.obtain()
         srcSchema.toWireFormat().writeToParcel(parcel, 0)
@@ -360,6 +363,9 @@
         parcel.recycle()
 
         assertThat(schema.userStyleSettings.size).isEqualTo(4)
+        assertThat(schema.rootUserStyleSettings.map { it.id }).containsExactly(
+            UserStyleSetting.Id("clock_type")
+        )
 
         val deserializedWatchFaceType = schema.userStyleSettings[0] as ListUserStyleSetting
         assertThat(deserializedWatchFaceType.id)
diff --git a/wear/watchface/watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java b/wear/watchface/watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
index c9db0f7..3c8e640 100644
--- a/wear/watchface/watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
+++ b/wear/watchface/watchface/samples/minimal/src/main/java/androidx/wear/watchface/samples/minimal/WatchFaceRenderer.java
@@ -66,7 +66,10 @@
         mPaint.setColor(Color.BLACK);
         canvas.drawRect(rect, mPaint);
         mPaint.setColor(Color.WHITE);
-        int hour = zonedDateTime.getHour() % 12;
+        int hour = zonedDateTime.getHour();
+        if (hour != 12) {
+            hour %= 12;
+        }
         int minute = zonedDateTime.getMinute();
         int second = zonedDateTime.getSecond();
         mTimeText[0] = DIGITS[hour / 10];
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 84970d4..a90f3c4 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
@@ -6737,4 +6737,4 @@
 
         override fun getSystemTimeZoneId() = ZoneId.of("UTC")
     }
-}
\ No newline at end of file
+}
diff --git a/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java b/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
index 07acee1..e8e0d93 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/DropDataContentProvider.java
@@ -41,7 +41,7 @@
  *
  * <pre class="prettyprint">
  *  &lt;provider
- *             android:authorities="{{your-package}}.DropDataProvider"
+ *             android:authorities="&lt;your-package&gt;.DropDataProvider"
  *             android:name="androidx.webkit.DropDataContentProvider"
  *             android:exported="false"
  *             android:grantUriPermissions="true"/&gt;
diff --git a/work/work-inspection/build.gradle b/work/work-inspection/build.gradle
index 5083c43..6b3612c 100644
--- a/work/work-inspection/build.gradle
+++ b/work/work-inspection/build.gradle
@@ -28,7 +28,7 @@
     api("androidx.annotation:annotation:1.1.0")
     api(libs.kotlinStdlib)
     compileOnly("androidx.inspection:inspection:1.0.0")
-    compileOnly("androidx.lifecycle:lifecycle-runtime:2.2.0")
+    compileOnly(project(":lifecycle:lifecycle-runtime"))
     compileOnly(project(":work:work-runtime"))
     compileOnly("androidx.room:room-runtime:2.5.0-beta01")
     androidTestImplementation(project(":inspection:inspection-testing"))
diff --git a/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt b/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
index f51e4cf..4487440 100644
--- a/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
+++ b/work/work-inspection/src/main/java/androidx/work/inspection/WorkManagerInspector.kt
@@ -281,7 +281,6 @@
         latch.await()
     }
 
-    override fun getLifecycle(): Lifecycle {
-        return lifecycleRegistry
-    }
+    override val lifecycle: Lifecycle
+        get() = lifecycleRegistry
 }