[go: nahoru, domu]

Merge "Adds Back as disallowed Action type for Row actions. Ensures ActionsConstraints OnClick restriction only applies to custom action types." into androidx-main
diff --git a/activity/activity-ktx/api/current.ignore b/activity/activity-ktx/api/current.ignore
deleted file mode 100644
index 1756030..0000000
--- a/activity/activity-ktx/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.activity.contextaware.ContextAwareKt#withContextAvailable(androidx.activity.contextaware.ContextAware, kotlin.jvm.functions.Function1<? super android.content.Context,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.activity.contextaware.ContextAwareKt.withContextAvailable
diff --git a/activity/activity-ktx/api/restricted_current.ignore b/activity/activity-ktx/api/restricted_current.ignore
deleted file mode 100644
index 1756030..0000000
--- a/activity/activity-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.activity.contextaware.ContextAwareKt#withContextAvailable(androidx.activity.contextaware.ContextAware, kotlin.jvm.functions.Function1<? super android.content.Context,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.activity.contextaware.ContextAwareKt.withContextAvailable
diff --git a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
index 00c2002..1972554 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/ComponentActivitySavedStateTest.kt
@@ -40,24 +40,26 @@
     }
 
     private fun ActivityScenario<SavedStateActivity>.initializeSavedState() = withActivity {
-        assertThat(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
+        val isLifecycleCreated = lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)
         val registry = savedStateRegistry
         val savedState = registry.consumeRestoredStateForKey(CALLBACK_KEY)
-        assertThat(savedState).isNull()
+        val savedStateIsNull = savedState == null
         registry.registerSavedStateProvider(CALLBACK_KEY, DefaultProvider())
+        isLifecycleCreated && savedStateIsNull
     }
 
     @Test
     @Throws(Throwable::class)
     fun savedState() {
         with(ActivityScenario.launch(SavedStateActivity::class.java)) {
-            initializeSavedState()
+            assertThat(initializeSavedState()).isTrue()
             recreate()
             moveToState(Lifecycle.State.CREATED)
-            withActivity {
-                assertThat(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
-                checkDefaultSavedState(savedStateRegistry)
-            }
+            val lifecycle = withActivity { lifecycle }
+            assertThat(lifecycle.currentState.isAtLeast(Lifecycle.State.CREATED)).isTrue()
+
+            val registry = withActivity { savedStateRegistry }
+            assertThat(hasDefaultSavedState(registry)).isTrue()
         }
     }
 
@@ -65,11 +67,10 @@
     @Throws(Throwable::class)
     fun savedStateLateInit() {
         with(ActivityScenario.launch(SavedStateActivity::class.java)) {
-            initializeSavedState()
+            assertThat(initializeSavedState()).isTrue()
             recreate()
-            withActivity {
-                checkDefaultSavedState(savedStateRegistry)
-            }
+            val registry = withActivity { savedStateRegistry }
+            assertThat(hasDefaultSavedState(registry)).isTrue()
         }
     }
 
@@ -77,7 +78,7 @@
     @Throws(Throwable::class)
     fun savedStateEarlyRegisterOnCreate() {
         with(ActivityScenario.launch(SavedStateActivity::class.java)) {
-            initializeSavedState()
+            assertThat(initializeSavedState()).isTrue()
             SavedStateActivity.checkEnabledInOnCreate = true
             recreate()
         }
@@ -87,7 +88,7 @@
     @Throws(Throwable::class)
     fun savedStateEarlyRegisterOnContextAvailable() {
         with(ActivityScenario.launch(SavedStateActivity::class.java)) {
-            initializeSavedState()
+            assertThat(initializeSavedState()).isTrue()
             SavedStateActivity.checkEnabledInOnContextAvailable = true
             recreate()
         }
@@ -97,7 +98,7 @@
     @Throws(Throwable::class)
     fun savedStateEarlyRegisterInitAddedLifecycleObserver() {
         with(ActivityScenario.launch(SavedStateActivity::class.java)) {
-            initializeSavedState()
+            assertThat(initializeSavedState()).isTrue()
             SavedStateActivity.checkEnabledInInitAddedLifecycleObserver = true
             recreate()
         }
@@ -112,26 +113,24 @@
 private const val VALUE = "value"
 private const val CALLBACK_KEY = "foo"
 
-private fun checkDefaultSavedState(store: SavedStateRegistry) {
+private fun hasDefaultSavedState(store: SavedStateRegistry): Boolean {
     val savedState = store.consumeRestoredStateForKey(CALLBACK_KEY)
-    assertThat(savedState).isNotNull()
-    assertThat(savedState!!.getString(KEY)).isEqualTo(VALUE)
+    return savedState?.getString(KEY) == VALUE
 }
 
 class SavedStateActivity : ComponentActivity() {
 
     init {
         addOnContextAvailableListener {
-            if (checkEnabledInOnContextAvailable) {
-                checkDefaultSavedState(savedStateRegistry)
+            if (checkEnabledInOnContextAvailable && hasDefaultSavedState(savedStateRegistry)) {
                 checkEnabledInOnContextAvailable = false
             }
         }
         lifecycle.addObserver(object : LifecycleEventObserver {
             override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
                 if (event == Lifecycle.Event.ON_CREATE &&
-                    checkEnabledInInitAddedLifecycleObserver) {
-                    checkDefaultSavedState(savedStateRegistry)
+                    checkEnabledInInitAddedLifecycleObserver &&
+                    hasDefaultSavedState(savedStateRegistry)) {
                     checkEnabledInInitAddedLifecycleObserver = false
                     lifecycle.removeObserver(this)
                 }
@@ -141,8 +140,7 @@
 
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
-        if (checkEnabledInOnCreate) {
-            checkDefaultSavedState(savedStateRegistry)
+        if (checkEnabledInOnCreate && hasDefaultSavedState(savedStateRegistry)) {
             checkEnabledInOnCreate = false
         }
     }
diff --git a/annotation/annotation-experimental/src/main/java/androidx/annotation/RequiresOptIn.kt b/annotation/annotation-experimental/src/main/java/androidx/annotation/RequiresOptIn.kt
index 9716177..345dab2 100644
--- a/annotation/annotation-experimental/src/main/java/androidx/annotation/RequiresOptIn.kt
+++ b/annotation/annotation-experimental/src/main/java/androidx/annotation/RequiresOptIn.kt
@@ -27,48 +27,43 @@
  * by using [OptIn] or by being annotated with that marker themselves, effectively causing
  * further propagation of that opt-in aspect.
  *
- * Example:
- * <pre>`
- * // Library code
- * &#64;Retention(CLASS)
- * &#64;Target({TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE})
- * &#64;RequiresOptIn(level = Level.ERROR)
- * public @interface ExperimentalDateTime {}
+ * Marker example:
  *
- * &#64;ExperimentalDateTime
- * public class DateProvider {
- *   // ...
- * }
-`</pre> *
+ *     @Retention(CLASS)
+ *     @Target({TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE})
+ *     @RequiresOptIn(level = Level.ERROR)
+ *     public @interface ExperimentalDateTime {}
  *
- * <pre>`
- * // Client code
- * int getYear() {
- *   DateProvider provider; // Error: DateProvider is experimental
- *   // ...
- * }
+ *     @ExperimentalDateTime
+ *     public class DateProvider {
+ *       // ...
+ *     }
  *
- * &#64;ExperimentalDateTime
- * Date getDate() {
- *   DateProvider provider; // OK: the function is marked as experimental
- *   // ...
- * }
+ * Client example:
  *
- * void displayDate() {
- *   System.out.println(getDate()); // Error: getDate() is experimental, acceptance is required
- * }
-`</pre> *
+ *     int getYear() {
+ *       DateProvider provider; // Error: DateProvider is experimental
+ *       // ...
+ *     }
+ *
+ *     @ExperimentalDateTime
+ *     Date getDate() {
+ *       DateProvider provider; // OK: the function is marked as experimental
+ *       // ...
+ *     }
+ *
+ *     void displayDate() {
+ *       System.out.println(getDate()); // Error: getDate() is experimental, acceptance is required
+ *     }
  *
  * To configure project-wide opt-in, specify the `opt-in` option value in `lint.xml` as a
  * comma-delimited list of opted-in annotations:
  *
- * <pre>`
- * &#64;lint>
- *   &#64;issue id="$issueId">
- *     &#64;option name="opt-in" value="com.foo.ExperimentalBarAnnotation" />
- *   &#64;/issue>
- * &#64;/lint>
- `</pre> *
+ *     <lint>
+ *       <issue id="$issueId">
+ *         <option name="opt-in" value="com.foo.ExperimentalBarAnnotation" />
+ *       </issue>
+ *     </lint>
  */
 @Retention(AnnotationRetention.BINARY)
 @Target(AnnotationTarget.ANNOTATION_CLASS)
diff --git a/annotation/annotation-experimental/src/main/java/androidx/annotation/experimental/Experimental.kt b/annotation/annotation-experimental/src/main/java/androidx/annotation/experimental/Experimental.kt
index 04e4e05..b173e65 100644
--- a/annotation/annotation-experimental/src/main/java/androidx/annotation/experimental/Experimental.kt
+++ b/annotation/annotation-experimental/src/main/java/androidx/annotation/experimental/Experimental.kt
@@ -26,39 +26,6 @@
  * call sites should accept the experimental aspect of it either by using [UseExperimental],
  * or by being annotated with that marker themselves, effectively causing further propagation of
  * that experimental aspect.
- *
- * Example:
- * <pre>`
- * // Library code
- * &#64;Retention(CLASS)
- * &#64;Target({TYPE, METHOD, CONSTRUCTOR, FIELD, PACKAGE})
- * &#64;Experimental(level = Level.ERROR)
- * public @interface ExperimentalDateTime {}
- *
- * &#64;ExperimentalDateTime
- * public class DateProvider {
- * // ...
- * }
-`</pre> *
- *
- * <pre>`
- * // Client code
- * int getYear() {
- * DateProvider provider; // Error: DateProvider is experimental
- * // ...
- * }
- *
- * &#64;ExperimentalDateTime
- * Date getDate() {
- * DateProvider provider; // OK: the function is marked as experimental
- * // ...
- * }
- *
- * void displayDate() {
- * System.out.println(getDate()); // Error: getDate() is experimental, acceptance is required
- * }
-`</pre> *
- *
  */
 @Deprecated(
     "This annotation has been replaced by `@RequiresOptIn`",
diff --git a/annotation/annotation/api/current.ignore b/annotation/annotation/api/current.ignore
deleted file mode 100644
index ff7ac01..0000000
--- a/annotation/annotation/api/current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-ChangedValue: androidx.annotation.InspectableProperty#enumMapping():
-    Method androidx.annotation.InspectableProperty.enumMapping has changed value from {} to nothing
-ChangedValue: androidx.annotation.InspectableProperty#flagMapping():
-    Method androidx.annotation.InspectableProperty.flagMapping has changed value from {} to nothing
-ChangedValue: androidx.annotation.IntDef#value():
-    Method androidx.annotation.IntDef.value has changed value from {} to nothing
-ChangedValue: androidx.annotation.LongDef#value():
-    Method androidx.annotation.LongDef.value has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission#allOf():
-    Method androidx.annotation.RequiresPermission.allOf has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission#anyOf():
-    Method androidx.annotation.RequiresPermission.anyOf has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission.Read#value():
-    Method androidx.annotation.RequiresPermission.Read.value has changed value from @androidx.annotation.RequiresPermission to androidx.annotation.RequiresPermission()
-ChangedValue: androidx.annotation.RequiresPermission.Write#value():
-    Method androidx.annotation.RequiresPermission.Write.value has changed value from @androidx.annotation.RequiresPermission to androidx.annotation.RequiresPermission()
-ChangedValue: androidx.annotation.StringDef#value():
-    Method androidx.annotation.StringDef.value has changed value from {} to nothing
diff --git a/annotation/annotation/api/restricted_current.ignore b/annotation/annotation/api/restricted_current.ignore
deleted file mode 100644
index ff7ac01..0000000
--- a/annotation/annotation/api/restricted_current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-ChangedValue: androidx.annotation.InspectableProperty#enumMapping():
-    Method androidx.annotation.InspectableProperty.enumMapping has changed value from {} to nothing
-ChangedValue: androidx.annotation.InspectableProperty#flagMapping():
-    Method androidx.annotation.InspectableProperty.flagMapping has changed value from {} to nothing
-ChangedValue: androidx.annotation.IntDef#value():
-    Method androidx.annotation.IntDef.value has changed value from {} to nothing
-ChangedValue: androidx.annotation.LongDef#value():
-    Method androidx.annotation.LongDef.value has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission#allOf():
-    Method androidx.annotation.RequiresPermission.allOf has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission#anyOf():
-    Method androidx.annotation.RequiresPermission.anyOf has changed value from {} to nothing
-ChangedValue: androidx.annotation.RequiresPermission.Read#value():
-    Method androidx.annotation.RequiresPermission.Read.value has changed value from @androidx.annotation.RequiresPermission to androidx.annotation.RequiresPermission()
-ChangedValue: androidx.annotation.RequiresPermission.Write#value():
-    Method androidx.annotation.RequiresPermission.Write.value has changed value from @androidx.annotation.RequiresPermission to androidx.annotation.RequiresPermission()
-ChangedValue: androidx.annotation.StringDef#value():
-    Method androidx.annotation.StringDef.value has changed value from {} to nothing
diff --git a/appcompat/appcompat-resources/api/restricted_current.ignore b/appcompat/appcompat-resources/api/restricted_current.ignore
deleted file mode 100644
index 93d6e5f..0000000
--- a/appcompat/appcompat-resources/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.appcompat.graphics.drawable.DrawableWrapper:
-    Removed class androidx.appcompat.graphics.drawable.DrawableWrapper
diff --git a/appcompat/appcompat/api/current.ignore b/appcompat/appcompat/api/current.ignore
deleted file mode 100644
index 6900f16b..0000000
--- a/appcompat/appcompat/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedSuperclass: androidx.appcompat.app.AppCompatDialog:
-    Class androidx.appcompat.app.AppCompatDialog superclass changed from android.app.Dialog to androidx.activity.ComponentDialog
diff --git a/appcompat/appcompat/api/restricted_current.ignore b/appcompat/appcompat/api/restricted_current.ignore
deleted file mode 100644
index 6900f16b..0000000
--- a/appcompat/appcompat/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedSuperclass: androidx.appcompat.app.AppCompatDialog:
-    Class androidx.appcompat.app.AppCompatDialog superclass changed from android.app.Dialog to androidx.activity.ComponentDialog
diff --git a/appcompat/appcompat/build.gradle b/appcompat/appcompat/build.gradle
index c32ca99..7f11e4d 100644
--- a/appcompat/appcompat/build.gradle
+++ b/appcompat/appcompat/build.gradle
@@ -24,7 +24,7 @@
     implementation("androidx.emoji2:emoji2-views-helper:1.2.0-rc01")
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.cursoradapter:cursoradapter:1.0.0")
-    api(projectOrArtifact(":activity:activity"))
+    api("androidx.activity:activity:1.6.0")
     api("androidx.fragment:fragment:1.3.6")
     api(project(":appcompat:appcompat-resources"))
     api("androidx.drawerlayout:drawerlayout:1.0.0")
diff --git a/appcompat/appcompat/src/main/res/values-in/strings.xml b/appcompat/appcompat/src/main/res/values-in/strings.xml
index eade686..8e3b809 100644
--- a/appcompat/appcompat/src/main/res/values-in/strings.xml
+++ b/appcompat/appcompat/src/main/res/values-in/strings.xml
@@ -19,7 +19,7 @@
     <string name="abc_action_mode_done" msgid="4692188335987374352">"Selesai"</string>
     <string name="abc_action_bar_home_description" msgid="5976598919945601918">"Tunjukkan jalan ke rumah"</string>
     <string name="abc_action_bar_up_description" msgid="8388173803310557296">"Kembali ke atas"</string>
-    <string name="abc_action_menu_overflow_description" msgid="3937310113216875497">"Opsi lain"</string>
+    <string name="abc_action_menu_overflow_description" msgid="3937310113216875497">"Opsi lainnya"</string>
     <string name="abc_toolbar_collapse_description" msgid="1656852541809559762">"Ciutkan"</string>
     <string name="abc_searchview_description_search" msgid="3417662926640357176">"Telusuri"</string>
     <string name="abc_search_hint" msgid="7208076849092622260">"Telusuri..."</string>
diff --git a/appsearch/appsearch-local-storage/api/current.txt b/appsearch/appsearch-local-storage/api/current.txt
index 94465dd..7289580 100644
--- a/appsearch/appsearch-local-storage/api/current.txt
+++ b/appsearch/appsearch-local-storage/api/current.txt
@@ -2,7 +2,6 @@
 package androidx.appsearch.localstorage {
 
   public class LocalStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.localstorage.LocalStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-local-storage/api/public_plus_experimental_current.txt b/appsearch/appsearch-local-storage/api/public_plus_experimental_current.txt
index 94465dd..7289580 100644
--- a/appsearch/appsearch-local-storage/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch-local-storage/api/public_plus_experimental_current.txt
@@ -2,7 +2,6 @@
 package androidx.appsearch.localstorage {
 
   public class LocalStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.localstorage.LocalStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-local-storage/api/restricted_current.txt b/appsearch/appsearch-local-storage/api/restricted_current.txt
index 94465dd..7289580 100644
--- a/appsearch/appsearch-local-storage/api/restricted_current.txt
+++ b/appsearch/appsearch-local-storage/api/restricted_current.txt
@@ -2,7 +2,6 @@
 package androidx.appsearch.localstorage {
 
   public class LocalStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.localstorage.LocalStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.localstorage.LocalStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
index 5a0dceb..94e56cc 100644
--- a/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
+++ b/appsearch/appsearch-local-storage/src/main/java/androidx/appsearch/localstorage/LocalStorage.java
@@ -281,18 +281,6 @@
     }
 
     /**
-     * @deprecated use {@link #createSearchSessionAsync}
-     * @param context The {@link SearchContext} contains all information to create a new
-     *                {@link AppSearchSession}
-     */
-    @NonNull
-    @Deprecated
-    public static ListenableFuture<AppSearchSession> createSearchSession(
-            @NonNull SearchContext context) {
-        return createSearchSessionAsync(context);
-    }
-
-    /**
      * Opens a new {@link GlobalSearchSession} on this storage.
      *
      * <p>This process requires a native search library. If it's not created, the initialization
@@ -313,18 +301,6 @@
     }
 
     /**
-     * @deprecated use {@link #createGlobalSearchSessionAsync}
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    @NonNull
-    @Deprecated
-    public static ListenableFuture<GlobalSearchSession> createGlobalSearchSession(
-            @NonNull GlobalSearchContext context) {
-        return createGlobalSearchSessionAsync(context);
-    }
-
-    /**
      * Returns the singleton instance of {@link LocalStorage}.
      *
      * <p>If the system is not initialized, it will be initialized using the provided
diff --git a/appsearch/appsearch-platform-storage/api/current.txt b/appsearch/appsearch-platform-storage/api/current.txt
index 0b3b2e6..29bfa55 100644
--- a/appsearch/appsearch-platform-storage/api/current.txt
+++ b/appsearch/appsearch-platform-storage/api/current.txt
@@ -2,9 +2,7 @@
 package androidx.appsearch.platformstorage {
 
   @RequiresApi(android.os.Build.VERSION_CODES.S) public final class PlatformStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSession(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-platform-storage/api/public_plus_experimental_current.txt b/appsearch/appsearch-platform-storage/api/public_plus_experimental_current.txt
index 0b3b2e6..29bfa55 100644
--- a/appsearch/appsearch-platform-storage/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch-platform-storage/api/public_plus_experimental_current.txt
@@ -2,9 +2,7 @@
 package androidx.appsearch.platformstorage {
 
   @RequiresApi(android.os.Build.VERSION_CODES.S) public final class PlatformStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSession(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-platform-storage/api/restricted_current.txt b/appsearch/appsearch-platform-storage/api/restricted_current.txt
index 0b3b2e6..29bfa55 100644
--- a/appsearch/appsearch-platform-storage/api/restricted_current.txt
+++ b/appsearch/appsearch-platform-storage/api/restricted_current.txt
@@ -2,9 +2,7 @@
 package androidx.appsearch.platformstorage {
 
   @RequiresApi(android.os.Build.VERSION_CODES.S) public final class PlatformStorage {
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSession(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GlobalSearchSession!> createGlobalSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.GlobalSearchContext);
-    method @Deprecated public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSession(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
     method public static com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchSession!> createSearchSessionAsync(androidx.appsearch.platformstorage.PlatformStorage.SearchContext);
   }
 
diff --git a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
index 6d0d9b6..1da86d9 100644
--- a/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
+++ b/appsearch/appsearch-platform-storage/src/main/java/androidx/appsearch/platformstorage/PlatformStorage.java
@@ -238,19 +238,6 @@
     }
 
     /**
-     * @deprecated use {@link #createSearchSessionAsync}.
-     *
-     * @param context The {@link SearchContext} contains all information to create a new
-     *                {@link AppSearchSession}
-     */
-    @NonNull
-    @Deprecated
-    public static ListenableFuture<AppSearchSession> createSearchSession(
-            @NonNull SearchContext context) {
-        return createSearchSessionAsync(context);
-    }
-
-    /**
      * Opens a new {@link GlobalSearchSession} on this storage.
      */
     @SuppressLint("WrongConstant")
@@ -279,14 +266,4 @@
                 });
         return future;
     }
-
-    /**
-     * @deprecated use {@link #createGlobalSearchSessionAsync}.
-     */
-    @Deprecated
-    @NonNull
-    public static ListenableFuture<GlobalSearchSession> createGlobalSearchSession(
-            @NonNull GlobalSearchContext context) {
-        return createGlobalSearchSessionAsync(context);
-    }
 }
diff --git a/appsearch/appsearch/api/current.txt b/appsearch/appsearch/api/current.txt
index 2ef699a..d4beb42 100644
--- a/appsearch/appsearch/api/current.txt
+++ b/appsearch/appsearch/api/current.txt
@@ -177,27 +177,17 @@
 
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespacesAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfoAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> put(androidx.appsearch.app.PutDocumentsRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putAsync(androidx.appsearch.app.PutDocumentsRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> remove(androidx.appsearch.app.RemoveByDocumentIdRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> remove(String, androidx.appsearch.app.SearchSpec);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> removeAsync(androidx.appsearch.app.RemoveByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> removeAsync(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsage(androidx.appsearch.app.ReportUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsageAsync(androidx.appsearch.app.ReportUsageRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlush();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlushAsync();
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchemaAsync(androidx.appsearch.app.SetSchemaRequest);
   }
 
@@ -302,10 +292,8 @@
     method public void close();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_BY_ID) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void registerObserverCallback(String, androidx.appsearch.observer.ObserverSpec, java.util.concurrent.Executor, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsageAsync(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void unregisterObserverCallback(String, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
@@ -437,7 +425,6 @@
 
   public interface SearchResults extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPage();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPageAsync();
   }
 
diff --git a/appsearch/appsearch/api/public_plus_experimental_current.txt b/appsearch/appsearch/api/public_plus_experimental_current.txt
index 2ef699a..d4beb42 100644
--- a/appsearch/appsearch/api/public_plus_experimental_current.txt
+++ b/appsearch/appsearch/api/public_plus_experimental_current.txt
@@ -177,27 +177,17 @@
 
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespacesAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfoAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> put(androidx.appsearch.app.PutDocumentsRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putAsync(androidx.appsearch.app.PutDocumentsRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> remove(androidx.appsearch.app.RemoveByDocumentIdRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> remove(String, androidx.appsearch.app.SearchSpec);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> removeAsync(androidx.appsearch.app.RemoveByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> removeAsync(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsage(androidx.appsearch.app.ReportUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsageAsync(androidx.appsearch.app.ReportUsageRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlush();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlushAsync();
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchemaAsync(androidx.appsearch.app.SetSchemaRequest);
   }
 
@@ -302,10 +292,8 @@
     method public void close();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_BY_ID) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void registerObserverCallback(String, androidx.appsearch.observer.ObserverSpec, java.util.concurrent.Executor, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsageAsync(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void unregisterObserverCallback(String, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
@@ -437,7 +425,6 @@
 
   public interface SearchResults extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPage();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPageAsync();
   }
 
diff --git a/appsearch/appsearch/api/restricted_current.txt b/appsearch/appsearch/api/restricted_current.txt
index 2ef699a..d4beb42 100644
--- a/appsearch/appsearch/api/restricted_current.txt
+++ b/appsearch/appsearch/api/restricted_current.txt
@@ -177,27 +177,17 @@
 
   public interface AppSearchSession extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentId(androidx.appsearch.app.GetByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespaces();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.Set<java.lang.String!>!> getNamespacesAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfo();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.StorageInfo!> getStorageInfoAsync();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> put(androidx.appsearch.app.PutDocumentsRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> putAsync(androidx.appsearch.app.PutDocumentsRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> remove(androidx.appsearch.app.RemoveByDocumentIdRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> remove(String, androidx.appsearch.app.SearchSpec);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,java.lang.Void!>!> removeAsync(androidx.appsearch.app.RemoveByDocumentIdRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> removeAsync(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsage(androidx.appsearch.app.ReportUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportUsageAsync(androidx.appsearch.app.ReportUsageRequest);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlush();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> requestFlushAsync();
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchema(androidx.appsearch.app.SetSchemaRequest);
     method public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.SetSchemaResponse!> setSchemaAsync(androidx.appsearch.app.SetSchemaRequest);
   }
 
@@ -302,10 +292,8 @@
     method public void close();
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_BY_ID) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.AppSearchBatchResult<java.lang.String!,androidx.appsearch.app.GenericDocument!>!> getByDocumentIdAsync(String, String, androidx.appsearch.app.GetByDocumentIdRequest);
     method public androidx.appsearch.app.Features getFeatures();
-    method @Deprecated @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public default com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchema(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA) public com.google.common.util.concurrent.ListenableFuture<androidx.appsearch.app.GetSchemaResponse!> getSchemaAsync(String, String);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void registerObserverCallback(String, androidx.appsearch.observer.ObserverSpec, java.util.concurrent.Executor, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsage(androidx.appsearch.app.ReportSystemUsageRequest);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void!> reportSystemUsageAsync(androidx.appsearch.app.ReportSystemUsageRequest);
     method public androidx.appsearch.app.SearchResults search(String, androidx.appsearch.app.SearchSpec);
     method @RequiresFeature(enforcement="androidx.appsearch.app.Features#isFeatureSupported", name=androidx.appsearch.app.Features.GLOBAL_SEARCH_SESSION_REGISTER_OBSERVER_CALLBACK) public void unregisterObserverCallback(String, androidx.appsearch.observer.ObserverCallback) throws androidx.appsearch.exceptions.AppSearchException;
@@ -437,7 +425,6 @@
 
   public interface SearchResults extends java.io.Closeable {
     method public void close();
-    method @Deprecated public default com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPage();
     method public com.google.common.util.concurrent.ListenableFuture<java.util.List<androidx.appsearch.app.SearchResult!>!> getNextPageAsync();
   }
 
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
index a8d9713..3a0f037 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/AppSearchSession.java
@@ -59,18 +59,6 @@
     ListenableFuture<SetSchemaResponse> setSchemaAsync(@NonNull SetSchemaRequest request);
 
     /**
-     * @deprecated use {@link #setSchemaAsync}
-     * @param  request the schema to set or update the AppSearch database to.
-     * @return a {@link ListenableFuture} which resolves to a {@link SetSchemaResponse} object.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<SetSchemaResponse> setSchema(
-            @NonNull SetSchemaRequest request) {
-        return setSchemaAsync(request);
-    }
-
-    /**
      * Retrieves the schema most recently successfully provided to {@link #setSchemaAsync}.
      *
      * @return The pending {@link GetSchemaResponse} of performing this operation.
@@ -81,19 +69,6 @@
     ListenableFuture<GetSchemaResponse> getSchemaAsync();
 
     /**
-     * @deprecated use {@link #getSchemaAsync}
-     *
-     * @return The pending {@link GetSchemaResponse} of performing this operation.
-     */
-    // This call hits disk; async API prevents us from treating these calls as properties.
-    @SuppressLint("KotlinPropertyAccess")
-    @NonNull
-    @Deprecated
-    default ListenableFuture<GetSchemaResponse> getSchema() {
-        return getSchemaAsync();
-    }
-
-    /**
      * Retrieves the set of all namespaces in the current database with at least one document.
      *
      * @return The pending result of performing this operation. */
@@ -101,16 +76,6 @@
     ListenableFuture<Set<String>> getNamespacesAsync();
 
     /**
-     * @deprecated use {@link #getNamespacesAsync()}
-     *
-     * @return The pending result of performing this operation. */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<Set<String>> getNamespaces() {
-        return getNamespacesAsync();
-    }
-
-    /**
      * Indexes documents into the {@link AppSearchSession} database.
      *
      * <p>Each {@link GenericDocument} object must have a {@code schemaType} field set to an
@@ -128,22 +93,6 @@
             @NonNull PutDocumentsRequest request);
 
     /**
-     * @deprecated use {@link #putAsync}
-     *
-     * @param request containing documents to be indexed.
-     * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
-     * The keys of the returned {@link AppSearchBatchResult} are the IDs of the input documents.
-     * The values are either {@code null} if the corresponding document was successfully indexed,
-     * or a failed {@link AppSearchResult} otherwise.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<AppSearchBatchResult<String, Void>> put(
-            @NonNull PutDocumentsRequest request) {
-        return putAsync(request);
-    }
-
-    /**
      * Gets {@link GenericDocument} objects by document IDs in a namespace from the
      * {@link AppSearchSession} database.
      *
@@ -161,25 +110,6 @@
             @NonNull GetByDocumentIdRequest request);
 
     /**
-     * @deprecated use {@link #getByDocumentIdAsync}
-     *
-     * @param request a request containing a namespace and IDs to get documents for.
-     * @return A {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
-     * The keys of the {@link AppSearchBatchResult} represent the input document IDs from the
-     * {@link GetByDocumentIdRequest} object. The values are either the corresponding
-     * {@link GenericDocument} object for the ID on success, or an {@link AppSearchResult}
-     * object on failure. For example, if an ID is not found, the value for that ID will be set
-     * to an {@link AppSearchResult} object with result code:
-     * {@link AppSearchResult#RESULT_NOT_FOUND}.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<AppSearchBatchResult<String, GenericDocument>> getByDocumentId(
-            @NonNull GetByDocumentIdRequest request) {
-        return getByDocumentIdAsync(request);
-    }
-
-    /**
      * Retrieves documents from the open {@link AppSearchSession} that match a given query string
      * and type of search provided.
      *
@@ -340,19 +270,6 @@
     ListenableFuture<Void> reportUsageAsync(@NonNull ReportUsageRequest request);
 
     /**
-     * @deprecated use {@link #reportUsageAsync}
-     *
-     * @param request The usage reporting request.
-     * @return The pending result of performing this operation which resolves to {@code null} on
-     *     success.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<Void> reportUsage(@NonNull ReportUsageRequest request) {
-        return reportUsageAsync(request);
-    }
-
-    /**
      * Removes {@link GenericDocument} objects by document IDs in a namespace from the
      * {@link AppSearchSession} database.
      *
@@ -376,24 +293,6 @@
             @NonNull RemoveByDocumentIdRequest request);
 
     /**
-     * @deprecated use {@link #removeAsync}
-     *
-     * @param request {@link RemoveByDocumentIdRequest} with IDs in a namespace to remove from the
-     *                index.
-     * @return a {@link ListenableFuture} which resolves to an {@link AppSearchBatchResult}.
-     * The keys of the {@link AppSearchBatchResult} represent the input IDs from the
-     * {@link RemoveByDocumentIdRequest} object. The values are either {@code null} on success,
-     * or a failed {@link AppSearchResult} otherwise. IDs that are not found will return a failed
-     * {@link AppSearchResult} with a result code of {@link AppSearchResult#RESULT_NOT_FOUND}.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<AppSearchBatchResult<String, Void>> remove(
-            @NonNull RemoveByDocumentIdRequest request) {
-        return removeAsync(request);
-    }
-
-    /**
      * Removes {@link GenericDocument}s from the index by Query. Documents will be removed if they
      * match the {@code queryExpression} in given namespaces and schemaTypes which is set via
      * {@link SearchSpec.Builder#addFilterNamespaces} and
@@ -415,22 +314,6 @@
             @NonNull SearchSpec searchSpec);
 
     /**
-     * @deprecated use {@link #removeAsync}
-     *
-     * @param queryExpression Query String to search.
-     * @param searchSpec      Spec containing schemaTypes, namespaces and query expression
-     *                        indicates how document will be removed. All specific about how to
-     *                        scoring, ordering, snippeting and resulting will be ignored.
-     * @return The pending result of performing this operation.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<Void> remove(@NonNull String queryExpression,
-            @NonNull SearchSpec searchSpec) {
-        return removeAsync(queryExpression, searchSpec);
-    }
-
-    /**
      * Gets the storage info for this {@link AppSearchSession} database.
      *
      * <p>This may take time proportional to the number of documents and may be inefficient to
@@ -442,17 +325,6 @@
     ListenableFuture<StorageInfo> getStorageInfoAsync();
 
     /**
-     * @deprecated use {@link #getStorageInfoAsync()}
-     *
-     * @return a {@link ListenableFuture} which resolves to a {@link StorageInfo} object.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<StorageInfo> getStorageInfo() {
-        return getStorageInfoAsync();
-    }
-
-    /**
      * Flush all schema and document updates, additions, and deletes to disk if possible.
      *
      * <p>The request is not guaranteed to be handled and may be ignored by some implementations of
@@ -467,20 +339,6 @@
     ListenableFuture<Void> requestFlushAsync();
 
     /**
-     * @deprecated use {@link #requestFlushAsync()}
-     *
-     * @return The pending result of performing this operation.
-     * {@link androidx.appsearch.exceptions.AppSearchException} with
-     * {@link AppSearchResult#RESULT_INTERNAL_ERROR} will be set to the future if we hit error when
-     * save to disk.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<Void> requestFlush() {
-        return requestFlushAsync();
-    }
-
-    /**
      * Returns the {@link Features} to check for the availability of certain features
      * for this session.
      */
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
index 4ce54b4..7f9dfb0 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/GlobalSearchSession.java
@@ -110,21 +110,6 @@
     ListenableFuture<Void> reportSystemUsageAsync(@NonNull ReportSystemUsageRequest request);
 
     /**
-     * @deprecated use {@link #reportSystemUsageAsync}
-     *
-     * @return The pending result of performing this operation which resolves to {@code null} on
-     *     success. The pending result will be completed with an
-     *     {@link androidx.appsearch.exceptions.AppSearchException} with a code of
-     *     {@link AppSearchResult#RESULT_SECURITY_ERROR} if this API is invoked by an app which
-     *     is not part of the system.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<Void> reportSystemUsage(@NonNull ReportSystemUsageRequest request) {
-        return reportSystemUsageAsync(request);
-    }
-
-    /**
      * Retrieves the collection of schemas most recently successfully provided to
      * {@link AppSearchSession#setSchemaAsync} for any types belonging to the requested package and
      * database that the caller has been granted access to.
@@ -151,28 +136,6 @@
             @NonNull String databaseName);
 
     /**
-     * @deprecated use {@link #getSchemaAsync}.
-     *
-     * @param packageName the package that owns the requested {@link AppSearchSchema} instances.
-     * @param databaseName the database that owns the requested {@link AppSearchSchema} instances.
-     * @return The pending {@link GetSchemaResponse} containing the schemas that the caller has
-     * access to or an empty GetSchemaResponse if the request package and database does not
-     * exist, has not set a schema or contains no schemas that are accessible to the caller.
-     */
-    @SuppressLint("KotlinPropertyAccess")
-    @NonNull
-    // @exportToFramework:startStrip()
-    @RequiresFeature(
-            enforcement = "androidx.appsearch.app.Features#isFeatureSupported",
-            name = Features.GLOBAL_SEARCH_SESSION_GET_SCHEMA)
-    // @exportToFramework:endStrip()
-    @Deprecated
-    default ListenableFuture<GetSchemaResponse> getSchema(@NonNull String packageName,
-            @NonNull String databaseName) {
-        return getSchemaAsync(packageName, databaseName);
-    }
-
-    /**
      * Returns the {@link Features} to check for the availability of certain features
      * for this session.
      */
diff --git a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResults.java b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResults.java
index 23b6b5d..2ed19ed 100644
--- a/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResults.java
+++ b/appsearch/appsearch/src/main/java/androidx/appsearch/app/SearchResults.java
@@ -52,17 +52,6 @@
     @NonNull
     ListenableFuture<List<SearchResult>> getNextPageAsync();
 
-    /**
-     * @deprecated use {@link #getNextPageAsync}.
-     * @return a {@link ListenableFuture} which resolves to a list of {@link SearchResult}
-     * objects.
-     */
-    @NonNull
-    @Deprecated
-    default ListenableFuture<List<SearchResult>> getNextPage() {
-        return getNextPageAsync();
-    }
-
     @Override
     void close();
 }
diff --git a/benchmark/benchmark-common/api/current.txt b/benchmark/benchmark-common/api/current.txt
index 8fd1366..421c7d0 100644
--- a/benchmark/benchmark-common/api/current.txt
+++ b/benchmark/benchmark-common/api/current.txt
@@ -35,6 +35,9 @@
   public final class ProfilerKt {
   }
 
+  public final class ShellKt {
+  }
+
   public final class UserspaceTracingKt {
   }
 
diff --git a/benchmark/benchmark-common/api/public_plus_experimental_current.txt b/benchmark/benchmark-common/api/public_plus_experimental_current.txt
index 281c6e7..24e4123 100644
--- a/benchmark/benchmark-common/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-common/api/public_plus_experimental_current.txt
@@ -45,6 +45,9 @@
   public final class ProfilerKt {
   }
 
+  public final class ShellKt {
+  }
+
   public final class UserspaceTracingKt {
   }
 
diff --git a/benchmark/benchmark-common/api/restricted_current.txt b/benchmark/benchmark-common/api/restricted_current.txt
index fb74f25..eb3307a 100644
--- a/benchmark/benchmark-common/api/restricted_current.txt
+++ b/benchmark/benchmark-common/api/restricted_current.txt
@@ -37,6 +37,9 @@
   public final class ProfilerKt {
   }
 
+  public final class ShellKt {
+  }
+
   public final class UserspaceTracingKt {
   }
 
diff --git a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
index 53177b2..4139a44 100644
--- a/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
+++ b/benchmark/benchmark-common/src/androidTest/java/androidx/benchmark/ShellTest.kt
@@ -21,12 +21,6 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
-import org.junit.After
-import org.junit.Assert
-import org.junit.Assume.assumeTrue
-import org.junit.Before
-import org.junit.Test
-import org.junit.runner.RunWith
 import java.io.File
 import kotlin.test.assertContains
 import kotlin.test.assertEquals
@@ -34,6 +28,12 @@
 import kotlin.test.assertNotNull
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
+import org.junit.After
+import org.junit.Assert
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
index ad8695f..92c1f32 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Arguments.kt
@@ -39,10 +39,16 @@
  * @hide
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-public object Arguments {
-
+object Arguments {
     // public properties are shared by micro + macro benchmarks
-    public val suppressedErrors: Set<String>
+    val suppressedErrors: Set<String>
+
+    /**
+     * Set to true to enable androidx.tracing.perfetto tracepoints (such as composition tracing)
+     *
+     * Currently internal/experimental
+     */
+    val fullTracingEnable: Boolean
 
     val enabledRules: Set<RuleType>
 
@@ -56,11 +62,11 @@
     val killProcessDelayMillis: Long
     val enableStartupProfiles: Boolean
     val strictStartupProfiles: Boolean
+    val dryRunMode: Boolean
 
     // internal properties are microbenchmark only
     internal val outputEnable: Boolean
     internal val startupMode: Boolean
-    internal val dryRunMode: Boolean
     internal val iterations: Int?
     private val _profiler: Profiler?
     internal val profiler: Profiler?
@@ -111,6 +117,9 @@
         iterations =
             arguments.getBenchmarkArgument("iterations")?.toInt()
 
+        fullTracingEnable =
+            (arguments.getBenchmarkArgument("fullTracing.enable")?.toBoolean() ?: false)
+
         // Transform comma-delimited list into set of suppressed errors
         // E.g. "DEBUGGABLE, UNLOCKED" -> setOf("DEBUGGABLE", "UNLOCKED")
         suppressedErrors = arguments.getBenchmarkArgument("suppressErrors", "")
@@ -132,8 +141,9 @@
             }
             .toSet()
 
+        // compilation defaults to disabled if dryRunMode is on
         enableCompilation =
-            arguments.getBenchmarkArgument("compilation.enabled")?.toBoolean() ?: true
+            arguments.getBenchmarkArgument("compilation.enabled")?.toBoolean() ?: !dryRunMode
 
         _profiler = arguments.getProfiler(outputEnable)
         profilerSampleFrequency =
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ConfigurationError.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ConfigurationError.kt
index ab498d3..1576062 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/ConfigurationError.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/ConfigurationError.kt
@@ -110,17 +110,21 @@
 fun List<ConfigurationError>.checkAndGetSuppressionState(
     suppressedErrorIds: Set<String>,
 ): ConfigurationError.SuppressionState? {
+    if (isEmpty()) {
+        return null
+    }
+
     val (suppressed, unsuppressed) = partition {
         suppressedErrorIds.contains(it.id)
     }
 
-    val prefix = suppressed.joinToString("_") { it.id } + "_"
+    val prefix = this.joinToString("_") { it.id } + "_"
 
-    val unsuppressedString = unsuppressed.joinToString(" ") { it.id }
-    val suppressedString = suppressed.joinToString(" ") { it.id }
-    val howToSuppressString = this.joinToString(",") { it.id }
-
-    if (unsuppressed.isNotEmpty()) {
+    // either fail and report all unsuppressed errors ...
+    if (unsuppressed.isNotEmpty() && !Arguments.dryRunMode) {
+        val unsuppressedString = unsuppressed.joinToString(" ") { it.id }
+        val suppressedString = suppressed.joinToString(" ") { it.id }
+        val howToSuppressString = this.joinToString(",") { it.id }
         throw AssertionError(
             """
                 |ERRORS (not suppressed): $unsuppressedString
@@ -140,9 +144,6 @@
         )
     }
 
-    if (suppressed.isEmpty()) {
-        return null
-    }
-
-    return ConfigurationError.SuppressionState(prefix, suppressed.prettyPrint("WARNING: "))
+    // ... or report all errors as suppressed
+    return ConfigurationError.SuppressionState(prefix, prettyPrint("WARNING: "))
 }
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
index 367def0..0cd1a71 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/Shell.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import android.os.Looper
+import android.os.ParcelFileDescriptor
 import android.os.ParcelFileDescriptor.AutoCloseInputStream
 import android.os.SystemClock
 import android.util.Log
@@ -25,6 +26,7 @@
 import androidx.annotation.RestrictTo
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.tracing.trace
+import java.io.Closeable
 import java.io.File
 import java.io.InputStream
 import java.nio.charset.Charset
@@ -186,7 +188,11 @@
      */
     @RequiresApi(21)
     fun executeScript(script: String, stdin: String? = null): String {
-        return ShellImpl.executeScript(script, stdin, false).first
+        return ShellImpl
+            .createShellScript(script, stdin, false)
+            .start()
+            .getOutputAndClose()
+            .stdout
     }
 
     data class Output(val stdout: String, val stderr: String)
@@ -212,13 +218,33 @@
         script: String,
         stdin: String? = null
     ): Output {
-        return ShellImpl.executeScript(
-            script = script,
-            stdin = stdin,
-            includeStderr = true
-        ).run {
-            Output(first, second!!)
-        }
+        return ShellImpl
+            .createShellScript(script = script, stdin = stdin, includeStderr = true)
+            .start()
+            .getOutputAndClose()
+    }
+
+    /**
+     * Creates a executable shell script that can be started. Similar to [executeScriptWithStderr]
+     * but allows deferring and caching script execution.
+     *
+     * @param script Script content to run
+     * @param stdin String to pass in as stdin to first command in script
+     *
+     * @return ShellScript that can be started.
+     */
+    @RequiresApi(21)
+    fun createShellScript(
+        script: String,
+        stdin: String? = null,
+        includeStderr: Boolean = true
+    ): ShellScript {
+        return ShellImpl
+            .createShellScript(
+                script = script,
+                stdin = stdin,
+                includeStderr = includeStderr
+            )
     }
 
     @RequiresApi(21)
@@ -400,11 +426,11 @@
         // These variables are used in executeCommand and executeScript, so we keep them as var
         // instead of val and use a separate initializer
         isSessionRooted = executeCommand("id").contains("uid=0(root)")
-        isSuAvailable = executeScript(
+        isSuAvailable = createShellScript(
             "su root id",
             null,
             false
-        ).first.contains("uid=0(root)")
+        ).start().getOutputAndClose().stdout.contains("uid=0(root)")
     }
 
     /**
@@ -412,23 +438,26 @@
      * to avoid the UiAutomator dependency, and add tracing
      */
     fun executeCommand(cmd: String): String = trace("executeCommand $cmd".take(127)) {
-        val parcelFileDescriptor = uiAutomation.executeShellCommand(
-            if (!isSessionRooted && isSuAvailable) {
-                "su root $cmd"
-            } else {
-                cmd
-            }
-        )
-        AutoCloseInputStream(parcelFileDescriptor).use { inputStream ->
-            return@trace inputStream.readBytes().toString(Charset.defaultCharset())
-        }
+        return@trace executeCommandNonBlocking(cmd).fullyReadInputStream()
     }
 
-    fun executeScript(
+    fun executeCommandNonBlocking(cmd: String): ParcelFileDescriptor =
+        trace("executeCommandNonBlocking $cmd".take(127)) {
+            return@trace uiAutomation.executeShellCommand(
+                if (!isSessionRooted && isSuAvailable) {
+                    "su root $cmd"
+                } else {
+                    cmd
+                }
+            )
+        }
+
+    fun createShellScript(
         script: String,
         stdin: String?,
         includeStderr: Boolean
-    ): Pair<String, String?> = trace("executeScript $script".take(127)) {
+    ): ShellScript = trace("createShellScript $script".take(127)) {
+
         // dirUsableByAppAndShell is writable, but we can't execute there (as of Q),
         // so we copy to /data/local/tmp
         val externalDir = Outputs.dirUsableByAppAndShell
@@ -448,6 +477,8 @@
             null
         }
 
+        var shellScript: ShellScript? = null
+
         try {
             var scriptText: String = script
             if (stdinFile != null) {
@@ -464,19 +495,123 @@
             executeCommand("cp ${writableScriptFile.absolutePath} $runnableScriptPath")
             Shell.chmodExecutable(runnableScriptPath)
 
-            val stdout = executeCommand(runnableScriptPath)
-            val stderr = stderrPath?.run { executeCommand("cat $stderrPath") }
+            shellScript = ShellScript(
+                stdinFile = stdinFile,
+                writableScriptFile = writableScriptFile,
+                stderrPath = stderrPath,
+                runnableScriptPath = runnableScriptPath
+            )
 
-            return@trace Pair(stdout, stderr)
-        } finally {
-            stdinFile?.delete()
-            writableScriptFile.delete()
-
-            if (stderrPath != null) {
-                executeCommand("rm $stderrPath $runnableScriptPath")
-            } else {
-                executeCommand("rm $runnableScriptPath")
-            }
+            return@trace shellScript
+        } catch (e: Exception) {
+            shellScript?.cleanUp()
+            throw Exception("Can't create shell script", e)
         }
     }
-}
\ No newline at end of file
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class ShellScript internal constructor(
+    private val stdinFile: File?,
+    private val writableScriptFile: File,
+    private val stderrPath: String?,
+    private val runnableScriptPath: String
+) {
+
+    private var cleanedUp: Boolean = false
+
+    /**
+     * Starts the shell script previously created.
+     *
+     * @param params a vararg string of parameters to be passed to the script.
+     *
+     * @return a [StartedShellScript] that contains streams to read output streams.
+     */
+    fun start(vararg params: String): StartedShellScript = trace("ShellScript#start") {
+        val cmd = "$runnableScriptPath ${params.joinToString(" ")}"
+        val stdoutDescriptor = ShellImpl.executeCommandNonBlocking(cmd)
+        val stderrDescriptor = stderrPath?.run {
+            ShellImpl.executeCommandNonBlocking(
+                "cat $stderrPath"
+            )
+        }
+
+        return@trace StartedShellScript(
+            stdoutDescriptor = stdoutDescriptor,
+            stderrDescriptor = stderrDescriptor,
+            cleanUpBlock = ::cleanUp
+        )
+    }
+
+    /**
+     * Manually clean up the shell script from the temp folder.
+     */
+    fun cleanUp() = trace("ShellScript#cleanUp") {
+        if (cleanedUp) {
+            return@trace
+        }
+        stdinFile?.delete()
+        writableScriptFile.delete()
+        if (stderrPath != null) {
+            ShellImpl.executeCommand("rm $stderrPath $runnableScriptPath")
+        } else {
+            ShellImpl.executeCommand("rm $runnableScriptPath")
+        }
+        cleanedUp = true
+    }
+}
+
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+@RequiresApi(Build.VERSION_CODES.LOLLIPOP)
+class StartedShellScript internal constructor(
+    private val stdoutDescriptor: ParcelFileDescriptor,
+    private val stderrDescriptor: ParcelFileDescriptor?,
+    private val cleanUpBlock: () -> Unit
+) : Closeable {
+
+    /**
+     * Returns a [Sequence] of [String] containing the lines written by the process to stdOut.
+     */
+    fun stdOutLineSequence(): Sequence<String> =
+        AutoCloseInputStream(stdoutDescriptor).bufferedReader().lineSequence()
+
+    /**
+     * Returns a [Sequence] of [String] containing the lines written by the process to stdErr.
+     * Note that if stdErr wasn't required when creating the process it simply returns an
+     * empty string.
+     */
+    fun stdErrLineSequence(): Sequence<String> = if (stderrDescriptor != null) {
+        AutoCloseInputStream(stderrDescriptor).bufferedReader().lineSequence()
+    } else {
+        "".lineSequence()
+    }
+
+    /**
+     * Cleans up this shell script.
+     */
+    override fun close() = cleanUpBlock()
+
+    /**
+     * Returns true whether this shell script has generated any stdErr output.
+     */
+    fun hasStdError(): Boolean = stdErrLineSequence().elementAtOrElse(0) { "" }.isNotBlank()
+
+    /**
+     * Reads the full process output and cleans up the generated script
+     */
+    fun getOutputAndClose(): Shell.Output {
+        val output = Shell.Output(
+            stdout = stdoutDescriptor.fullyReadInputStream(),
+            stderr = stderrDescriptor?.fullyReadInputStream() ?: ""
+        )
+        close()
+        return output
+    }
+}
+
+internal fun ParcelFileDescriptor.fullyReadInputStream(): String {
+    AutoCloseInputStream(this).use { inputStream ->
+        return inputStream.readBytes().toString(Charset.defaultCharset())
+    }
+}
diff --git a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
index b1be5ed..4e88608 100644
--- a/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
+++ b/benchmark/benchmark-common/src/main/java/androidx/benchmark/perfetto/PerfettoCaptureWrapper.kt
@@ -20,6 +20,7 @@
 import android.util.Log
 import androidx.annotation.RequiresApi
 import androidx.annotation.RestrictTo
+import androidx.benchmark.Arguments
 import androidx.benchmark.Outputs
 import androidx.benchmark.Outputs.dateToFileName
 import androidx.benchmark.PropOverride
@@ -41,10 +42,22 @@
 
     @RequiresApi(Build.VERSION_CODES.LOLLIPOP)
     private fun start(packages: List<String>): Boolean {
-        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
-            Log.d(PerfettoHelper.LOG_TAG, "Recording perfetto trace")
-            capture?.start(packages)
+        capture?.apply {
+            if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
+                Log.d(PerfettoHelper.LOG_TAG, "Recording perfetto trace")
+                if (Arguments.fullTracingEnable &&
+                    packages.isNotEmpty() &&
+                    Build.VERSION.SDK_INT >= Build.VERSION_CODES.R
+                ) {
+                    enableAndroidxTracingPerfetto(
+                        targetPackage = packages.first(),
+                        provideBinariesIfMissing = true
+                    )
+                }
+                start(packages)
+            }
         }
+
         return true
     }
 
diff --git a/benchmark/benchmark-darwin-core/build.gradle b/benchmark/benchmark-darwin-core/build.gradle
index d27219b..89758d4 100644
--- a/benchmark/benchmark-darwin-core/build.gradle
+++ b/benchmark/benchmark-darwin-core/build.gradle
@@ -20,9 +20,6 @@
         }
     }
 
-    // b/243154573
-    jvm()
-
     ios {
         compilations.main {
             cinterops {
diff --git a/benchmark/benchmark-darwin-core/src/commonMain/Placeholder.kt b/benchmark/benchmark-darwin-core/src/commonMain/Placeholder.kt
deleted file mode 100644
index 5afbba8..0000000
--- a/benchmark/benchmark-darwin-core/src/commonMain/Placeholder.kt
+++ /dev/null
@@ -1,3 +0,0 @@
-@file:RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-
-// Defining a placeholder here to work around b/243154573
diff --git a/benchmark/benchmark-darwin-samples-xcode/iosAppUnitTests/main/Benchmarks.swift b/benchmark/benchmark-darwin-samples-xcode/iosAppUnitTests/main/Benchmarks.swift
new file mode 100644
index 0000000..b11fccb
--- /dev/null
+++ b/benchmark/benchmark-darwin-samples-xcode/iosAppUnitTests/main/Benchmarks.swift
@@ -0,0 +1,46 @@
+/*
+ * 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.
+ */
+
+import Foundation
+import XCTest
+import AndroidXDarwinSampleBenchmarks
+
+class BenchmarkTest: XCTestCase {
+    var testCase: TestCase? = nil
+    override func setUpWithError() throws {
+        testCase!.setUp()
+    }
+
+    override class var defaultTestSuite: XCTestSuite {
+        let suite = XCTestSuite(forTestCaseClass: BenchmarkTest.self)
+        let testCases = TestCases.shared.benchmarkTests()
+        for testCase in testCases {
+            let test = BenchmarkTest(selector: #selector(runBenchmark))
+            test.testCase = testCase
+            suite.addTest(test)
+        }
+        return suite
+    }
+
+    @objc func runBenchmark() {
+        // https://masilotti.com/xctest-name-readability/
+        XCTContext.runActivity(named: testCase!.testDescription()) { _ -> Void in
+            // Run the actual benchmark
+            let context = TestCaseContextWrapper(context: self)
+            testCase?.benchmark(context: context)
+        }
+    }
+}
diff --git a/benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt b/benchmark/benchmark-darwin-samples-xcode/iosSources/main/ContentView.swift
similarity index 72%
copy from benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt
copy to benchmark/benchmark-darwin-samples-xcode/iosSources/main/ContentView.swift
index bdc7389..407d57ad 100644
--- a/benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt
+++ b/benchmark/benchmark-darwin-samples-xcode/iosSources/main/ContentView.swift
@@ -14,9 +14,18 @@
  * limitations under the License.
  */
 
-package androidx.benchmark.darwin
+import SwiftUI
 
-/**
- * Test measurement options that control how many iterations to run.
- */
-actual data class MeasureOptions(actual var iterationCount: ULong)
+struct ContentView: View {
+  let greet = "Hello Benchmarking"
+
+  var body: some View {
+    Text(greet)
+  }
+}
+
+struct ContentView_Previews: PreviewProvider {
+  static var previews: some View {
+    ContentView()
+  }
+}
diff --git a/benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt b/benchmark/benchmark-darwin-samples-xcode/iosSources/main/iosApp.swift
similarity index 76%
rename from benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt
rename to benchmark/benchmark-darwin-samples-xcode/iosSources/main/iosApp.swift
index bdc7389..1600119c 100644
--- a/benchmark/benchmark-darwin/src/jvmMain/kotlin/androidx/benchmark/darwin/MeasureOptions.kt
+++ b/benchmark/benchmark-darwin-samples-xcode/iosSources/main/iosApp.swift
@@ -14,9 +14,13 @@
  * limitations under the License.
  */
 
-package androidx.benchmark.darwin
+import SwiftUI
 
-/**
- * Test measurement options that control how many iterations to run.
- */
-actual data class MeasureOptions(actual var iterationCount: ULong)
+@main
+struct iOSApp: App {
+  var body: some Scene {
+    WindowGroup {
+      ContentView()
+    }
+  }
+}
diff --git a/benchmark/benchmark-darwin-samples-xcode/run-benchmarks.sh b/benchmark/benchmark-darwin-samples-xcode/run-benchmarks.sh
new file mode 100755
index 0000000..0a3f119
--- /dev/null
+++ b/benchmark/benchmark-darwin-samples-xcode/run-benchmarks.sh
@@ -0,0 +1,12 @@
+#!/bin/bash
+
+# Runs benchmark tests.
+
+rm -rf benchmark-darwin-sample-xcode.xcodeproj
+xcodegen --spec xcodegen-project.yml
+
+xcodebuild \
+ test \
+ -project benchmark-darwin-sample-xcode.xcodeproj \
+ -scheme testapp-ios \
+ -destination 'platform=iOS Simulator,name=iPhone 13,OS=15.2'
diff --git a/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml b/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml
new file mode 100644
index 0000000..27052a5
--- /dev/null
+++ b/benchmark/benchmark-darwin-samples-xcode/xcodegen-project.yml
@@ -0,0 +1,51 @@
+# XCodeGen for the :benchmark:benchmark-darwin-samples module..
+
+name: benchmark-darwin-sample-xcode
+targets:
+
+  testapp-ios:
+    type: application
+    platform: iOS
+    info:
+      path: Info.plist
+    sources:
+      - path: 'iosSources/main'
+    scheme:
+      testTargets:
+        - testapp-ios-benchmarks
+      gatherCoverageData: false
+    settings:
+      PRODUCT_NAME: testapp-ios
+
+  testapp-ios-benchmarks:
+    type: bundle.unit-test
+    platform: iOS
+    info:
+      path: Info.plist
+    sources:
+      - path: 'iosAppUnitTests/main'
+    scheme:
+      preActions:
+        - name: build AndroidXDarwinSampleBenchmarks.xcframework
+          basedOnDependencyAnalysis: false
+          settingsTarget: testapp-ios
+          script: |
+            cd ${PROJECT_DIR}/../..
+            ./gradlew :benchmark:benchmark-darwin-samples:assembleAndroidXDarwinSampleBenchmarksDebugXCFramework \
+                --no-configuration-cache                         \
+                -Pandroidx.enabled.kmp.target.platforms="+MAC"
+          outputFiles:
+            - "${PROJECT_DIR}/../../../../out/androidx/benchmark/benchmark-darwin-samples/build/XCFrameworks/debug/AndroidXDarwinSampleBenchmarks.xcframework"
+    dependencies:
+      - framework: "${PROJECT_DIR}/../../../../out/androidx/benchmark/benchmark-darwin-samples/build/XCFrameworks/debug/AndroidXDarwinSampleBenchmarks.xcframework"
+    settings:
+      PRODUCT_NAME: testapp-ios-benchmarks
+
+settings:
+  PRODUCT_BUNDLE_IDENTIFIER: androidx.benchmark
+  SWIFT_VERSION: 5
+  CODE_SIGN_IDENTITY: ''
+  CODE_SIGNING_REQUIRED: 'NO'
+  CODE_SIGN_ENTITLEMENTS: ''
+  CODE_SIGNING_ALLOWED: 'NO'
+  IPHONEOS_DEPLOYMENT_TARGET: 15.2
diff --git a/benchmark/benchmark-darwin-samples-xcode/xcodegenw.sh b/benchmark/benchmark-darwin-samples-xcode/xcodegenw.sh
new file mode 100755
index 0000000..fdd0cf3
--- /dev/null
+++ b/benchmark/benchmark-darwin-samples-xcode/xcodegenw.sh
@@ -0,0 +1,5 @@
+#!/bin/bash
+# Generates the XCode project to run / debug benchmark tests.
+
+xcodegen --spec xcodegen-project.yml
+open benchmark-darwin-sample-xcode.xcodeproj
diff --git a/benchmark/benchmark-darwin-samples/build.gradle b/benchmark/benchmark-darwin-samples/build.gradle
new file mode 100644
index 0000000..f31dd41
--- /dev/null
+++ b/benchmark/benchmark-darwin-samples/build.gradle
@@ -0,0 +1,54 @@
+import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
+
+plugins {
+    id("AndroidXPlugin")
+}
+
+androidXMultiplatform {
+
+    def xcf = new XCFrameworkConfig(project, "AndroidXDarwinSampleBenchmarks")
+
+    ios {
+        binaries.framework {
+            baseName = "AndroidXDarwinSampleBenchmarks"
+            // https://youtrack.jetbrains.com/issue/KT-48552
+            embedBitcode = BitcodeEmbeddingMode.DISABLE
+            export(project(":benchmark:benchmark-darwin"))
+            xcf.add(it)
+        }
+    }
+
+    sourceSets {
+        commonMain {
+            dependencies {
+                api(libs.kotlinStdlib)
+            }
+        }
+        commonTest {
+            dependencies {
+                implementation(libs.kotlinTest)
+                implementation(libs.kotlinTestAnnotationsCommon)
+            }
+        }
+        iosArm64Main {
+            dependsOn(commonMain)
+            dependencies {
+                api(project(":benchmark:benchmark-darwin"))
+            }
+        }
+        iosSimulatorArm64Main {
+            dependsOn(iosArm64Main)
+        }
+        iosX64Main {
+            dependsOn(iosArm64Main)
+        }
+    }
+}
+
+androidx {
+    name = "AndroidX Benchmarks - Darwin Samples"
+    mavenGroup = LibraryGroups.BENCHMARK
+    inceptionYear = "2022"
+    description = "AndroidX Benchmarks - Darwin Samples"
+}
diff --git a/benchmark/benchmark-darwin/src/iosArm64Main/kotlin/androidx/benchmark/darwin/tests/SleepTestCase.kt b/benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/ArrayListAllocationBenchmark.kt
similarity index 69%
rename from benchmark/benchmark-darwin/src/iosArm64Main/kotlin/androidx/benchmark/darwin/tests/SleepTestCase.kt
rename to benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/ArrayListAllocationBenchmark.kt
index cb7559c..2e03b35 100644
--- a/benchmark/benchmark-darwin/src/iosArm64Main/kotlin/androidx/benchmark/darwin/tests/SleepTestCase.kt
+++ b/benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/ArrayListAllocationBenchmark.kt
@@ -14,24 +14,21 @@
  * limitations under the License.
  */
 
-package androidx.benchmark.darwin.tests
+package androidx.benchmark.darwin.samples
 
 import androidx.benchmark.darwin.TestCase
 import androidx.benchmark.darwin.TestCaseContext
-import androidx.benchmark.darwin.TestCases
-import platform.Foundation.NSLog
 import platform.XCTest.XCTMeasureOptions
-import platform.posix.sleep
 
-class SleepTestCase : TestCase() {
+class ArrayListAllocationBenchmark : TestCase() {
     override fun setUp() {
-        NSLog("%s", "Hello Benchmarks !")
+        // does nothing
     }
 
     override fun benchmark(context: TestCaseContext) {
         val options = XCTMeasureOptions.defaultOptions()
-        // A single iteration
-        options.iterationCount = 1.toULong()
+        // 5 Iterations
+        options.iterationCount = 5.toULong()
         context.measureWithMetrics(
             listOf(
                 platform.XCTest.XCTCPUMetric(),
@@ -40,20 +37,19 @@
             ),
             options
         ) {
-            repeat(3) {
-                NSLog("%s", "Sleeping for 1 second")
-                sleep(1)
+            // Do something a bit expensive
+            repeat(1000) {
+                ArrayList<Float>(SIZE)
             }
         }
     }
 
     override fun testDescription(): String {
-        return "A test that sleeps for 3 seconds"
+        return "Allocate an ArrayList of size $SIZE"
     }
 
     companion object {
-        fun addBenchmarkTest() {
-            TestCases.addBenchmarkTest(SleepTestCase())
-        }
+        // The initial capacity of the allocation
+        private const val SIZE = 1000
     }
 }
diff --git a/benchmark/benchmark-darwin/src/commonMain/kotlin/androidx/benchmark/darwin/TestCases.kt b/benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/TestCases.kt
similarity index 78%
rename from benchmark/benchmark-darwin/src/commonMain/kotlin/androidx/benchmark/darwin/TestCases.kt
rename to benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/TestCases.kt
index d9bca8a..6ec9870 100644
--- a/benchmark/benchmark-darwin/src/commonMain/kotlin/androidx/benchmark/darwin/TestCases.kt
+++ b/benchmark/benchmark-darwin-samples/src/iosArm64Main/kotlin/androidx/benchmark/darwin/samples/TestCases.kt
@@ -14,18 +14,17 @@
  * limitations under the License.
  */
 
-package androidx.benchmark.darwin
+package androidx.benchmark.darwin.samples
+
+import androidx.benchmark.darwin.TestCase
 
 /**
  * Returns a [List] of [TestCase]s to run for benchmarks.
  */
 object TestCases {
-    private val testCases = mutableSetOf<TestCase>()
     fun benchmarkTests(): List<TestCase> {
-        return testCases.toList()
-    }
-
-    fun addBenchmarkTest(testCase: TestCase) {
-        testCases += testCase
+        return listOf(
+            ArrayListAllocationBenchmark()
+        )
     }
 }
diff --git a/benchmark/benchmark-darwin/build.gradle b/benchmark/benchmark-darwin/build.gradle
index e98a138..51acaba5 100644
--- a/benchmark/benchmark-darwin/build.gradle
+++ b/benchmark/benchmark-darwin/build.gradle
@@ -1,17 +1,18 @@
 import org.jetbrains.kotlin.gradle.plugin.mpp.BitcodeEmbeddingMode
+import org.jetbrains.kotlin.gradle.plugin.mpp.apple.XCFrameworkConfig
 
 plugins {
     id("AndroidXPlugin")
 }
 
 androidXMultiplatform {
-    // b/243154573
-    jvm()
+    def xcf = new XCFrameworkConfig(project, "AndroidXDarwinBenchmarks")
     ios {
         binaries.framework {
             baseName = "AndroidXDarwinBenchmarks"
             // https://youtrack.jetbrains.com/issue/KT-48552
             embedBitcode = BitcodeEmbeddingMode.DISABLE
+            xcf.add(it)
         }
     }
     sourceSets {
diff --git a/benchmark/benchmark-macro/api/current.txt b/benchmark/benchmark-macro/api/current.txt
index 68a4a8c..7840107 100644
--- a/benchmark/benchmark-macro/api/current.txt
+++ b/benchmark/benchmark-macro/api/current.txt
@@ -107,6 +107,12 @@
   public final class ForceTracingKt {
   }
 
+  public final class PerfettoTraceProcessorKt {
+  }
+
+  public final class SliceKt {
+  }
+
   public final class StringHelperKt {
   }
 
diff --git a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
index f2aa08e..cc03eaa 100644
--- a/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
+++ b/benchmark/benchmark-macro/api/public_plus_experimental_current.txt
@@ -128,6 +128,12 @@
   public final class ForceTracingKt {
   }
 
+  public final class PerfettoTraceProcessorKt {
+  }
+
+  public final class SliceKt {
+  }
+
   public final class StringHelperKt {
   }
 
diff --git a/benchmark/benchmark-macro/api/restricted_current.txt b/benchmark/benchmark-macro/api/restricted_current.txt
index 90b9e32..7b95bc2 100644
--- a/benchmark/benchmark-macro/api/restricted_current.txt
+++ b/benchmark/benchmark-macro/api/restricted_current.txt
@@ -181,12 +181,10 @@
   public final class ForceTracingKt {
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public final class PerfettoTraceProcessor {
-    method public String getJsonMetrics(String absoluteTracePath, String metric);
-    method @org.jetbrains.annotations.TestOnly public String getShellPath();
-    method public String rawQuery(String absoluteTracePath, String query);
-    property @org.jetbrains.annotations.TestOnly public final String shellPath;
-    field public static final androidx.benchmark.macro.perfetto.PerfettoTraceProcessor INSTANCE;
+  public final class PerfettoTraceProcessorKt {
+  }
+
+  public final class SliceKt {
   }
 
   public final class StringHelperKt {
diff --git a/benchmark/benchmark-macro/build.gradle b/benchmark/benchmark-macro/build.gradle
index 2a1c4a6..158b519 100644
--- a/benchmark/benchmark-macro/build.gradle
+++ b/benchmark/benchmark-macro/build.gradle
@@ -22,6 +22,7 @@
     id("AndroidXPlugin")
     id("com.android.library")
     id("kotlin-android")
+    id("com.squareup.wire")
 }
 
 android {
@@ -57,6 +58,9 @@
     implementation("androidx.tracing:tracing-ktx:1.1.0-rc01")
     implementation(libs.testCore)
     implementation(libs.testUiautomator)
+    implementation(libs.wireRuntime)
+    implementation(libs.retrofit)
+    implementation(libs.retrofitConverterWire)
 
     androidTestImplementation(project(":internal-testutils-ktx"))
     androidTestImplementation("androidx.activity:activity-ktx:1.3.1")
@@ -75,3 +79,72 @@
     inceptionYear = "2020"
     description = "Android Benchmark - Macrobenchmark"
 }
+
+// Allow usage of Kotlin's @OptIn.
+tasks.withType(KotlinCompile).configureEach {
+    kotlinOptions {
+        freeCompilerArgs += [ '-opt-in=kotlin.RequiresOptIn' ]
+    }
+}
+
+wire {
+    kotlin {}
+    sourcePath {
+        srcDir 'src/main/proto'
+        include '*.proto'
+    }
+
+    prune 'perfetto.protos.AndroidBatteryMetric'
+    prune 'perfetto.protos.AndroidBinderMetric'
+    prune 'perfetto.protos.AndroidCameraMetric'
+    prune 'perfetto.protos.AndroidCameraUnaggregatedMetric'
+    prune 'perfetto.protos.AndroidCpuMetric'
+    prune 'perfetto.protos.AndroidDisplayMetrics'
+    prune 'perfetto.protos.AndroidDmaHeapMetric'
+    prune 'perfetto.protos.AndroidDvfsMetric'
+    prune 'perfetto.protos.AndroidFastrpcMetric'
+    prune 'perfetto.protos.AndroidFrameTimelineMetric'
+    prune 'perfetto.protos.AndroidGpuMetric'
+    prune 'perfetto.protos.AndroidHwcomposerMetrics'
+    prune 'perfetto.protos.AndroidHwuiMetric'
+    prune 'perfetto.protos.AndroidIonMetric'
+    prune 'perfetto.protos.AndroidIrqRuntimeMetric'
+    prune 'perfetto.protos.AndroidJankCujMetric'
+    prune 'perfetto.protos.AndroidLmkMetric'
+    prune 'perfetto.protos.AndroidLmkReasonMetric'
+    prune 'perfetto.protos.AndroidMemoryMetric'
+    prune 'perfetto.protos.AndroidMemoryUnaggregatedMetric'
+    prune 'perfetto.protos.AndroidMultiuserMetric'
+    prune 'perfetto.protos.AndroidNetworkMetric'
+    prune 'perfetto.protos.AndroidOtherTracesMetric'
+    prune 'perfetto.protos.AndroidPackageList'
+    prune 'perfetto.protos.AndroidPowerRails'
+    prune 'perfetto.protos.AndroidProcessMetadata'
+    prune 'perfetto.protos.AndroidRtRuntimeMetric'
+    prune 'perfetto.protos.AndroidSimpleperfMetric'
+    prune 'perfetto.protos.AndroidSurfaceflingerMetric'
+    prune 'perfetto.protos.AndroidSysUiCujMetrics'
+    prune 'perfetto.protos.AndroidTaskNames'
+    prune 'perfetto.protos.AndroidTraceQualityMetric'
+    prune 'perfetto.protos.AndroidTrustyWorkqueues'
+    prune 'perfetto.protos.G2dMetrics'
+    prune 'perfetto.protos.JavaHeapHistogram'
+    prune 'perfetto.protos.JavaHeapStats'
+    prune 'perfetto.protos.ProcessRenderInfo'
+    prune 'perfetto.protos.ProfilerSmaps'
+    prune 'perfetto.protos.TraceAnalysisStats'
+    prune 'perfetto.protos.TraceMetadata'
+    prune 'perfetto.protos.UnsymbolizedFrames'
+}
+
+// https://github.com/square/wire/issues/1947
+// Remove when we upgrade to fixed wire library
+afterEvaluate {
+    tasks.named("compileReleaseKotlin").configure {
+        it.dependsOn("generateDebugProtos")
+    }
+    tasks.named("compileDebugKotlin").configure {
+        it.dependsOn("generateReleaseProtos")
+    }
+}
+
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ConfigurableActivity.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ConfigurableActivity.kt
index 81dde1e..edfd61f 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ConfigurableActivity.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/ConfigurableActivity.kt
@@ -56,6 +56,16 @@
                 view.postDelayed(runnable, reportFullyDrawnDelayMs)
             }
         }
+        // enable in-app navigation, which carries forward report fully drawn delay
+        view.setOnClickListener {
+            startActivity(
+                createIntent(
+                    text = INNER_ACTIVITY_TEXT,
+                    reportFullyDrawnDelayMs = reportFullyDrawnDelayMs
+                )
+                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK)
+            )
+        }
     }
 
     companion object {
@@ -65,17 +75,18 @@
         private const val EXTRA_SLEEP_DUR_MS: String = "SLEEP_DUR_MS"
         private const val EXTRA_REPORT_FULLY_DRAWN_DELAY_MS = "REPORT_FULLY_DRAWN_DELAY_MS"
         const val FULLY_DRAWN_TEXT = "FULLY DRAWN"
+        const val INNER_ACTIVITY_TEXT = "INNER ACTIVITY"
 
         fun createIntent(
             text: String,
             sleepDurMs: Long = 0,
-            reportFullyDrawnWithDelay: Long? = null
+            reportFullyDrawnDelayMs: Long? = null
         ): Intent {
             return Intent().apply {
                 action = ACTION
                 putExtra(EXTRA_TEXT, text)
                 putExtra(EXTRA_SLEEP_DUR_MS, sleepDurMs)
-                putExtra(EXTRA_REPORT_FULLY_DRAWN_DELAY_MS, reportFullyDrawnWithDelay)
+                putExtra(EXTRA_REPORT_FULLY_DRAWN_DELAY_MS, reportFullyDrawnDelayMs)
             }
         }
     }
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FrameStatsResultTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FrameStatsResultTest.kt
index d6a19a2..49cecb3 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FrameStatsResultTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/FrameStatsResultTest.kt
@@ -31,13 +31,11 @@
             listOf(
                 FrameStatsResult(
                     uniqueName = "com.pkg/com.pkg.MyActivity1/android.view.ViewRootImpl@ade24ea",
-                    lastFrameNs = 4211995467212,
-                    lastLaunchNs = 3211995467212
+                    lastFrameNs = 4211995467212
                 ),
                 FrameStatsResult(
                     uniqueName = "com.pkg/com.pkg.MyActivity2/android.view.ViewRootImpl@e8a2229b",
-                    lastFrameNs = 6117484488193,
-                    lastLaunchNs = 5117484488193
+                    lastFrameNs = 6117484488193
                 )
             ),
             FrameStatsResult.parse(
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
index ec67850..0815a2a 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/MacrobenchmarkScopeTest.kt
@@ -202,10 +202,6 @@
 
         if (pressHome) {
             assertTrue(secondFrameStats.lastFrameNs!! > initialFrameStats.lastFrameNs!!)
-            if (Build.VERSION.SDK_INT >= 29) {
-                // data not trustworthy before API 29
-                assertTrue(secondFrameStats.lastLaunchNs!! > initialFrameStats.lastLaunchNs!!)
-            }
         }
     }
 
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
index 4f7f64c..d741589 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/PowerMetricTest.kt
@@ -18,6 +18,7 @@
 
 import android.os.Build
 import androidx.annotation.RequiresApi
+import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 import kotlin.test.assertEquals
 import org.junit.Assume.assumeTrue
@@ -40,9 +41,9 @@
         val categories = PowerCategory.values()
             .associateWith { PowerCategoryDisplayLevel.BREAKDOWN }
 
-        val actualMetrics = PowerMetric(
-            PowerMetric.Energy(categories)
-        ).getMetrics(captureInfo, traceFile.absolutePath)
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerMetric(PowerMetric.Energy(categories)).getMetrics(captureInfo, this)
+        }
 
         assertEquals(
             IterationResult(
@@ -78,21 +79,21 @@
         val categories = PowerCategory.values()
             .associateWith { PowerCategoryDisplayLevel.TOTAL }
 
-        val actualMetrics = PowerMetric(
-            PowerMetric.Power(categories)
-        ).getMetrics(captureInfo, traceFile.absolutePath)
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerMetric(PowerMetric.Power(categories)).getMetrics(captureInfo, this)
+        }
 
         assertEquals(
             IterationResult(
                 singleMetrics = mapOf(
-                    "powerCategoryCpuUw" to 80.940907,
-                    "powerCategoryDisplayUw" to 208.777524,
-                    "powerCategoryGpuUw" to 13.799502,
-                    "powerCategoryMemoryUw" to 73.69686899999999,
-                    "powerCategoryMachineLearningUw" to 10.52768,
-                    "powerCategoryNetworkUw" to 123.74248399999999,
-                    "powerUncategorizedUw" to 25.454282,
-                    "powerTotalUw" to 536.939248
+                    "powerCategoryCpuUw" to 80.94090814845532,
+                    "powerCategoryDisplayUw" to 208.77752436243003,
+                    "powerCategoryGpuUw" to 13.799502384408045,
+                    "powerCategoryMemoryUw" to 73.69686916856728,
+                    "powerCategoryMachineLearningUw" to 10.527679867302508,
+                    "powerCategoryNetworkUw" to 123.74248393116318,
+                    "powerUncategorizedUw" to 25.454281567489115,
+                    "powerTotalUw" to 536.9392494298155,
                 ),
                 sampledMetrics = emptyMap()
             ), actualMetrics
@@ -113,20 +114,20 @@
             PowerCategory.UNCATEGORIZED to PowerCategoryDisplayLevel.BREAKDOWN
         )
 
-        val actualMetrics = PowerMetric(
-            PowerMetric.Power(categories)
-        ).getMetrics(captureInfo, traceFile.absolutePath)
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerMetric(PowerMetric.Power(categories)).getMetrics(captureInfo, this)
+        }
 
         assertEquals(
             IterationResult(
                 singleMetrics = mapOf(
-                    "powerCategoryCpuUw" to 80.940907,
-                    "powerCategoryDisplayUw" to 208.777524,
-                    "powerCategoryMemoryUw" to 73.69686899999999,
-                    "powerCategoryNetworkUw" to 123.74248399999999,
-                    "powerComponentSystemFabricUw" to 25.454282,
-                    "powerUnselectedUw" to 24.327182,
-                    "powerTotalUw" to 536.939248
+                    "powerCategoryCpuUw" to 80.94090814845532,
+                    "powerCategoryDisplayUw" to 208.77752436243003,
+                    "powerCategoryMemoryUw" to 73.69686916856728,
+                    "powerCategoryNetworkUw" to 123.74248393116318,
+                    "powerComponentSystemFabricUw" to 25.454281567489115,
+                    "powerUnselectedUw" to 24.327182251710553,
+                    "powerTotalUw" to 536.9392494298155
                 ),
                 sampledMetrics = emptyMap()
             ), actualMetrics
@@ -142,9 +143,9 @@
         val categories = PowerCategory.values()
             .associateWith { PowerCategoryDisplayLevel.BREAKDOWN }
 
-        val actualMetrics = PowerMetric(
-            PowerMetric.Energy(categories)
-        ).getMetrics(captureInfo, traceFile.absolutePath)
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerMetric(PowerMetric.Energy(categories)).getMetrics(captureInfo, this)
+        }
 
         assertEquals(
             IterationResult(
@@ -161,9 +162,9 @@
 
         val traceFile = createTempFileFromAsset("api31_battery_discharge", ".perfetto-trace")
 
-        val actualMetrics = PowerMetric(
-            PowerMetric.Battery()
-        ).getMetrics(captureInfo, traceFile.absolutePath)
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerMetric(PowerMetric.Battery()).getMetrics(captureInfo, this)
+        }
 
         assertEquals(
             IterationResult(
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
index 04d3195..f206893 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/StartupTimingMetricTest.kt
@@ -21,6 +21,7 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.benchmark.Outputs
+import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
 import androidx.benchmark.perfetto.PerfettoCaptureWrapper
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -30,14 +31,15 @@
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.uiautomator.By
 import androidx.test.uiautomator.UiDevice
+import androidx.test.uiautomator.UiObject2
 import androidx.test.uiautomator.Until
-import org.junit.Assume.assumeTrue
-import org.junit.Test
-import org.junit.runner.RunWith
 import java.io.File
 import kotlin.test.assertEquals
 import kotlin.test.assertNotNull
 import kotlin.test.assertTrue
+import org.junit.Assume.assumeTrue
+import org.junit.Test
+import org.junit.runner.RunWith
 
 @SdkSuppress(minSdkVersion = 23)
 @RunWith(AndroidJUnit4::class)
@@ -59,7 +61,7 @@
     // lack of profileable. Within our test process (other startup tests in this class), we use
     // reflection to force reportFullyDrawn() to be traced. See b/182386956
     @SdkSuppress(minSdkVersion = 29)
-    fun validateStartup() {
+    fun startup() {
         assumeTrue(isAbiSupported())
         val packageName = "androidx.benchmark.integration.macrobenchmark.target"
         val scope = MacrobenchmarkScope(packageName = packageName, launchWithClearTask = true)
@@ -82,26 +84,59 @@
         assertNotNull(iterationResult.timelineRangeNs)
     }
 
-    private fun validateStartup_fullyDrawn(delayMs: Long) {
+    /**
+     * Validate that reasonable startup and fully drawn metrics are extracted, either from
+     * startActivityAndWait, or from in-app Activity based navigation
+     */
+    private fun validateStartup_fullyDrawn(
+        delayMs: Long,
+        useInAppNav: Boolean = false
+    ) {
+        val awaitActivityText: (String) -> UiObject2 = { expectedText ->
+            UiDevice
+                .getInstance(InstrumentationRegistry.getInstrumentation())
+                .wait(Until.findObject(By.text(expectedText)), 3000)!!
+        }
         assumeTrue(isAbiSupported())
-        val scope = MacrobenchmarkScope(packageName = Packages.TEST, launchWithClearTask = true)
-        val iterationResult = measureStartup(Packages.TEST, StartupMode.WARM) {
-            // Simulate a warm start, since it's our own process
-            scope.pressHome()
-            scope.startActivityAndWait(
-                ConfigurableActivity.createIntent(
-                    text = "ORIGINAL TEXT",
-                    reportFullyDrawnWithDelay = delayMs
-                )
-            )
 
+        val scope = MacrobenchmarkScope(packageName = Packages.TEST, launchWithClearTask = true)
+        val launchIntent = ConfigurableActivity.createIntent(
+            text = "ORIGINAL TEXT",
+            reportFullyDrawnDelayMs = delayMs
+        )
+        // setup initial Activity if needed
+        if (useInAppNav) {
+            scope.startActivityAndWait(launchIntent)
             if (delayMs > 0) {
-                UiDevice
-                    .getInstance(InstrumentationRegistry.getInstrumentation())
-                    .wait(Until.findObject(By.text(ConfigurableActivity.FULLY_DRAWN_TEXT)), 3000)
+                awaitActivityText(ConfigurableActivity.FULLY_DRAWN_TEXT)
             }
         }
 
+        // measure the activity launch
+        val iterationResult = measureStartup(Packages.TEST, StartupMode.WARM) {
+            // Simulate a warm start, since it's our own process
+            if (useInAppNav) {
+                // click the textview, which triggers an activity launch
+                awaitActivityText(
+                    if (delayMs > 0) {
+                        ConfigurableActivity.FULLY_DRAWN_TEXT
+                    } else {
+                        "ORIGINAL TEXT"
+                    }
+                ).click()
+            } else {
+                scope.pressHome()
+                scope.startActivityAndWait(launchIntent)
+            }
+
+            if (delayMs > 0) {
+                awaitActivityText(ConfigurableActivity.FULLY_DRAWN_TEXT)
+            } else if (useInAppNav) {
+                awaitActivityText(ConfigurableActivity.INNER_ACTIVITY_TEXT)
+            }
+        }
+
+        // validate
         assertEquals(
             setOf("timeToInitialDisplayMs", "timeToFullDisplayMs"),
             iterationResult.singleMetrics.keys
@@ -128,30 +163,45 @@
 
     @LargeTest
     @Test
-    fun validateStartup_fullyDrawn_immediate() {
-        validateStartup_fullyDrawn(0)
+    fun startup_fullyDrawn_immediate() {
+        validateStartup_fullyDrawn(delayMs = 0)
     }
 
     @LargeTest
     @Test
-    fun validateStartup_fullyDrawn_delayed() {
-        validateStartup_fullyDrawn(100)
+    fun startup_fullyDrawn_delayed() {
+        validateStartup_fullyDrawn(delayMs = 100)
+    }
+
+    @LargeTest
+    @Test
+    fun startupInAppNav_immediate() {
+        validateStartup_fullyDrawn(delayMs = 0, useInAppNav = true)
+    }
+
+    @LargeTest
+    @Test
+    fun startupInAppNav_fullyDrawn() {
+        validateStartup_fullyDrawn(delayMs = 100, useInAppNav = true)
     }
 
     private fun getApi32WarmMetrics(metric: Metric): IterationResult {
         assumeTrue(isAbiSupported())
         val traceFile = createTempFileFromAsset("api32_startup_warm", ".perfetto-trace")
         val packageName = "androidx.benchmark.integration.macrobenchmark.target"
+
         metric.configure(packageName)
-        return metric.getMetrics(
-            captureInfo = Metric.CaptureInfo(
-                targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
-                testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
-                startupMode = StartupMode.WARM,
-                apiLevel = 32
-            ),
-            tracePath = traceFile.absolutePath
-        )
+        return PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            metric.getMetrics(
+                captureInfo = Metric.CaptureInfo(
+                    targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
+                    testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
+                    startupMode = StartupMode.WARM,
+                    apiLevel = 32
+                ),
+                perfettoTraceProcessor = this
+            )
+        }
     }
 
     @MediumTest
@@ -164,15 +214,18 @@
         )
         val metric = StartupTimingMetric()
         metric.configure(Packages.TEST)
-        val metrics = metric.getMetrics(
-            captureInfo = Metric.CaptureInfo(
-                targetPackageName = Packages.TEST,
-                testPackageName = Packages.TEST,
-                startupMode = StartupMode.WARM,
-                apiLevel = 24
-            ),
-            tracePath = traceFile.absolutePath
-        )
+
+        val metrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            metric.getMetrics(
+                captureInfo = Metric.CaptureInfo(
+                    targetPackageName = Packages.TEST,
+                    testPackageName = Packages.TEST,
+                    startupMode = StartupMode.WARM,
+                    apiLevel = 24
+                ),
+                perfettoTraceProcessor = this
+            )
+        }
 
         // check known values
         assertEquals(
@@ -232,15 +285,18 @@
         },
         block = measureBlock
     )!!
-    return metric.getMetrics(
-        captureInfo = Metric.CaptureInfo(
-            targetPackageName = packageName,
-            testPackageName = Packages.TEST,
-            startupMode = startupMode,
-            apiLevel = Build.VERSION.SDK_INT
-        ),
-        tracePath = tracePath
-    )
+
+    return PerfettoTraceProcessor.runServer(tracePath) {
+        metric.getMetrics(
+            captureInfo = Metric.CaptureInfo(
+                targetPackageName = packageName,
+                testPackageName = Packages.TEST,
+                startupMode = startupMode,
+                apiLevel = Build.VERSION.SDK_INT
+            ),
+            perfettoTraceProcessor = this
+        )
+    }
 }
 
 @Suppress("SameParameterValue")
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
index 9f97a44..8fc7256 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/TraceSectionMetricTest.kt
@@ -17,6 +17,7 @@
 package androidx.benchmark.macro
 
 import android.annotation.SuppressLint
+import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
 import androidx.benchmark.perfetto.PerfettoHelper
 import androidx.test.filters.MediumTest
 import kotlin.test.assertEquals
@@ -96,10 +97,13 @@
             val metric = TraceSectionMetric(sectionName)
             val expectedKey = sectionName + "Ms"
             metric.configure(packageName = packageName)
-            val iterationResult = metric.getMetrics(
-                captureInfo = captureInfo,
-                tracePath = tracePath
-            )
+
+            val iterationResult = PerfettoTraceProcessor.runServer(tracePath) {
+                metric.getMetrics(
+                    captureInfo = captureInfo,
+                    perfettoTraceProcessor = this
+                )
+            }
 
             assertEquals(setOf(expectedKey), iterationResult.singleMetrics.keys)
             assertEquals(expectedMs, iterationResult.singleMetrics[expectedKey]!!, 0.001)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AndroidxTracingTraceTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AndroidxTracingTraceTest.kt
index 16d883e..6bfe76a 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AndroidxTracingTraceTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AndroidxTracingTraceTest.kt
@@ -94,11 +94,11 @@
 
         perfettoCapture.stop(traceFilePath)
 
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = traceFilePath,
-            query = QUERY
-        )
-        val matchingSlices = Slice.parseListFromQueryResult(queryResult)
+        val queryResult = PerfettoTraceProcessor.runServer(traceFilePath) {
+            rawQuery(query = QUERY)
+        }
+
+        val matchingSlices = queryResult.toSlices()
         assertEquals(
             List(10) { "$PREFIX$it" } +
                 listOf(
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AudioUnderrunQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AudioUnderrunQueryTest.kt
index f3fd1ca..5798301 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AudioUnderrunQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/AudioUnderrunQueryTest.kt
@@ -37,7 +37,10 @@
         // the trace was generated during 2 seconds AudioUnderrunBenchmark scenario run
         val traceFile = createTempFileFromAsset("api23_audio_underrun", ".perfetto-trace")
 
-        val subMetrics = AudioUnderrunQuery.getSubMetrics(traceFile.absolutePath)
+        val subMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            AudioUnderrunQuery.getSubMetrics(this)
+        }
+
         val expectedMetrics = AudioUnderrunQuery.SubMetrics(2212, 892)
 
         assertEquals(expectedMetrics, subMetrics)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/BatteryDischargeQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/BatteryDischargeQueryTest.kt
index c7610fc..ea8984b 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/BatteryDischargeQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/BatteryDischargeQueryTest.kt
@@ -36,12 +36,12 @@
         assumeTrue(isAbiSupported())
 
         val traceFile = createTempFileFromAsset("api31_battery_discharge", ".perfetto-trace")
-        val slice = PerfettoTraceProcessor.querySlices(
-            traceFile.absolutePath, PowerMetric.MEASURE_BLOCK_SECTION_NAME
-        ).first()
-        val actualMetrics = BatteryDischargeQuery.getBatteryDischargeMetrics(
-            traceFile.absolutePath, slice
-        )
+
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            val slice = querySlices(PowerMetric.MEASURE_BLOCK_SECTION_NAME).first()
+            BatteryDischargeQuery.getBatteryDischargeMetrics(this, slice)
+        }
+
         assertEquals(listOf(
             BatteryDischargeQuery.BatteryDischargeMeasurement(
                 name = "Start",
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/FrameTimingQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/FrameTimingQueryTest.kt
index 547bab3..35e287c 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/FrameTimingQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/FrameTimingQueryTest.kt
@@ -36,11 +36,13 @@
         assumeTrue(isAbiSupported())
         val traceFile = createTempFileFromAsset("api28_scroll", ".perfetto-trace")
 
-        val frameSubMetrics = FrameTimingQuery.getFrameSubMetrics(
-            absoluteTracePath = traceFile.absolutePath,
-            captureApiLevel = 28,
-            packageName = "androidx.benchmark.integration.macrobenchmark.target"
-        )
+        val frameSubMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            FrameTimingQuery.getFrameSubMetrics(
+                perfettoTraceProcessor = this,
+                captureApiLevel = 28,
+                packageName = "androidx.benchmark.integration.macrobenchmark.target"
+            )
+        }
 
         assertEquals(
             expected = mapOf(
@@ -64,11 +66,14 @@
         assumeTrue(isAbiSupported())
         val traceFile = createTempFileFromAsset("api31_scroll", ".perfetto-trace")
 
-        val frameSubMetrics = FrameTimingQuery.getFrameSubMetrics(
-            absoluteTracePath = traceFile.absolutePath,
-            captureApiLevel = 31,
-            packageName = "androidx.benchmark.integration.macrobenchmark.target"
-        )
+        val frameSubMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            FrameTimingQuery.getFrameSubMetrics(
+                perfettoTraceProcessor = this,
+                captureApiLevel = 31,
+                packageName = "androidx.benchmark.integration.macrobenchmark.target"
+            )
+        }
+
         assertEquals(
             expected = mapOf(
                 FrameDurationCpuNs to listOf(6881407L, 5648542L, 3830261L, 4343438L),
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
index c9062e1..ff6b62a 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoCaptureSweepTest.kt
@@ -102,10 +102,9 @@
 
         perfettoCapture.stop(traceFilePath)
 
-        val matchingSlices = PerfettoTraceProcessor.querySlices(
-            absoluteTracePath = traceFilePath,
-            "PerfettoCaptureTest_%"
-        )
+        val matchingSlices = PerfettoTraceProcessor.runServer(traceFilePath) {
+            querySlices("PerfettoCaptureTest_%")
+        }
 
         // Note: this test avoids validating platform-triggered trace sections, to avoid flakes
         // from legitimate (and coincidental) platform use during test.
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoResultsParserTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoResultsParserTest.kt
deleted file mode 100644
index 8a55dba..0000000
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoResultsParserTest.kt
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Copyright 2021 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark.macro.perfetto
-
-import androidx.benchmark.macro.IterationResult
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.SmallTest
-import org.junit.Test
-import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-
-@SmallTest
-@RunWith(AndroidJUnit4::class)
-class PerfettoResultsParserTest {
-    private fun startupJson(fullyDrawn: Boolean): String {
-        val fullyDrawnString = if (fullyDrawn) {
-            """
-            "report_fully_drawn": {
-                "dur_ns": 204445333,
-                "dur_ms": 204.445333
-            },
-            """
-        } else {
-            ""
-        }
-        return """
-    {
-      "android_startup": {
-        "startup": [
-          {
-            "startup_id": 2,
-            "package_name": "androidx.benchmark.macro.test",
-            "process_name": "androidx.benchmark.macro.test",
-            "zygote_new_process": 0,
-            "to_first_frame": {
-              "dur_ns": 149438504,
-              "main_thread_by_task_state": {
-                "running_dur_ns": 66840634,
-                "runnable_dur_ns": 13585470,
-                "uninterruptible_sleep_dur_ns": 2215416,
-                "interruptible_sleep_dur_ns": 45290784
-              },
-              "other_processes_spawned_count": 0,
-              "time_activity_manager": {
-                "dur_ns": 12352501,
-                "dur_ms": 12.352501
-              },
-              "time_activity_start": {
-                "dur_ns": 53247818,
-                "dur_ms": 53.247818
-              },
-              "time_activity_resume": {
-                "dur_ns": 11945314,
-                "dur_ms": 11.945314
-              },
-              "time_choreographer": {
-                "dur_ns": 45386619,
-                "dur_ms": 45.386619
-              },
-              "dur_ms": 149.438504,
-              "time_inflate": {
-                "dur_ns": 8330678,
-                "dur_ms": 8.330678
-              },
-              "time_get_resources": {
-                "dur_ns": 1426719,
-                "dur_ms": 1.426719
-              },
-              "time_verify_class": {
-                "dur_ns": 7012711,
-                "dur_ms": 7.012711
-              },
-              "mcycles_by_core_type": {
-                "little": 415,
-                "big": 446,
-                "bigger": 152
-              },
-              "jit_compiled_methods": 19,
-              "time_jit_thread_pool_on_cpu": {
-                "dur_ns": 6647968,
-                "dur_ms": 6.647968
-              }
-            },
-            "activity_hosting_process_count": 1,
-            "process": {
-              "name": "androidx.benchmark.macro.test",
-              "uid": 10327
-            },
-            $fullyDrawnString
-            "activities": [
-              {
-                "name": "androidx.benchmark.macro.ConfigurableActivity",
-                "method": "performCreate",
-                "ts_method_start": 345883126877037
-              },
-              {
-                "name": "androidx.benchmark.macro.ConfigurableActivity",
-                "method": "performResume",
-                "ts_method_start": 345883158971676
-              }
-            ],
-            "event_timestamps": {
-              "intent_received": 345883080735887,
-              "first_frame": 345883230174391
-            }
-          }
-        ]
-      }
-    }
-    """
-    }
-
-    @Test
-    fun parseStartupResult_notFullyDrawn() {
-        assertEquals(
-            PerfettoResultsParser.parseStartupResult(
-                startupJson(fullyDrawn = false),
-                "androidx.benchmark.macro.test"
-            ),
-            IterationResult(
-                singleMetrics = mapOf("startupMs" to 149.438504),
-                sampledMetrics = emptyMap(),
-                timelineRangeNs = 345883080735887..345883230174391
-            )
-        )
-    }
-
-    @Test
-    fun parseStartupResult_fullyDrawn() {
-        assertEquals(
-            PerfettoResultsParser.parseStartupResult(
-                startupJson(fullyDrawn = true),
-                "androidx.benchmark.macro.test"
-            ),
-            IterationResult(
-                singleMetrics = mapOf(
-                    "startupMs" to 149.438504,
-                    "fullyDrawnMs" to 204.445333
-                ),
-                sampledMetrics = emptyMap(),
-                timelineRangeNs = 345883080735887..345883230174391
-            )
-        )
-    }
-}
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
index 0a6ef65..d72114d 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoSdkHandshakeTest.kt
@@ -47,7 +47,7 @@
 import org.junit.runners.Parameterized
 import org.junit.runners.Parameterized.Parameters
 
-private const val tracingPerfettoVersion = "1.0.0-alpha03" // TODO(224510255): get by 'reflection'
+private const val tracingPerfettoVersion = "1.0.0-alpha04" // TODO(224510255): get by 'reflection'
 private const val minSupportedSdk = Build.VERSION_CODES.R // TODO(234351579): Support API < 30
 
 @RunWith(Parameterized::class)
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessorTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessorTest.kt
index 210fe48..d0193ff 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessorTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessorTest.kt
@@ -18,21 +18,23 @@
 
 import androidx.benchmark.Shell
 import androidx.benchmark.macro.createTempFileFromAsset
+import androidx.benchmark.macro.perfetto.server.PerfettoApi
 import androidx.benchmark.perfetto.PerfettoHelper.Companion.isAbiSupported
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
 import androidx.test.platform.app.InstrumentationRegistry
+import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import org.junit.Assert.assertTrue
 import org.junit.Assume.assumeFalse
 import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
-import kotlin.test.assertEquals
-import kotlin.test.assertFailsWith
 
 @SmallTest
 @RunWith(AndroidJUnit4::class)
 class PerfettoTraceProcessorTest {
+
     @Test
     fun shellPath() {
         assumeTrue(isAbiSupported())
@@ -48,7 +50,7 @@
     fun getJsonMetrics_tracePathWithSpaces() {
         assumeTrue(isAbiSupported())
         assertFailsWith<IllegalArgumentException> {
-            PerfettoTraceProcessor.getJsonMetrics("/a b", "ignored")
+            PerfettoTraceProcessor.runServer("/a b") { }
         }
     }
 
@@ -56,7 +58,14 @@
     fun getJsonMetrics_metricWithSpaces() {
         assumeTrue(isAbiSupported())
         assertFailsWith<IllegalArgumentException> {
-            PerfettoTraceProcessor.getJsonMetrics("/ignored", "a b")
+            PerfettoTraceProcessor.runServer(
+                createTempFileFromAsset(
+                    "api31_startup_cold",
+                    ".perfetto-trace"
+                ).absolutePath
+            ) {
+                getTraceMetrics("a b")
+            }
         }
     }
 
@@ -68,7 +77,14 @@
         }
 
         assertFailsWith<IllegalStateException> {
-            PerfettoTraceProcessor.getJsonMetrics("ignored_path", "ignored_metric")
+            PerfettoTraceProcessor.runServer(
+                createTempFileFromAsset(
+                    "api31_startup_cold",
+                    ".perfetto-trace"
+                ).absolutePath
+            ) {
+                getTraceMetrics("ignored_metric")
+            }
         }
     }
 
@@ -77,35 +93,35 @@
         // check known slice content is queryable
         assumeTrue(isAbiSupported())
         val traceFile = createTempFileFromAsset("api31_startup_cold", ".perfetto-trace")
-        assertEquals(
-            expected = listOf(
-                Slice(
-                    name = "activityStart",
-                    ts = 186975009436431,
-                    dur = 29580628
-                )
-            ),
-            actual = PerfettoTraceProcessor.querySlices(traceFile.absolutePath, "activityStart")
-        )
-        assertEquals(
-            expected = listOf(
-                Slice(
-                    name = "activityStart",
-                    ts = 186975009436431,
-                    dur = 29580628
+        PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+
+            assertEquals(
+                expected = listOf(
+                    Slice(
+                        name = "activityStart",
+                        ts = 186975009436431,
+                        dur = 29580628
+                    )
                 ),
-                Slice(
-                    name = "activityResume",
-                    ts = 186975039764298,
-                    dur = 6570418
-                )
-            ),
-            actual = PerfettoTraceProcessor.querySlices(
-                traceFile.absolutePath,
-                "activityStart",
-                "activityResume"
-            ).sortedBy { it.ts }
-        )
+                actual = querySlices("activityStart")
+            )
+            assertEquals(
+                expected = listOf(
+                    Slice(
+                        name = "activityStart",
+                        ts = 186975009436431,
+                        dur = 29580628
+                    ),
+                    Slice(
+                        name = "activityResume",
+                        ts = 186975039764298,
+                        dur = 6570418
+                    )
+                ),
+                actual = querySlices("activityStart", "activityResume")
+                    .sortedBy { it.ts }
+            )
+        }
     }
 
     @Test
@@ -119,4 +135,30 @@
             assets.toSet().containsAll(entries)
         )
     }
+
+    @Test
+    fun runServerShouldHandleStartAndStopServer() {
+        assumeTrue(isAbiSupported())
+
+        val perfettoApi = PerfettoApi.create("http://localhost:10555/")
+        fun isRunning(): Boolean =
+            try {
+                perfettoApi.status().execute()
+                true
+            } catch (e: Exception) {
+                false
+            }
+
+        // Check server is not running
+        assertTrue(!isRunning())
+
+        PerfettoTraceProcessor.runServer(httpServerPort = 10555) {
+
+            // Check server is running
+            assertTrue(isRunning())
+        }
+
+        // Check server is not running
+        assertTrue(!isRunning())
+    }
 }
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PowerQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PowerQueryTest.kt
index dfd591f..74e74ee 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PowerQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/PowerQueryTest.kt
@@ -23,10 +23,10 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SdkSuppress
 import androidx.test.filters.SmallTest
+import kotlin.test.assertEquals
 import org.junit.Assume.assumeTrue
 import org.junit.Test
 import org.junit.runner.RunWith
-import kotlin.test.assertEquals
 
 @SdkSuppress(minSdkVersion = 29)
 @RunWith(AndroidJUnit4::class)
@@ -37,109 +37,113 @@
         assumeTrue(isAbiSupported())
 
         val traceFile = createTempFileFromAsset("api32_odpm_rails", ".perfetto-trace")
-        val slice = PerfettoTraceProcessor.querySlices(
-            traceFile.absolutePath, MEASURE_BLOCK_SECTION_NAME
-        ).first()
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerQuery.getPowerMetrics(
+                this,
+                querySlices(MEASURE_BLOCK_SECTION_NAME).first()
+            )
+        }
 
-        val actualMetrics = PowerQuery.getPowerMetrics(traceFile.absolutePath, slice)
         assertEquals(
             mapOf(
                 PowerCategory.CPU to PowerQuery.CategoryMeasurement(
                     energyUws = 390378.0,
-                    powerUw = 80.940907,
+                    powerUw = 80.94090814845532,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "CpuBig",
                             energyUws = 31935.0,
-                            powerUw = 6.621397
+                            powerUw = 6.621397470454074
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "CpuLittle",
                             energyUws = 303264.0,
-                            powerUw = 62.878706
+                            powerUw = 62.878706199460915
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "CpuMid",
                             energyUws = 55179.0,
-                            powerUw = 11.440804
+                            powerUw = 11.440804478540327
                         )
                     )
                 ),
                 PowerCategory.DISPLAY to PowerQuery.CategoryMeasurement(
                     energyUws = 1006934.0,
-                    powerUw = 208.777524,
+                    powerUw = 208.77752436243003,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "Display",
                             energyUws = 1006934.0,
-                            powerUw = 208.777524
+                            powerUw = 208.77752436243003
                         )
                     )
                 ),
                 PowerCategory.GPU to PowerQuery.CategoryMeasurement(
                     energyUws = 66555.0,
-                    powerUw = 13.799502,
+                    powerUw = 13.799502384408045,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "Gpu",
                             energyUws = 66555.0,
-                            powerUw = 13.799502
+                            powerUw = 13.799502384408045
                         )
                     )
                 ),
                 PowerCategory.MEMORY to PowerQuery.CategoryMeasurement(
                     energyUws = 355440.0,
-                    powerUw = 73.69686899999999,
+                    powerUw = 73.69686916856728,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "DdrA",
                             energyUws = 48458.0,
-                            powerUw = 10.047273
+                            powerUw = 10.047273481235745
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "DdrB",
                             energyUws = 54988.0,
-                            powerUw = 11.401203
+                            powerUw = 11.401202571013892
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "DdrC",
                             energyUws = 100082.0,
-                            powerUw = 20.750985),
+                            powerUw = 20.75098486419241
+                        ),
                         PowerQuery.ComponentMeasurement(
                             name = "MemoryInterface",
                             energyUws = 151912.0,
-                            powerUw = 31.497408
+                            powerUw = 31.497408252125233
                         ),
                     )
                 ),
                 PowerCategory.MACHINE_LEARNING to PowerQuery.CategoryMeasurement(
                     energyUws = 50775.0,
-                    powerUw = 10.52768,
+                    powerUw = 10.527679867302508,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "Tpu",
                             energyUws = 50775.0,
-                            powerUw = 10.52768
+                            powerUw = 10.527679867302508
                         )
                     )
                 ),
                 PowerCategory.NETWORK to PowerQuery.CategoryMeasurement(
                     energyUws = 596810.0,
-                    powerUw = 123.74248399999999,
+                    powerUw = 123.74248393116318,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "AocLogic",
                             energyUws = 74972.0,
-                            powerUw = 15.544682),
+                            powerUw = 15.544681733360978
+                        ),
                         PowerQuery.ComponentMeasurement(
                             name = "AocMemory",
                             energyUws = 19601.0,
-                            powerUw = 4.064068
+                            powerUw = 4.0640680074642335
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "Modem",
                             energyUws = 8369.0,
-                            powerUw = 1.735227
+                            powerUw = 1.7352270371138296
                         ),
                         PowerQuery.ComponentMeasurement(
                             name = "RadioFrontend",
@@ -149,22 +153,23 @@
                         PowerQuery.ComponentMeasurement(
                             name = "WifiBt",
                             energyUws = 493868.0,
-                            powerUw = 102.398507
+                            powerUw = 102.39850715322413
                         )
                     )
                 ),
                 PowerCategory.UNCATEGORIZED to PowerQuery.CategoryMeasurement(
                     energyUws = 122766.0,
-                    powerUw = 25.454282,
+                    powerUw = 25.454281567489115,
                     components = listOf(
                         PowerQuery.ComponentMeasurement(
                             name = "SystemFabric",
                             energyUws = 122766.0,
-                            powerUw = 25.454282
+                            powerUw = 25.454281567489115
                         )
                     )
                 )
-            ), actualMetrics)
+            ), actualMetrics
+        )
     }
 
     @Test
@@ -172,12 +177,11 @@
         assumeTrue(isAbiSupported())
 
         val traceFile = createTempFileFromAsset("api31_odpm_rails_empty", ".perfetto-trace")
-        val slice = PerfettoTraceProcessor.querySlices(
-            traceFile.absolutePath, MEASURE_BLOCK_SECTION_NAME
-        ).first()
-        val actualMetrics = PowerQuery.getPowerMetrics(
-            traceFile.absolutePath, slice
-        )
+
+        val actualMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            PowerQuery.getPowerMetrics(this, querySlices(MEASURE_BLOCK_SECTION_NAME).first())
+        }
+
         assertEquals(emptyMap(), actualMetrics)
     }
 }
diff --git a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
index b913cfa..7023a69 100644
--- a/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
+++ b/benchmark/benchmark-macro/src/androidTest/java/androidx/benchmark/macro/perfetto/StartupTimingQueryTest.kt
@@ -39,13 +39,14 @@
         assumeTrue(isAbiSupported())
         val traceFile = createTempFileFromAsset(prefix = tracePrefix, suffix = ".perfetto-trace")
 
-        val startupSubMetrics = StartupTimingQuery.getFrameSubMetrics(
-            absoluteTracePath = traceFile.absolutePath,
-            captureApiLevel = api,
-            targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
-            testPackageName = "androidx.benchmark.integration.macrobenchmark.test",
-            startupMode = startupMode
-        )
+        val startupSubMetrics = PerfettoTraceProcessor.runServer(traceFile.absolutePath) {
+            StartupTimingQuery.getFrameSubMetrics(
+                perfettoTraceProcessor = this,
+                captureApiLevel = api,
+                targetPackageName = "androidx.benchmark.integration.macrobenchmark.target",
+                startupMode = startupMode
+            )
+        }
 
         assertEquals(expected = expectedMetrics, actual = startupSubMetrics)
     }
diff --git a/benchmark/benchmark-macro/src/main/AndroidManifest.xml b/benchmark/benchmark-macro/src/main/AndroidManifest.xml
index e71c620..a042c7b 100644
--- a/benchmark/benchmark-macro/src/main/AndroidManifest.xml
+++ b/benchmark/benchmark-macro/src/main/AndroidManifest.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2020 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,8 +13,8 @@
   ~ See the License for the specific language governing permissions and
   ~ limitations under the License.
   -->
-<manifest xmlns:tools="http://schemas.android.com/tools"
-    xmlns:android="http://schemas.android.com/apk/res/android">
+<manifest xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools">
 
     <!-- QUERY_ALL_PACKAGES is used to enable macrobenchmarks to read package information from
     potential target packages, e.g. for querying debuggable, profileable, or default launch intent.
@@ -24,6 +23,15 @@
     target package, but this removes the need for documenting that setup step, and detecting
     misconfiguration.
     -->
-    <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
+
+    <!-- usesCleartextTraffic is required for perfetto trace shell processor http server-->
+    <application android:usesCleartextTraffic="true" />
+    <uses-permission
+        android:name="android.permission.QUERY_ALL_PACKAGES"
         tools:ignore="QueryAllPackagesPermission" />
+
+    <!-- Internet permission is required for perfetto trace shell processor http server but
+        it's used to reach localhost only-->
+    <uses-permission android:name="android.permission.INTERNET" />
+
 </manifest>
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/FrameStatsResult.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/FrameStatsResult.kt
index 11b9e31..761053b 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/FrameStatsResult.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/FrameStatsResult.kt
@@ -27,14 +27,7 @@
     /**
      * Most recent clock monotonic (e.g. System.nanoTime()) timestamp of a frame's vsync.
      */
-    val lastFrameNs: Long?,
-
-    /**
-     * Most recent clock monotonic (e.g. System.nanoTime()) timestamp of a launch frame's vsync.
-     *
-     * Note: may be null pre-API 29, as it may fail to be detected
-     */
-    val lastLaunchNs: Long?
+    val lastFrameNs: Long?
 ) {
     companion object {
         private val NAME_REGEX = Regex("(\\S+) \\(visibility=[0-9]+\\)")
@@ -62,12 +55,7 @@
                     FrameStatsResult(
                         uniqueName = uniqueName,
                         lastFrameNs = profileDataLatestActivityLaunchNs(
-                            profileData,
-                            requireFlag = false
-                        ),
-                        lastLaunchNs = profileDataLatestActivityLaunchNs(
-                            profileData,
-                            requireFlag = true
+                            profileData
                         )
                     )
                 }
@@ -86,30 +74,22 @@
          * 0,6038928372818,//...
          * ```
          *
-         * Would return `5077693738881` - most recent intended vsync of frame with
-         * 0x1 flag from table
+         * Would return `6038928372818` - most recent intended vsync of frame
+         *
+         * Note that we previously checked for flag & 0x1 when looking specifically for initial
+         * frames or a startup, but this behavior is inconsistent, especially on emulators
          */
-        private fun profileDataLatestActivityLaunchNs(
-            profileData: String,
-            requireFlag: Boolean
-        ): Long? {
+        private fun profileDataLatestActivityLaunchNs(profileData: String): Long? {
             val lines = profileData.split(Regex("\r?\n"))
 
             val columnLabels = lines.first().split(",")
-            val flagsIndex = columnLabels.indexOf("Flags")
             val intendedVsyncIndex = columnLabels.indexOf("IntendedVsync")
 
             lines.forEachIndexed { index, s -> println("$index $s") }
             return lines
                 .drop(1)
-                .mapNotNull {
-                    val columns = it.split(",")
-                    if (!requireFlag || (columns[flagsIndex].toLong() and 0x1L) != 0L) {
-                        // 0x1L mask means initial frame
-                        columns[intendedVsyncIndex].toLong()
-                    } else {
-                        null
-                    }
+                .map {
+                    it.split(",")[intendedVsyncIndex].toLong()
                 }
                 .maxOfOrNull { it }
         }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
index fbb5d40..16a6787 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Macrobenchmark.kt
@@ -30,6 +30,7 @@
 import androidx.benchmark.UserspaceTracing
 import androidx.benchmark.checkAndGetSuppressionState
 import androidx.benchmark.conditionalError
+import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
 import androidx.benchmark.perfetto.PerfettoCaptureWrapper
 import androidx.benchmark.perfetto.UiState
 import androidx.benchmark.perfetto.appendUiState
@@ -153,86 +154,99 @@
         metrics.forEach {
             it.configure(packageName)
         }
-        val measurements = List(iterations) { iteration ->
-            // Wake the device to ensure it stays awake with large iteration count
-            userspaceTrace("wake device") {
-                scope.device.wakeUp()
-            }
-
-            scope.iteration = iteration
-            userspaceTrace("setupBlock") {
-                setupBlock(scope)
-            }
-
-            val tracePath = perfettoCollector.record(
-                benchmarkName = uniqueName,
-                iteration = iteration,
-
-                /**
-                 * Prior to API 24, every package name was joined into a single setprop which can
-                 * overflow, and disable *ALL* app level tracing.
-                 *
-                 * For safety here, we only trace the macrobench package on newer platforms, and use
-                 * reflection in the macrobench test process to trace important sections
-                 *
-                 * @see androidx.benchmark.macro.perfetto.ForceTracing
-                 */
-                packages = if (Build.VERSION.SDK_INT >= 24) {
-                    listOf(packageName, macrobenchPackageName)
-                } else {
-                    listOf(packageName)
+        val measurements = PerfettoTraceProcessor.runServer {
+            List(if (Arguments.dryRunMode) 1 else iterations) { iteration ->
+                // Wake the device to ensure it stays awake with large iteration count
+                userspaceTrace("wake device") {
+                    scope.device.wakeUp()
                 }
-            ) {
-                try {
-                    trace("start metrics") {
-                        metrics.forEach {
-                            it.start()
+
+                scope.iteration = iteration
+                userspaceTrace("setupBlock") {
+                    setupBlock(scope)
+                }
+
+                val tracePath = perfettoCollector.record(
+                    benchmarkName = uniqueName,
+                    iteration = iteration,
+
+                    /**
+                     * Prior to API 24, every package name was joined into a single setprop which
+                     * can overflow, and disable *ALL* app level tracing.
+                     *
+                     * For safety here, we only trace the macrobench package on newer platforms,
+                     * and use reflection in the macrobench test process to trace important
+                     * sections
+                     *
+                     * @see androidx.benchmark.macro.perfetto.ForceTracing
+                     */
+                    packages = if (Build.VERSION.SDK_INT >= 24) {
+                        listOf(packageName, macrobenchPackageName)
+                    } else {
+                        listOf(packageName)
+                    }
+                ) {
+                    try {
+                        trace("start metrics") {
+                            metrics.forEach {
+                                it.start()
+                            }
+                        }
+                        trace("measureBlock") {
+                            measureBlock(scope)
+                        }
+                    } finally {
+                        trace("stop metrics") {
+                            metrics.forEach {
+                                it.stop()
+                            }
                         }
                     }
-                    trace("measureBlock") {
-                        measureBlock(scope)
+                }!!
+
+                tracePaths.add(tracePath)
+
+                // Loads a new trace in the perfetto trace processor shell
+                loadTrace(tracePath)
+                val iterationResult =
+                    // Extracts the metrics using the perfetto trace processor
+                    userspaceTrace("extract metrics") {
+                        metrics
+                            // capture list of Map<String,Long> per metric
+                            .map {
+                                it.getMetrics(
+                                    Metric.CaptureInfo(
+                                        targetPackageName = packageName,
+                                        testPackageName = macrobenchPackageName,
+                                        startupMode = startupModeMetricHint,
+                                        apiLevel = Build.VERSION.SDK_INT
+                                    ),
+                                    this
+                                )
+                            }
+                            // merge into one map
+                            .reduce { sum, element -> sum + element }
                     }
-                } finally {
-                    trace("stop metrics") {
-                        metrics.forEach {
-                            it.stop()
-                        }
-                    }
+
+                // append UI state to trace, so tools opening trace will highlight relevant part in UI
+                val uiState = UiState(
+                    timelineStart = iterationResult.timelineRangeNs?.first,
+                    timelineEnd = iterationResult.timelineRangeNs?.last,
+                    highlightPackage = packageName
+                )
+                File(tracePath).apply {
+                    // Disabled currently, see b/194424816 and b/174007010
+                    // appendBytes(UserspaceTracing.commitToTrace().encode())
+                    UserspaceTracing.commitToTrace() // clear buffer
+
+                    appendUiState(uiState)
                 }
-            }!!
+                Log.d(TAG, "Iteration $iteration captured $uiState")
 
-            tracePaths.add(tracePath)
-
-            val iterationResult = userspaceTrace("extract metrics") {
-                metrics
-                    // capture list of Map<String,Long> per metric
-                    .map { it.getMetrics(Metric.CaptureInfo(
-                        targetPackageName = packageName,
-                        testPackageName = macrobenchPackageName,
-                        startupMode = startupModeMetricHint,
-                        apiLevel = Build.VERSION.SDK_INT
-                    ), tracePath) }
-                    // merge into one map
-                    .reduce { sum, element -> sum + element }
-            }
-            // append UI state to trace, so tools opening trace will highlight relevant part in UI
-            val uiState = UiState(
-                timelineStart = iterationResult.timelineRangeNs?.first,
-                timelineEnd = iterationResult.timelineRangeNs?.last,
-                highlightPackage = packageName
-            )
-            File(tracePath).apply {
-                // Disabled currently, see b/194424816 and b/174007010
-                // appendBytes(UserspaceTracing.commitToTrace().encode())
-                UserspaceTracing.commitToTrace() // clear buffer
-
-                appendUiState(uiState)
-            }
-            Log.d(TAG, "Iteration $iteration captured $uiState")
-
-            // report just the metrics
-            iterationResult
-        }.mergeIterationMeasurements()
+                // report just the metrics
+                iterationResult
+            }.mergeIterationMeasurements()
+        }
 
         require(measurements.isNotEmpty()) {
             """
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
index a322e19..45a220b 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/MacrobenchmarkScope.kt
@@ -158,15 +158,10 @@
         var lastFrameStats: List<FrameStatsResult> = emptyList()
         repeat(100) {
             lastFrameStats = getFrameStats()
-            if (lastFrameStats
-                .filter { it.uniqueName !in ignoredUniqueNames }
-                .any {
-                    val lastFrameTimestampNs = if (Build.VERSION.SDK_INT >= 29) {
-                        it.lastLaunchNs
-                    } else {
-                        it.lastFrameNs
-                    } ?: Long.MIN_VALUE
-                    lastFrameTimestampNs > preLaunchTimestampNs
+            if (lastFrameStats.any {
+                    it.uniqueName !in ignoredUniqueNames &&
+                        it.lastFrameNs != null &&
+                        it.lastFrameNs > preLaunchTimestampNs
                 }) {
                 return // success, launch observed!
             }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 19d7601..53b41ee 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -22,12 +22,12 @@
 import androidx.annotation.RestrictTo
 import androidx.benchmark.Shell
 import androidx.benchmark.macro.BatteryCharge.hasMinimumCharge
+import androidx.benchmark.macro.PowerMetric.Type
 import androidx.benchmark.macro.PowerRail.hasMetrics
 import androidx.benchmark.macro.perfetto.AudioUnderrunQuery
 import androidx.benchmark.macro.perfetto.BatteryDischargeQuery
 import androidx.benchmark.macro.perfetto.FrameTimingQuery
 import androidx.benchmark.macro.perfetto.FrameTimingQuery.SubMetric
-import androidx.benchmark.macro.perfetto.PerfettoResultsParser.parseStartupResult
 import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
 import androidx.benchmark.macro.perfetto.PowerQuery
 import androidx.benchmark.macro.perfetto.Slice
@@ -45,13 +45,17 @@
     internal abstract fun start()
 
     internal abstract fun stop()
+
     /**
      * After stopping, collect metrics
      *
      * TODO: takes package for package level filtering, but probably want a
      *  general config object coming into [start].
      */
-    internal abstract fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult
+    internal abstract fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult
 
     internal data class CaptureInfo(
         val apiLevel: Int,
@@ -93,8 +97,11 @@
     internal override fun stop() {
     }
 
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
-        val subMetrics = AudioUnderrunQuery.getSubMetrics(tracePath)
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
+        val subMetrics = AudioUnderrunQuery.getSubMetrics(perfettoTraceProcessor)
 
         return IterationResult(
             singleMetrics = mapOf(
@@ -188,7 +195,10 @@
         "totalFrameCount"
     )
 
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String) = IterationResult(
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ) = IterationResult(
         singleMetrics = helper.metrics
             .map {
                 val prefix = "gfxinfo_${packageName}_"
@@ -227,9 +237,12 @@
     internal override fun stop() {}
 
     @SuppressLint("SyntheticAccessor")
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
         val subMetricsMsMap = FrameTimingQuery.getFrameSubMetrics(
-            absoluteTracePath = tracePath,
+            perfettoTraceProcessor = perfettoTraceProcessor,
             captureApiLevel = Build.VERSION.SDK_INT,
             packageName = captureInfo.targetPackageName
         )
@@ -277,12 +290,14 @@
     }
 
     @SuppressLint("SyntheticAccessor")
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
         return StartupTimingQuery.getFrameSubMetrics(
-            absoluteTracePath = tracePath,
+            perfettoTraceProcessor = perfettoTraceProcessor,
             captureApiLevel = captureInfo.apiLevel,
             targetPackageName = captureInfo.targetPackageName,
-            testPackageName = captureInfo.testPackageName,
 
             // Pick an arbitrary startup mode if unspecified. In the future, consider throwing an
             // error if startup mode not defined
@@ -317,9 +332,43 @@
     internal override fun stop() {
     }
 
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
-        val json = PerfettoTraceProcessor.getJsonMetrics(tracePath, "android_startup")
-        return parseStartupResult(json, captureInfo.targetPackageName)
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
+
+        // Acquires perfetto metrics
+        val traceMetrics = perfettoTraceProcessor.getTraceMetrics("android_startup")
+        val androidStartup = traceMetrics.android_startup
+            ?: throw IllegalStateException("No android_startup metric found.")
+        val appStartup =
+            androidStartup.startup.first { it.package_name == captureInfo.targetPackageName }
+
+        // Extract app startup
+        val metricMap = mutableMapOf<String, Double>()
+
+        val durMs = appStartup.to_first_frame?.dur_ms
+        if (durMs != null) {
+            metricMap["startupMs"] = durMs
+        }
+
+        val fullyDrawnMs = appStartup.report_fully_drawn?.dur_ms
+        if (fullyDrawnMs != null) {
+            metricMap["fullyDrawnMs"] = fullyDrawnMs
+        }
+
+        val timelineStart = appStartup.event_timestamps?.intent_received
+        val timelineEnd = appStartup.event_timestamps?.first_frame
+
+        return IterationResult(
+            singleMetrics = metricMap,
+            sampledMetrics = emptyMap(),
+            timelineRangeNs = if (timelineStart != null && timelineEnd != null) {
+                timelineStart..timelineEnd
+            } else {
+                null
+            }
+        )
     }
 }
 
@@ -347,8 +396,11 @@
     }
 
     @SuppressLint("SyntheticAccessor")
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
-        val slice = PerfettoTraceProcessor.querySlices(tracePath, sectionName).firstOrNull()
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
+        val slice = perfettoTraceProcessor.querySlices(sectionName).firstOrNull()
         return if (slice == null) {
             IterationResult.EMPTY
         } else IterationResult(
@@ -360,6 +412,7 @@
         )
     }
 }
+
 /**
  * Captures the change of power, energy or battery charge metrics over time for specified duration.
  * A configurable output of power, energy, subsystems, and battery charge will be generated.
@@ -450,9 +503,11 @@
         class Power(
             powerCategories: Map<PowerCategory, PowerCategoryDisplayLevel> = emptyMap()
         ) : Type(powerCategories)
+
         class Energy(
             energyCategories: Map<PowerCategory, PowerCategoryDisplayLevel> = emptyMap()
         ) : Type(energyCategories)
+
         class Battery : Type()
     }
 
@@ -476,21 +531,30 @@
         }
     }
 
-    internal override fun getMetrics(captureInfo: CaptureInfo, tracePath: String): IterationResult {
+    internal override fun getMetrics(
+        captureInfo: CaptureInfo,
+        perfettoTraceProcessor: PerfettoTraceProcessor
+    ): IterationResult {
         // collect metrics between trace point flags
-        val slice = PerfettoTraceProcessor.querySlices(tracePath, MEASURE_BLOCK_SECTION_NAME)
+        val slice = perfettoTraceProcessor.querySlices(MEASURE_BLOCK_SECTION_NAME)
             .firstOrNull()
             ?: return IterationResult.EMPTY
 
         if (type is Type.Battery) {
-            return getBatteryDischargeMetrics(tracePath, slice)
+            return getBatteryDischargeMetrics(perfettoTraceProcessor, slice)
         }
 
-        return getPowerMetrics(tracePath, slice)
+        return getPowerMetrics(perfettoTraceProcessor, slice)
     }
 
-    private fun getBatteryDischargeMetrics(tracePath: String, slice: Slice): IterationResult {
-        val metrics = BatteryDischargeQuery.getBatteryDischargeMetrics(tracePath, slice)
+    private fun getBatteryDischargeMetrics(
+        perfettoTraceProcessor: PerfettoTraceProcessor,
+        slice: Slice
+    ): IterationResult {
+        val metrics = BatteryDischargeQuery.getBatteryDischargeMetrics(
+            perfettoTraceProcessor,
+            slice
+        )
 
         val metricMap: Map<String, Double> = metrics.associate { measurement ->
             getLabel(measurement.name) to measurement.chargeMah
@@ -498,24 +562,30 @@
 
         return IterationResult(
             singleMetrics = metricMap,
-            sampledMetrics = emptyMap())
+            sampledMetrics = emptyMap()
+        )
     }
 
-    private fun getPowerMetrics(tracePath: String, slice: Slice): IterationResult {
-        val metrics = PowerQuery.getPowerMetrics(tracePath, slice)
+    private fun getPowerMetrics(
+        perfettoTraceProcessor: PerfettoTraceProcessor,
+        slice: Slice
+    ): IterationResult {
+        val metrics = PowerQuery.getPowerMetrics(perfettoTraceProcessor, slice)
 
         val metricMap: Map<String, Double> = getSpecifiedMetrics(metrics)
         if (metricMap.isEmpty()) {
             return IterationResult(
                 singleMetrics = emptyMap(),
-                sampledMetrics = emptyMap())
+                sampledMetrics = emptyMap()
+            )
         }
 
         val extraMetrics: Map<String, Double> = getTotalAndUnselectedMetrics(metrics)
 
         return IterationResult(
             singleMetrics = metricMap + extraMetrics,
-            sampledMetrics = emptyMap())
+            sampledMetrics = emptyMap()
+        )
     }
 
     private fun getLabel(metricName: String, displayType: String = ""): String {
@@ -532,14 +602,14 @@
         return mapOf(
             getLabel("Total") to
                 metrics.values.fold(0.0) { total, next ->
-                total + next.getValue(type)
-            },
+                    total + next.getValue(type)
+                },
             getLabel("Unselected") to
                 metrics.filter { (category, _) ->
-                !type.categories.containsKey(category)
-            }.values.fold(0.0) { total, next ->
-                total + next.getValue(type)
-            }
+                    !type.categories.containsKey(category)
+                }.values.fold(0.0) { total, next ->
+                    total + next.getValue(type)
+                }
         ).filter { (_, measurement) ->
             measurement != 0.0
         }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
index 12db8c3..f1ae5c4 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/AudioUnderrunQuery.kt
@@ -30,53 +30,37 @@
     )
 
     fun getSubMetrics(
-        absoluteTracePath: String
+        perfettoTraceProcessor: PerfettoTraceProcessor
     ): SubMetrics {
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = absoluteTracePath,
-            query = getFullQuery()
-        )
-
-        val resultLines = queryResult.split("\n")
-
-        if (resultLines.first() != "\"name\",\"value\",\"ts\"") {
-            throw IllegalStateException("query failed!")
-        }
-
-        // we can't measure duration when there is only one time stamp
-        if (resultLines.size <= 3) {
-            throw RuntimeException("No playing audio detected")
-        }
+        val queryResult = perfettoTraceProcessor.rawQuery(getFullQuery())
 
         var trackName: String? = null
         var lastTs: Long? = null
-
         var totalNs: Long = 0
         var zeroNs: Long = 0
 
-        resultLines
-            .drop(1) // column names
-            .dropLast(1) // empty line
-            .forEach {
-                val lineVals = it.split(",")
-                if (lineVals.size != VAL_MAX)
+        queryResult
+            .asSequence()
+            .forEach { lineVals ->
+
+                if (lineVals.size != EXPECTED_COLUMN_COUNT)
                     throw IllegalStateException("query failed")
 
                 if (trackName == null) {
-                    trackName = lineVals[VAL_NAME]
-                } else if (!trackName.equals(lineVals[VAL_NAME])) {
+                    trackName = lineVals[VAL_NAME] as String?
+                } else if (trackName!! != lineVals[VAL_NAME]) {
                     throw RuntimeException("There could be only one AudioTrack per measure")
                 }
 
                 if (lastTs == null) {
-                    lastTs = lineVals[VAL_TS].toLong()
+                    lastTs = lineVals[VAL_TS] as Long
                 } else {
-                    val frameNs = lineVals[VAL_TS].toLong() - lastTs!!
-                    lastTs = lineVals[VAL_TS].toLong()
+                    val frameNs = lineVals[VAL_TS] as Long - lastTs!!
+                    lastTs = lineVals[VAL_TS] as Long
 
                     totalNs += frameNs
 
-                    val frameCounter = lineVals[VAL_VALUE].toDouble().toInt()
+                    val frameCounter = (lineVals[VAL_VALUE] as Double).toInt()
 
                     if (frameCounter == 0)
                         zeroNs += frameNs
@@ -86,8 +70,8 @@
         return SubMetrics((totalNs / 1_000_000).toInt(), (zeroNs / 1_000_000).toInt())
     }
 
-    private const val VAL_NAME = 0
-    private const val VAL_VALUE = 1
-    private const val VAL_TS = 2
-    private const val VAL_MAX = 3
+    private const val VAL_NAME = "name"
+    private const val VAL_VALUE = "value"
+    private const val VAL_TS = "ts"
+    private const val EXPECTED_COLUMN_COUNT = 3
 }
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/BatteryDischargeQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/BatteryDischargeQuery.kt
index 1644236..d170ac2 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/BatteryDischargeQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/BatteryDischargeQuery.kt
@@ -33,43 +33,35 @@
         var chargeMah: Double
     )
 
-    public fun getBatteryDischargeMetrics(
-        absoluteTracePath: String,
+    fun getBatteryDischargeMetrics(
+        perfettoTraceProcessor: PerfettoTraceProcessor,
         slice: Slice
     ): List<BatteryDischargeMeasurement> {
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = absoluteTracePath,
+        val queryResult = perfettoTraceProcessor.rawQuery(
             query = getFullQuery(slice)
         )
 
-        val resultLines = queryResult.split("\n")
-
-        if (resultLines.first() != """"startMah","endMah","diffMah"""") {
-            throw IllegalStateException("query failed!\n${getFullQuery(slice)}")
-        }
-
-        // results are in CSV with a header row, with 1 row of results
-        val columns = resultLines
-            .filter { it.isNotBlank() } // drop blank lines
-            .drop(1) // drop the header row
-            .joinToString().split(",")
-
-        if (columns.isEmpty()) {
+        if (queryResult.isEmpty()) {
             return emptyList()
         }
 
+        if (queryResult.size() != 1) {
+            throw IllegalStateException("Unexpected query result size for battery discharge.")
+        }
+
+        val row = queryResult.next()
         return listOf(
             BatteryDischargeMeasurement(
                 name = "Start",
-                chargeMah = columns[0].toDouble(),
+                chargeMah = row["startMah"] as Double,
             ),
             BatteryDischargeMeasurement(
                 name = "End",
-                chargeMah = columns[1].toDouble()
+                chargeMah = row["endMah"] as Double
             ),
             BatteryDischargeMeasurement(
                 name = "Diff",
-                chargeMah = columns[2].toDouble()
+                chargeMah = row["diffMah"] as Double
             )
         )
     }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
index bf07e48..e885746 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/FrameTimingQuery.kt
@@ -16,8 +16,6 @@
 
 package androidx.benchmark.macro.perfetto
 
-import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor.processNameLikePkg
-
 internal object FrameTimingQuery {
     private fun getFullQuery(packageName: String) = """
         ------ Select all frame-relevant slices from slice table
@@ -137,15 +135,14 @@
     }
 
     fun getFrameSubMetrics(
-        absoluteTracePath: String,
+        perfettoTraceProcessor: PerfettoTraceProcessor,
         captureApiLevel: Int,
         packageName: String,
     ): Map<SubMetric, List<Long>> {
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = absoluteTracePath,
+        val queryResultIterator = perfettoTraceProcessor.rawQuery(
             query = getFullQuery(packageName)
         )
-        val slices = Slice.parseListFromQueryResult(queryResult).let { list ->
+        val slices = queryResultIterator.toSlices().let { list ->
             list.map { it.copy(ts = it.ts - list.first().ts) }
         }
 
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoResultsParser.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoResultsParser.kt
deleted file mode 100644
index c38d31f..0000000
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoResultsParser.kt
+++ /dev/null
@@ -1,63 +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.benchmark.macro.perfetto
-
-import androidx.benchmark.macro.IterationResult
-import org.json.JSONObject
-
-internal object PerfettoResultsParser {
-    fun parseStartupResult(jsonMetricResults: String, packageName: String): IterationResult {
-        val json = JSONObject(jsonMetricResults)
-        json.optJSONObject("android_startup")?.let { androidStartup ->
-            androidStartup.optJSONArray("startup")?.let { startup ->
-                for (i in 0 until startup.length()) {
-                    val startupResult = startup.getJSONObject(i)
-                    if (startupResult.optString("package_name") == packageName) {
-                        // NOTE: we return the startup for this process, and ignore any more
-                        return startupResult.parseStartupMetricsWithUiState()
-                    }
-                }
-            }
-        }
-
-        return IterationResult(emptyMap(), emptyMap(), null)
-    }
-
-    private fun JSONObject.parseStartupMetricsWithUiState(): IterationResult {
-        val durMs = getJSONObject("to_first_frame").getDouble("dur_ms")
-        val fullyDrawnMs = optJSONObject("report_fully_drawn")?.getDouble("dur_ms")
-
-        val metricMap = mutableMapOf("startupMs" to durMs)
-        if (fullyDrawnMs != null) {
-            metricMap["fullyDrawnMs"] = fullyDrawnMs
-        }
-
-        val eventTimestamps = optJSONObject("event_timestamps")
-        val timelineStart = eventTimestamps?.optLong("intent_received")
-        val timelineEnd = eventTimestamps?.optLong("first_frame")
-
-        return IterationResult(
-            singleMetrics = metricMap,
-            sampledMetrics = emptyMap(),
-            timelineRangeNs = if (timelineStart != null && timelineEnd != null) {
-                timelineStart..timelineEnd
-            } else {
-                null
-            }
-        )
-    }
-}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
index 3ec5957..230dfe4 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PerfettoTraceProcessor.kt
@@ -16,119 +16,181 @@
 
 package androidx.benchmark.macro.perfetto
 
-import android.util.Log
 import androidx.annotation.RestrictTo
-import androidx.benchmark.Outputs
-import androidx.benchmark.Shell
+import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP
+import androidx.benchmark.macro.perfetto.server.PerfettoHttpServer
+import androidx.benchmark.macro.perfetto.server.QueryResultIterator
 import androidx.benchmark.perfetto.PerfettoHelper
 import androidx.benchmark.userspaceTrace
-import org.jetbrains.annotations.TestOnly
 import java.io.File
-
-import androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX
+import org.jetbrains.annotations.TestOnly
+import perfetto.protos.TraceMetrics
 
 /**
  * Enables parsing perfetto traces on-device
  */
-@RestrictTo(LIBRARY_GROUP_PREFIX) // for internal benchmarking only
-object PerfettoTraceProcessor {
-    private const val TAG = "PerfettoTraceProcessor"
+@RestrictTo(LIBRARY_GROUP) // for internal benchmarking only
+class PerfettoTraceProcessor(httpServerPort: Int = DEFAULT_HTTP_SERVER_PORT) {
 
-    /**
-     * The actual [File] path to the `trace_processor_shell`.
-     *
-     * Lazily copies the `trace_processor_shell` and enables parsing of the perfetto trace files.
-     */
-    @get:TestOnly
-    val shellPath: String by lazy {
-        // Checks for ABI support
-        PerfettoHelper.createExecutable("trace_processor_shell")
+    companion object {
+        private const val TAG = "PerfettoTraceProcessor"
+        private const val DEFAULT_HTTP_SERVER_PORT = 9001
+
+        /**
+         * The actual [File] path to the `trace_processor_shell`.
+         *
+         * Lazily copies the `trace_processor_shell` and enables parsing of the perfetto trace files.
+         */
+        @get:TestOnly
+        val shellPath: String by lazy {
+            // Checks for ABI support
+            PerfettoHelper.createExecutable("trace_processor_shell")
+        }
+
+        /**
+         * Starts a perfetto trace processor shell server in http mode, loads a trace and executes
+         * the given block. It stops the server after the block is complete
+         */
+        fun <T> runServer(
+            absoluteTracePath: String? = null,
+            httpServerPort: Int = DEFAULT_HTTP_SERVER_PORT,
+            block: PerfettoTraceProcessor.() -> T
+        ): T = userspaceTrace("PerfettoTraceProcessor#runServer") {
+            var perfettoTraceProcessor: PerfettoTraceProcessor? = null
+            try {
+
+                // Initializes the server process
+                perfettoTraceProcessor = PerfettoTraceProcessor(httpServerPort).startServer()
+
+                // Loads a trace if required
+                if (absoluteTracePath != null) {
+                    perfettoTraceProcessor.loadTrace(absoluteTracePath)
+                }
+
+                // Executes the query block
+                return@userspaceTrace userspaceTrace("PerfettoTraceProcessor#runServer#block") {
+                    block(perfettoTraceProcessor)
+                }
+            } finally {
+                perfettoTraceProcessor?.stopServer()
+            }
+        }
     }
 
-    private fun validateTracePath(absoluteTracePath: String) {
+    private val perfettoHttpServer: PerfettoHttpServer = PerfettoHttpServer(httpServerPort)
+    private var traceLoaded = false
+
+    private fun startServer(): PerfettoTraceProcessor =
+        userspaceTrace("PerfettoTraceProcessor#startServer") {
+            perfettoHttpServer.startServer()
+            return@userspaceTrace this
+        }
+
+    private fun stopServer() = userspaceTrace("PerfettoTraceProcessor#stopServer") {
+        perfettoHttpServer.stopServer()
+    }
+
+    /**
+     * Loads a trace in the current instance of the trace processor, clearing any previous loaded
+     * trace if existing.
+     */
+    fun loadTrace(absoluteTracePath: String) = userspaceTrace("PerfettoTraceProcessor#loadTrace") {
         require(!absoluteTracePath.contains(" ")) {
             "Trace path must not contain spaces: $absoluteTracePath"
         }
+
+        val traceFile = File(absoluteTracePath)
+        require(traceFile.exists() && traceFile.isFile) {
+            "Trace path must exist and not be a directory: $absoluteTracePath"
+        }
+
+        // In case a previous trace was loaded, ensures to clear
+        if (traceLoaded) {
+            clearTrace()
+        }
+        traceLoaded = false
+
+        val parseResult = perfettoHttpServer.parse(traceFile.readBytes())
+        if (parseResult.error != null) {
+            throw IllegalStateException(parseResult.error)
+        }
+
+        // Notifies the server that it won't receive any more trace parts
+        perfettoHttpServer.notifyEof()
+
+        traceLoaded = true
     }
 
     /**
-     * Returns a json string containing the requested metric computed by trace_shell_processor on
-     * the given perfetto trace.
-     *
-     * @throws IllegalStateException if the returned json is empty as result of an invalid trace.
+     * Clears the current loaded trace.
      */
-    fun getJsonMetrics(absoluteTracePath: String, metric: String): String {
-        validateTracePath(absoluteTracePath)
-        require(!metric.contains(" ")) {
-            "Metric must not contain spaces: $metric"
-        }
-
-        val command = "$shellPath --run-metric $metric $absoluteTracePath --metrics-output=json"
-        Log.d(TAG, "Executing command $command")
-
-        val json = userspaceTrace("trace_processor_shell") {
-            Shell.executeCommand(command)
-                .trim() // trim to enable empty check below
-        }
-        Log.d(TAG, "Trace Processor result: \n\n $json")
-        if (json.isEmpty()) {
-            throw IllegalStateException(
-                "Empty json result from Trace Processor - " +
-                    "possibly malformed command? Command: $command"
-            )
-        }
-        return json
+    private fun clearTrace() = userspaceTrace("PerfettoTraceProcessor#clearTrace") {
+        perfettoHttpServer.restoreInitialTables()
     }
 
     /**
+     * Computes the given metric on the previously loaded trace.
+     */
+    fun getTraceMetrics(metric: String): TraceMetrics =
+        userspaceTrace("PerfettoTraceProcessor#getTraceMetrics $metric") {
+            require(!metric.contains(" ")) {
+                "Metric must not contain spaces: $metric"
+            }
+            require(perfettoHttpServer.isRunning()) {
+                "Perfetto trace_shell_process is not running."
+            }
+
+            // Compute metrics
+            val computeResult = perfettoHttpServer.computeMetric(listOf(metric))
+            if (computeResult.error != null) {
+                throw IllegalStateException(computeResult.error)
+            }
+
+            // Decode and return trace metrics
+            return@userspaceTrace TraceMetrics.ADAPTER.decode(computeResult.metrics!!)
+        }
+
+    /**
+     * Computes the given query on the previously loaded trace.
+     */
+    fun rawQuery(query: String): QueryResultIterator =
+        userspaceTrace("PerfettoTraceProcessor#rawQuery $query".take(127)) {
+            require(perfettoHttpServer.isRunning()) {
+                "Perfetto trace_shell_process is not running."
+            }
+            return@userspaceTrace perfettoHttpServer.executeQuery(query)
+        }
+
+    /**
      * Query a trace for a list of slices - name, timestamp, and duration.
      *
      * Note that sliceNames may include wildcard matches, such as `foo%`
      */
     internal fun querySlices(
-        absoluteTracePath: String,
         vararg sliceNames: String
     ): List<Slice> {
+        require(perfettoHttpServer.isRunning()) { "Perfetto trace_shell_process is not running." }
+
         val whereClause = sliceNames
             .joinToString(separator = " OR ") {
                 "slice.name LIKE \"$it\""
             }
 
-        return Slice.parseListFromQueryResult(
-            queryResult = rawQuery(
-                absoluteTracePath = absoluteTracePath,
-                query = """
+        val queryResultIterator = rawQuery(
+            query = """
                 SELECT slice.name,ts,dur
                 FROM slice
                 WHERE $whereClause
             """.trimMargin()
-            )
         )
+
+        return queryResultIterator.toSlices()
     }
+}
 
-    fun rawQuery(
-        absoluteTracePath: String,
-        query: String
-    ): String {
-        validateTracePath(absoluteTracePath)
-
-        val queryFile = File(Outputs.dirUsableByAppAndShell, "trace_processor_query.sql")
-        try {
-            queryFile.writeText(query)
-
-            val command = "$shellPath --query-file ${queryFile.absolutePath} $absoluteTracePath"
-            return userspaceTrace("trace_processor_shell") {
-                Shell.executeCommand(command)
-            }
-        } finally {
-            queryFile.delete()
-        }
-    }
-
-    /**
-     * Helper for fuzzy matching process name to package
-     */
-    internal fun processNameLikePkg(pkg: String): String {
-        return """(process.name LIKE "$pkg" OR process.name LIKE "$pkg:%")"""
-    }
+/**
+ * Helper for fuzzy matching process name to package
+ */
+internal fun processNameLikePkg(pkg: String): String {
+    return """(process.name LIKE "$pkg" OR process.name LIKE "$pkg:%")"""
 }
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt
index 9a19172..2b9bc20 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/PowerQuery.kt
@@ -91,12 +91,12 @@
         }
     }
 
-    public fun getPowerMetrics(
-        absoluteTracePath: String,
+    fun getPowerMetrics(
+        perfettoTraceProcessor: PerfettoTraceProcessor,
         slice: Slice
     ): Map<PowerCategory, CategoryMeasurement> {
         // gather all recorded rails
-        val railMetrics: List<ComponentMeasurement> = getRailMetrics(absoluteTracePath, slice)
+        val railMetrics: List<ComponentMeasurement> = getRailMetrics(perfettoTraceProcessor, slice)
         railMetrics.ifEmpty { return emptyMap() }
 
         // sort ComponentMeasurements into CategoryMeasurements
@@ -133,32 +133,20 @@
     }
 
     private fun getRailMetrics(
-        absoluteTracePath: String,
+        perfettoTraceProcessor: PerfettoTraceProcessor,
         slice: Slice
     ): List<ComponentMeasurement> {
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = absoluteTracePath,
-            query = getFullQuery(slice)
-        )
 
-        val resultLines = queryResult.split("\n")
+        val query = getFullQuery(slice)
+        val queryResult = perfettoTraceProcessor.rawQuery(query)
 
-        if (resultLines.first() != """"name","energyUws","powerUs"""") {
-            throw IllegalStateException("query failed!\n${getFullQuery(slice)}")
+        return queryResult.toList {
+            ComponentMeasurement(
+                name = (it["name"] as String).camelCase(),
+                energyUws = it["energyUws"] as Double,
+                powerUw = it["powerUs"] as Double,
+            )
         }
-
-        // results are in CSV with a header row, and strings wrapped with quotes
-        return resultLines
-            .filter { it.isNotBlank() } // drop blank lines
-            .drop(1) // drop the header row
-            .map {
-                val columns = it.split(",")
-                ComponentMeasurement(
-                    name = columns[0].unquote().camelCase(),
-                    energyUws = columns[1].toDouble(),
-                    powerUw = columns[2].toDouble(),
-                )
-            }
     }
 
     /**
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/Slice.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/Slice.kt
index 57cc39d..4cc0db3 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/Slice.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/Slice.kt
@@ -16,6 +16,8 @@
 
 package androidx.benchmark.macro.perfetto
 
+import androidx.benchmark.macro.perfetto.server.QueryResultIterator
+
 internal data class Slice(
     val name: String,
     val ts: Long,
@@ -27,34 +29,13 @@
     fun contains(targetTs: Long): Boolean {
         return targetTs >= ts && targetTs <= (ts + dur)
     }
-
-    companion object {
-
-        fun parseListFromQueryResult(queryResult: String): List<Slice> {
-            val resultLines = queryResult.split("\n").onEach {
-                println("query result line $it")
-            }
-
-            if (resultLines.first() != """"name","ts","dur"""") {
-                throw IllegalStateException("query failed!")
-            }
-
-            // results are in CSV with a header row, and strings wrapped with quotes
-            return resultLines
-                .filter { it.isNotBlank() } // drop blank lines
-                .drop(1) // drop the header row
-                .map {
-                    val columns = it.split(",")
-                    // Trace section names may have a ","
-                    // Parse the duration, and timestamps first. Whatever is remaining must be the
-                    // name.
-                    val size = columns.size
-                    Slice(
-                        name = columns.dropLast(2).joinToString(",").unquote(),
-                        ts = columns[size - 2].toLong(),
-                        dur = columns[size - 1].toLong()
-                    )
-                }
-        }
-    }
 }
+
+/**
+ * Convenient function to immediately retrieve a list of slices.
+ * Note that this method is provided for convenience and exhausts the iterator.
+ */
+internal fun QueryResultIterator.toSlices(): List<Slice> =
+    toList {
+        Slice(name = it["name"] as String, ts = it["ts"] as Long, dur = it["dur"] as Long)
+    }
\ No newline at end of file
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
index a31965c..6cb5a71 100644
--- a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/StartupTimingQuery.kt
@@ -18,11 +18,10 @@
 
 import android.util.Log
 import androidx.benchmark.macro.StartupMode
-import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor.processNameLikePkg
 
 internal object StartupTimingQuery {
 
-    private fun getFullQuery(testPackageName: String, targetPackageName: String) = """
+    private fun getFullQuery(targetPackageName: String) = """
         ------ Select all startup-relevant slices from slice table
         SELECT
             slice.name as name,
@@ -33,14 +32,11 @@
             INNER JOIN thread USING(utid)
             INNER JOIN process USING(upid)
         WHERE (
-            (${processNameLikePkg(testPackageName)} AND slice.name LIKE "startActivityAndWait") OR
-            (
                 ${processNameLikePkg(targetPackageName)} AND (
                     (slice.name LIKE "activityResume" AND process.pid LIKE thread.tid) OR
                     (slice.name LIKE "Choreographer#doFrame%" AND process.pid LIKE thread.tid) OR
                     (slice.name LIKE "reportFullyDrawn() for %" AND process.pid LIKE thread.tid) OR
                     (slice.name LIKE "DrawFrame%" AND thread.name LIKE "RenderThread")
-                )
             ) OR
             (
                 -- Signals beginning of launch event, only present in API 29+
@@ -66,7 +62,6 @@
     """.trimIndent()
 
     enum class StartupSliceType {
-        StartActivityAndWait,
         NotifyStarted,
         Launching,
         ReportFullyDrawn,
@@ -107,20 +102,17 @@
     }
 
     fun getFrameSubMetrics(
-        absoluteTracePath: String,
+        perfettoTraceProcessor: PerfettoTraceProcessor,
         captureApiLevel: Int,
         targetPackageName: String,
-        testPackageName: String,
         startupMode: StartupMode
     ): SubMetrics? {
-        val queryResult = PerfettoTraceProcessor.rawQuery(
-            absoluteTracePath = absoluteTracePath,
+        val queryResultIterator = perfettoTraceProcessor.rawQuery(
             query = getFullQuery(
-                testPackageName = testPackageName,
                 targetPackageName = targetPackageName
             )
         )
-        val slices = Slice.parseListFromQueryResult(queryResult)
+        val slices = queryResultIterator.toSlices()
 
         val groupedData = slices
             .filter { it.dur > 0 } // drop non-terminated slices
@@ -135,14 +127,10 @@
                     it.name == "MetricsLogger:launchObserverNotifyIntentStarted" ->
                         StartupSliceType.NotifyStarted
                     it.name == "activityResume" -> StartupSliceType.ActivityResume
-                    it.name == "startActivityAndWait" -> StartupSliceType.StartActivityAndWait
                     else -> throw IllegalStateException("Unexpected slice $it")
                 }
             }
 
-        val startActivityAndWaitSlice = groupedData[StartupSliceType.StartActivityAndWait]?.first()
-            ?: return null
-
         val uiSlices = groupedData.getOrElse(StartupSliceType.FrameUiThread) { listOf() }
         val rtSlices = groupedData.getOrElse(StartupSliceType.FrameRenderThread) { listOf() }
 
@@ -154,11 +142,10 @@
         val startTs: Long
         val initialDisplayTs: Long
         if (captureApiLevel >= 29 || startupMode != StartupMode.HOT) {
+            // find first matching "launching" slice
             val launchingSlice = groupedData[StartupSliceType.Launching]?.firstOrNull {
-                // find first "launching" slice that starts within startActivityAndWait
                 // verify full name only on API 23+, since before package name not specified
-                startActivityAndWaitSlice.contains(it.ts) &&
-                    (captureApiLevel < 23 || it.name == "launching: $targetPackageName")
+                (captureApiLevel < 23 || it.name == "launching: $targetPackageName")
             } ?: return null
 
             startTs = if (captureApiLevel >= 29) {
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoApi.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoApi.kt
new file mode 100644
index 0000000..e250517
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoApi.kt
@@ -0,0 +1,80 @@
+/*
+ * 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.benchmark.macro.perfetto.server
+
+import java.util.concurrent.TimeUnit
+import okhttp3.OkHttpClient
+import okhttp3.RequestBody
+import perfetto.protos.AppendTraceDataResult
+import perfetto.protos.ComputeMetricArgs
+import perfetto.protos.ComputeMetricResult
+import perfetto.protos.DisableAndReadMetatraceResult
+import perfetto.protos.QueryArgs
+import perfetto.protos.QueryResult
+import perfetto.protos.StatusResult
+import retrofit2.Call
+import retrofit2.Retrofit
+import retrofit2.converter.wire.WireConverterFactory
+import retrofit2.http.Body
+import retrofit2.http.GET
+import retrofit2.http.POST
+
+/**
+ * Perfetto trace_shell_processor api exposed in the http server implementation
+ */
+internal interface PerfettoApi {
+
+    @POST("/query")
+    fun query(@Body queryArgs: QueryArgs): Call<QueryResult>
+
+    @POST("/compute_metric")
+    fun computeMetric(@Body computeMetricArgs: ComputeMetricArgs): Call<ComputeMetricResult>
+
+    @POST("/parse")
+    fun parse(@Body bytes: RequestBody): Call<AppendTraceDataResult>
+
+    @GET("/notify_eof")
+    fun notifyEof(): Call<Unit>
+
+    @GET("/status")
+    fun status(): Call<StatusResult>
+
+    @GET("/enable_metatrace")
+    fun enableMetatrace(): Call<String>
+
+    @GET("/disable_and_read_metatrace")
+    fun disableAndReadMetatrace(): Call<DisableAndReadMetatraceResult>
+
+    @GET("/restore_initial_tables")
+    fun restoreInitialTables(): Call<Unit>
+
+    companion object {
+
+        private const val READ_TIMEOUT_SECONDS = 300L
+
+        fun create(address: String): PerfettoApi {
+            return Retrofit.Builder()
+                .baseUrl(address)
+                .addConverterFactory(WireConverterFactory.create())
+                .client(OkHttpClient.Builder()
+                    .readTimeout(READ_TIMEOUT_SECONDS, TimeUnit.SECONDS)
+                    .build())
+                .build()
+                .create(PerfettoApi::class.java)
+        }
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt
new file mode 100644
index 0000000..3a6ae70
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/PerfettoHttpServer.kt
@@ -0,0 +1,182 @@
+/*
+ * 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.benchmark.macro.perfetto.server
+
+import android.util.Log
+import androidx.benchmark.Shell
+import androidx.benchmark.ShellScript
+import androidx.benchmark.macro.perfetto.PerfettoTraceProcessor
+import androidx.benchmark.userspaceTrace
+import okhttp3.MediaType
+import okhttp3.RequestBody
+import perfetto.protos.AppendTraceDataResult
+import perfetto.protos.ComputeMetricArgs
+import perfetto.protos.ComputeMetricResult
+import perfetto.protos.QueryArgs
+import perfetto.protos.StatusResult
+import retrofit2.Call
+
+/**
+ * Wrapper around perfetto trace_shell_processor that communicates via http. The implementation
+ * is based on the python one of the official repo:
+ * https://github.com/google/perfetto/blob/master/python/perfetto/trace_processor/http.py
+ */
+internal class PerfettoHttpServer(private val port: Int) {
+
+    companion object {
+        private const val TAG = "PerfettoHttpServer"
+        private const val SERVER_START_TIMEOUT_MS = 5000
+        private var shellScript: ShellScript? = null
+
+        /**
+         * Returns a cached instance of the shell script to run the perfetto trace shell processor
+         * as http server. Note that the generated script doesn't specify the port and this must
+         * be passed as parameter when running the script.
+         */
+        fun getOrCreateShellScript(): ShellScript = shellScript ?: synchronized(this) {
+            var instance = shellScript
+            if (instance != null) {
+                return@synchronized instance
+            }
+            val script =
+                """echo pid:$$ ; exec ${PerfettoTraceProcessor.shellPath} -D --http-port "$@" """
+            instance = Shell.createShellScript(script)
+            shellScript = instance
+            instance
+        }
+
+        /**
+         * Clean up the shell script
+         */
+        fun cleanUpShellScript() = synchronized(this) {
+            shellScript?.cleanUp()
+            shellScript = null
+        }
+    }
+
+    private val perfettoApi by lazy { PerfettoApi.create("http://localhost:$port/") }
+    private var processId: Int? = null
+
+    /**
+     * Blocking method that runs the perfetto trace_shell_processor in server mode.
+     *
+     * @throws IllegalStateException if the server is not running by the end of the timeout.
+     */
+    fun startServer() = userspaceTrace("PerfettoHttpServer#startServer port $port") {
+        if (processId != null) {
+            Log.w(TAG, "Tried to start a trace shell processor that is already running.")
+            return@userspaceTrace
+        }
+
+        val shellScript = getOrCreateShellScript().start(port.toString())
+
+        processId = shellScript
+            .stdOutLineSequence()
+            .first { it.startsWith("pid:") }
+            .split("pid:")[1]
+            .toInt()
+
+        // Wait for the trace_processor_shell server to start.
+        var elapsed = 0
+        while (!isRunning()) {
+            Thread.sleep(5)
+            elapsed += 5
+            if (elapsed >= SERVER_START_TIMEOUT_MS) {
+                throw IllegalStateException(
+                    """
+                        Perfetto trace_processor_shell did not start correctly.
+                        Process stderr:
+                        ${shellScript.getOutputAndClose().stderr}
+                    """.trimIndent()
+                )
+            }
+        }
+        Log.i(TAG, "Perfetto trace processor shell server started (pid=$processId).")
+    }
+
+    /**
+     * Stops the server killing the associated process
+     */
+    fun stopServer() = userspaceTrace("PerfettoHttpServer#stopServer port $port") {
+        if (processId == null) {
+            Log.w(TAG, "Tried to stop trace shell processor http server without starting it.")
+            return@userspaceTrace
+        }
+        Shell.executeCommand("kill -TERM $processId")
+        Log.i(TAG, "Perfetto trace processor shell server stopped (pid=$processId).")
+    }
+
+    /**
+     * Returns true whether the server is running, false otherwise.
+     */
+    fun isRunning(): Boolean = userspaceTrace("PerfettoHttpServer#isRunning port $port") {
+        return@userspaceTrace try {
+            status()
+            true
+        } catch (e: Exception) {
+            false
+        }
+    }
+
+    /**
+     * Executes the given [sqlQuery] on a previously parsed trace and returns the result as a
+     * query result iterator.
+     */
+    fun executeQuery(sqlQuery: String): QueryResultIterator =
+        QueryResultIterator(perfettoApi.query(QueryArgs(sqlQuery)).executeAndGetBody())
+
+    /**
+     * Computes the given metrics on a previously parsed trace.
+     */
+    fun computeMetric(metrics: List<String>): ComputeMetricResult =
+        perfettoApi.computeMetric(ComputeMetricArgs(metrics)).executeAndGetBody()
+
+    /**
+     * Parses the trace file in chunks. Note that [notifyEof] should be called at the end to let
+     * the processor know that no more chunks will be sent.
+     */
+    fun parse(chunk: ByteArray): AppendTraceDataResult {
+        val bytes = RequestBody.create(MediaType.parse("application/octet-stream"), chunk)
+        return perfettoApi.parse(bytes).executeAndGetBody()
+    }
+
+    /**
+     * Notifies that the entire trace has been uploaded and no more chunks will be sent.
+     */
+    fun notifyEof(): Unit =
+        perfettoApi.notifyEof().executeAndGetBody()
+
+    /**
+     * Checks the status of the trace_shell_processor http server.
+     */
+    fun status(): StatusResult =
+        perfettoApi.status().executeAndGetBody()
+
+    /**
+     * Clears the loaded trace and restore the state of the initial tables
+     */
+    fun restoreInitialTables(): Unit =
+        perfettoApi.restoreInitialTables().executeAndGetBody()
+
+    private fun <T> Call<T>.executeAndGetBody(): T {
+        val response = execute()
+        if (!response.isSuccessful) {
+            throw IllegalStateException(response.message())
+        }
+        return response.body()!!
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/QueryResultIterator.kt b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/QueryResultIterator.kt
new file mode 100644
index 0000000..164b524
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/java/androidx/benchmark/macro/perfetto/server/QueryResultIterator.kt
@@ -0,0 +1,139 @@
+/*
+ * 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.benchmark.macro.perfetto.server
+
+import androidx.annotation.RestrictTo
+import okio.ByteString
+import perfetto.protos.QueryResult
+
+/**
+ * Wrapper class around [QueryResult] returned after executing a query on perfetto. The parsing
+ * logic is copied from the python project:
+ * https://github.com/google/perfetto/blob/master/python/perfetto/trace_processor/api.py#L89
+ */
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP) // for internal benchmarking only
+class QueryResultIterator internal constructor(queryResult: QueryResult) :
+    Iterator<Map<String, Any?>> {
+
+    private val dataLists = object {
+        val stringBatches = mutableListOf<String>()
+        val varIntBatches = mutableListOf<Long>()
+        val float64Batches = mutableListOf<Double>()
+        val blobBatches = mutableListOf<ByteString>()
+
+        var stringIndex = 0
+        var varIntIndex = 0
+        var float64Index = 0
+        var blobIndex = 0
+    }
+
+    private val cells = mutableListOf<QueryResult.CellsBatch.CellType>()
+    private val columnNames = queryResult.column_names
+    private val columnCount: Int
+    private val count: Int
+
+    private var currentIndex = 0
+
+    init {
+
+        // Parsing every batch
+        for (batch in queryResult.batch) {
+            val stringsBatch = batch.string_cells!!.split(0x00.toChar()).dropLast(1)
+            dataLists.stringBatches.addAll(stringsBatch)
+            dataLists.varIntBatches.addAll(batch.varint_cells)
+            dataLists.float64Batches.addAll(batch.float64_cells)
+            dataLists.blobBatches.addAll(batch.blob_cells)
+            cells.addAll(batch.cells)
+        }
+
+        columnCount = columnNames.size
+        count = if (columnCount > 0) cells.size / columnCount else 0
+    }
+
+    /**
+     * Returns the number of rows in the query result.
+     */
+    fun size(): Int {
+        return count
+    }
+
+    /**
+     * Returns true whether there are no results stored in this iterator, false otherwise.
+     */
+    fun isEmpty(): Boolean {
+        return count == 0
+    }
+
+    override fun hasNext(): Boolean {
+        return currentIndex < count
+    }
+
+    /**
+     * Returns a map containing the next row of the query results.
+     *
+     * @throws IllegalArgumentException if the query returns an invalid cell type
+     * @throws NoSuchElementException if the query has no next row.
+     */
+    override fun next(): Map<String, Any?> {
+        if (!hasNext()) throw NoSuchElementException()
+
+        val row = mutableMapOf<String, Any?>()
+        val baseCellIndex = currentIndex * columnCount
+
+        for ((num, columnName) in columnNames.withIndex()) {
+            val colType = cells[baseCellIndex + num]
+            val colIndex: Int
+            row[columnName] = when (colType) {
+                QueryResult.CellsBatch.CellType.CELL_STRING -> {
+                    colIndex = dataLists.stringIndex
+                    dataLists.stringIndex += 1
+                    dataLists.stringBatches[colIndex]
+                }
+                QueryResult.CellsBatch.CellType.CELL_VARINT -> {
+                    colIndex = dataLists.varIntIndex
+                    dataLists.varIntIndex += 1
+                    dataLists.varIntBatches[colIndex]
+                }
+                QueryResult.CellsBatch.CellType.CELL_FLOAT64 -> {
+                    colIndex = dataLists.float64Index
+                    dataLists.float64Index += 1
+                    dataLists.float64Batches[colIndex]
+                }
+                QueryResult.CellsBatch.CellType.CELL_BLOB -> {
+                    colIndex = dataLists.blobIndex
+                    dataLists.blobIndex += 1
+                    dataLists.blobBatches[colIndex]
+                }
+                QueryResult.CellsBatch.CellType.CELL_INVALID ->
+                    throw IllegalArgumentException("Invalid cell type")
+                QueryResult.CellsBatch.CellType.CELL_NULL ->
+                    null
+            }
+        }
+
+        currentIndex += 1
+        return row
+    }
+
+    /**
+     * Converts this iterator to a list of [T] using the given mapping function.
+     * Note that this method is provided for convenience and exhausts the iterator.
+     */
+    fun <T> toList(mapFunc: (Map<String, Any?>) -> (T)): List<T> {
+        return this.asSequence().map(mapFunc).toList()
+    }
+}
diff --git a/benchmark/benchmark-macro/src/main/proto/descriptor.proto b/benchmark/benchmark-macro/src/main/proto/descriptor.proto
new file mode 100644
index 0000000..7ee05e3
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/proto/descriptor.proto
@@ -0,0 +1,198 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+// This is a subset of descriptor.proto from the Protobuf library.
+syntax = "proto2";
+
+package perfetto.protos;
+
+// The protocol compiler can output a FileDescriptorSet containing the .proto
+// files it parses.
+message FileDescriptorSet {
+  repeated FileDescriptorProto file = 1;
+}
+
+// Describes a complete .proto file.
+message FileDescriptorProto {
+  // file name, relative to root of source tree
+  optional string name = 1;
+  // e.g. "foo", "foo.bar", etc.
+  optional string package = 2;
+
+  // Names of files imported by this file.
+  repeated string dependency = 3;
+  // Indexes of the public imported files in the dependency list above.
+  repeated int32 public_dependency = 10;
+  // Indexes of the weak imported files in the dependency list.
+  // For Google-internal migration only. Do not use.
+  repeated int32 weak_dependency = 11;
+
+  // All top-level definitions in this file.
+  repeated DescriptorProto message_type = 4;
+  repeated EnumDescriptorProto enum_type = 5;
+  repeated FieldDescriptorProto extension = 7;
+
+  reserved 6;
+  reserved 8;
+  reserved 9;
+  reserved 12;
+}
+
+// Describes a message type.
+message DescriptorProto {
+  optional string name = 1;
+
+  repeated FieldDescriptorProto field = 2;
+  repeated FieldDescriptorProto extension = 6;
+
+  repeated DescriptorProto nested_type = 3;
+  repeated EnumDescriptorProto enum_type = 4;
+
+  reserved 5;
+
+  repeated OneofDescriptorProto oneof_decl = 8;
+
+  reserved 7;
+
+  // Range of reserved tag numbers. Reserved tag numbers may not be used by
+  // fields or extension ranges in the same message. Reserved ranges may
+  // not overlap.
+  message ReservedRange {
+    // Inclusive.
+    optional int32 start = 1;
+    // Exclusive.
+    optional int32 end = 2;
+  }
+  repeated ReservedRange reserved_range = 9;
+  // Reserved field names, which may not be used by fields in the same message.
+  // A given name may only be reserved once.
+  repeated string reserved_name = 10;
+}
+
+// Describes a field within a message.
+message FieldDescriptorProto {
+  enum Type {
+    // 0 is reserved for errors.
+    // Order is weird for historical reasons.
+    TYPE_DOUBLE = 1;
+    TYPE_FLOAT = 2;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT64 if
+    // negative values are likely.
+    TYPE_INT64 = 3;
+    TYPE_UINT64 = 4;
+    // Not ZigZag encoded.  Negative numbers take 10 bytes.  Use TYPE_SINT32 if
+    // negative values are likely.
+    TYPE_INT32 = 5;
+    TYPE_FIXED64 = 6;
+    TYPE_FIXED32 = 7;
+    TYPE_BOOL = 8;
+    TYPE_STRING = 9;
+    // Tag-delimited aggregate.
+    // Group type is deprecated and not supported in proto3. However, Proto3
+    // implementations should still be able to parse the group wire format and
+    // treat group fields as unknown fields.
+    TYPE_GROUP = 10;
+    // Length-delimited aggregate.
+    TYPE_MESSAGE = 11;
+
+    // New in version 2.
+    TYPE_BYTES = 12;
+    TYPE_UINT32 = 13;
+    TYPE_ENUM = 14;
+    TYPE_SFIXED32 = 15;
+    TYPE_SFIXED64 = 16;
+    // Uses ZigZag encoding.
+    TYPE_SINT32 = 17;
+    // Uses ZigZag encoding.
+    TYPE_SINT64 = 18;
+  };
+
+  enum Label {
+    // 0 is reserved for errors
+    LABEL_OPTIONAL = 1;
+    LABEL_REQUIRED = 2;
+    LABEL_REPEATED = 3;
+  };
+
+  optional string name = 1;
+  optional int32 number = 3;
+  optional Label label = 4;
+
+  // If type_name is set, this need not be set.  If both this and type_name
+  // are set, this must be one of TYPE_ENUM, TYPE_MESSAGE or TYPE_GROUP.
+  optional Type type = 5;
+
+  // For message and enum types, this is the name of the type.  If the name
+  // starts with a '.', it is fully-qualified.  Otherwise, C++-like scoping
+  // rules are used to find the type (i.e. first the nested types within this
+  // message are searched, then within the parent, on up to the root
+  // namespace).
+  optional string type_name = 6;
+
+  // For extensions, this is the name of the type being extended.  It is
+  // resolved in the same manner as type_name.
+  optional string extendee = 2;
+
+  // For numeric types, contains the original text representation of the value.
+  // For booleans, "true" or "false".
+  // For strings, contains the default text contents (not escaped in any way).
+  // For bytes, contains the C escaped value.  All bytes >= 128 are escaped.
+  // TODO(kenton):  Base-64 encode?
+  optional string default_value = 7;
+
+  // If set, gives the index of a oneof in the containing type's oneof_decl
+  // list.  This field is a member of that oneof.
+  optional int32 oneof_index = 9;
+
+  reserved 10;
+
+  reserved 8;
+}
+
+// Describes a oneof.
+message OneofDescriptorProto {
+  optional string name = 1;
+  optional OneofOptions options = 2;
+}
+
+// Describes an enum type.
+message EnumDescriptorProto {
+  optional string name = 1;
+
+  repeated EnumValueDescriptorProto value = 2;
+
+  reserved 3;
+  reserved 4;
+
+  // Reserved enum value names, which may not be reused. A given name may only
+  // be reserved once.
+  repeated string reserved_name = 5;
+}
+
+// Describes a value within an enum.
+message EnumValueDescriptorProto {
+  optional string name = 1;
+  optional int32 number = 2;
+
+  reserved 3;
+}
+
+message OneofOptions {
+  reserved 999;
+
+  // Clients can define custom options in extensions of this message. See above.
+  extensions 1000 to max;
+}
diff --git a/benchmark/benchmark-macro/src/main/proto/perfetto_merged_metrics.proto b/benchmark/benchmark-macro/src/main/proto/perfetto_merged_metrics.proto
new file mode 100644
index 0000000..d889dc7
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/proto/perfetto_merged_metrics.proto
@@ -0,0 +1,1896 @@
+// AUTOGENERATED - DO NOT EDIT
+// ---------------------------
+// This file has been generated by
+// AOSP://external/perfetto/tools/gen_merged_protos
+// merging the perfetto config protos.
+// This fused proto is intended to be copied in:
+//  - Android tree, for statsd.
+//  - Google internal repos.
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+option go_package = "github.com/google/perfetto/perfetto_proto";
+
+// Begin of protos/perfetto/metrics/android/process_metadata.proto
+
+message AndroidProcessMetadata {
+  // Process name. Usually, cmdline or <package_name>(:<custom_name>)?.
+  optional string name = 1;
+
+  // User id under which this process runs.
+  optional int64 uid = 2;
+
+  // Package metadata from Android package list.
+  message Package {
+    optional string package_name = 1;
+    optional int64 apk_version_code = 2;
+    optional bool debuggable = 3;
+  }
+
+  // Package that this process belongs to.
+  //
+  // If this process shares its uid (see `packages_for_uid` field), the package
+  // is determined based on the process name and package name. If there is no
+  // match this field is empty.
+  optional Package package = 7;
+
+  // All packages using this uid.
+  //
+  // Shared uid documentation:
+  // https://developer.android.com/guide/topics/manifest/manifest-element#uid
+  repeated Package packages_for_uid = 8;
+
+  reserved 3, 4, 5, 6;
+}
+
+// End of protos/perfetto/metrics/android/process_metadata.proto
+
+// Begin of protos/perfetto/metrics/android/android_frame_timeline_metric.proto
+
+message AndroidFrameTimelineMetric {
+  message ProcessBreakdown {
+    optional AndroidProcessMetadata process = 3;
+
+    optional int64 total_frames = 4;
+    optional int64 missed_frames = 5;
+    optional int64 missed_app_frames = 6;
+    optional int64 missed_sf_frames = 7;
+
+    optional int64 frame_dur_max = 8;
+    optional int64 frame_dur_avg = 9;
+    optional int64 frame_dur_p50 = 10;
+    optional int64 frame_dur_p90 = 11;
+    optional int64 frame_dur_p95 = 12;
+    optional int64 frame_dur_p99 = 13;
+
+    reserved 1, 2;
+  }
+
+  optional int64 total_frames = 4;
+  optional int64 missed_app_frames = 5;
+
+  repeated ProcessBreakdown process = 2;
+
+  reserved 1;
+}
+
+
+// End of protos/perfetto/metrics/android/android_frame_timeline_metric.proto
+
+// Begin of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
+// Metric used to generate a simplified view of the Trusty kworker events.
+message AndroidTrustyWorkqueues {}
+
+// End of protos/perfetto/metrics/android/android_trusty_workqueues.proto
+
+// Begin of protos/perfetto/metrics/android/batt_metric.proto
+
+message AndroidBatteryMetric {
+  message BatteryCounters {
+    // Timestamp measured from boot time [ns].
+    optional int64 timestamp_ns = 1;
+    // Fields 2-5 are the same as in BatteryCounters proto in TracePacket.
+    optional double charge_counter_uah = 2;
+    optional float capacity_percent = 3;
+    optional double current_ua = 4;
+    optional double current_avg_ua = 5;
+  }
+
+  message BatteryAggregates {
+    // Field numbers for these 3 == the int values from Android
+    optional int64 total_screen_off_ns = 1;
+    optional int64 total_screen_on_ns = 2;
+    optional int64 total_screen_doze_ns = 3;
+    // Total time a wakelock was held
+    optional int64 total_wakelock_ns = 4;
+    // Amount of time the device was suspended. Depends on the ftrace source
+    // "power/suspend_resume".
+    optional int64 sleep_ns = 5;
+    optional int64 sleep_screen_off_ns = 6;
+    optional int64 sleep_screen_on_ns = 7;
+    optional int64 sleep_screen_doze_ns = 8;
+  }
+
+  // Period of time during the trace that the device went to sleep completely.
+  message SuspendPeriod {
+    optional int64 timestamp_ns = 1;
+    optional int64 duration_ns = 2;
+  }
+
+  // Battery counters info for each ts of the trace. This should only be
+  // extracted for short traces.
+  repeated BatteryCounters battery_counters = 1;
+
+  optional BatteryAggregates battery_aggregates = 2;
+
+  repeated SuspendPeriod suspend_period = 3;
+}
+
+// End of protos/perfetto/metrics/android/batt_metric.proto
+
+// Begin of protos/perfetto/metrics/android/binder_metric.proto
+
+// This metric provides per-process Binder statistics for traces with binder_driver enabled
+// Specifically, transactions are categorized and counted
+message AndroidBinderMetric {
+  message PerProcessBreakdown {
+    optional string process_name = 1;
+    optional uint32 pid = 2;
+    optional string slice_name = 3;
+    optional uint32 count = 4;
+  }
+
+  repeated PerProcessBreakdown process_breakdown = 1;
+}
+// End of protos/perfetto/metrics/android/binder_metric.proto
+
+// Begin of protos/perfetto/metrics/android/camera_metric.proto
+
+message AndroidCameraMetric {
+  message Counter {
+    optional double min = 1;
+    optional double max = 2;
+    optional double avg = 3;
+  }
+
+  // Counter for the sum of DMA and RSS across GCA, cameraserver
+  // and HAL. This provides a single number for the memory
+  // pressure using the camera is putting on the rest of the
+  // system.
+  //
+  // Note: this number assumes that all DMA pressure is coming
+  // from the camera as this is usually a pretty good
+  // approximation. Being more accurate here would increase the
+  // complexity of the metric significantly.
+  //
+  // Note: if there are multiple GCA/cameraserver/HAL processes
+  // in the trace, this metric will simply take the latest
+  // one in the trace and ignore the others.
+  optional Counter gc_rss_and_dma = 1;
+}
+
+// End of protos/perfetto/metrics/android/camera_metric.proto
+
+// Begin of protos/perfetto/metrics/android/camera_unagg_metric.proto
+
+message AndroidCameraUnaggregatedMetric {
+  message Value {
+    optional int64 ts = 1;
+    optional double value = 2;
+  }
+
+  // Timeseries for the sum of DMA and RSS across GCA, cameraserver
+  // and HAL. This provides a single number for the memory
+  // pressure using the camera is putting on the rest of the
+  // system.
+  //
+  // Note: this number assumes that all DMA pressure is coming
+  // from the camera as this is usually a pretty good
+  // approximation. Being more accurate here would increase the
+  // complexity of the metric significantly.
+  //
+  // Note: if there are multiple GCA/cameraserver/HAL processes
+  // in the trace, this metric will simply take the latest
+  // one in the trace and ignore the others.
+  repeated Value gc_rss_and_dma = 1;
+}
+
+// End of protos/perfetto/metrics/android/camera_unagg_metric.proto
+
+// Begin of protos/perfetto/metrics/android/cpu_metric.proto
+
+message AndroidCpuMetric {
+  // Next id: 6
+  message Metrics {
+    // CPU megacycles (i.e. cycles divided by 1e6).
+    optional int64 mcycles = 1;
+
+    // Total time the thread was running for this breakdown in
+    // nanoseconds.
+    optional int64 runtime_ns = 2;
+
+    // Min/max/average CPU frequency weighted by the time the CPU was
+    // running at each frequency in this breakdown.
+    optional int64 min_freq_khz = 3;
+    optional int64 max_freq_khz = 4;
+    optional int64 avg_freq_khz = 5;
+  }
+
+  // Next id: 7
+  message CoreData {
+    optional uint32 id = 1;
+    optional Metrics metrics = 6;
+
+    reserved 2 to 5;
+  }
+
+  // Next id: 3
+  message CoreTypeData {
+    optional string type = 1;
+    optional Metrics metrics = 2;
+  }
+
+  // Next id: 7
+  message Thread {
+    optional string name = 1;
+    optional Metrics metrics = 4;
+
+    // Breakdowns of above metrics.
+    repeated CoreData core = 2;
+    repeated CoreTypeData core_type = 5;
+
+    reserved 3;
+  }
+
+  // Next id: 8
+  message Process {
+    optional string name = 1;
+    optional Metrics metrics = 4;
+
+    // Breakdowns of above metrics.
+    repeated Thread threads = 6;
+    repeated CoreData core = 7;
+    repeated CoreTypeData core_type = 5;
+
+    reserved 3;
+  }
+
+  repeated Process process_info = 1;
+}
+
+// End of protos/perfetto/metrics/android/cpu_metric.proto
+
+// Begin of protos/perfetto/metrics/android/display_metrics.proto
+
+message AndroidDisplayMetrics {
+  // Stat that reports the number of duplicate frames submitted
+  // to the display for rendering. That is frames that have the same
+  // pixels values but where still submitted. It is tracked based on
+  // comparing the MISR of the current frame vs previous frame.
+  optional uint32 total_duplicate_frames = 1;
+
+  // Stat reports whether there is any duplicate_frames tracked
+  optional uint32 duplicate_frames_logged = 2;
+
+  // Stat that reports the number of dpu underrrun occurs count.
+  optional uint32 total_dpu_underrun_count = 3;
+
+
+  message RefreshRateStat {
+    // The refresh rate value (the number of frames per second)
+    optional uint32 refresh_rate_fps = 1;
+
+    // Calculate the number of refresh rate switches to this fps
+    optional uint32 count = 2;
+
+    // Calculate the total duration of refresh rate stays at this fps
+    optional double total_dur_ms = 3;
+
+    // Calculate the average duration of refresh rate stays at this fps
+    optional double avg_dur_ms = 4;
+  }
+
+  // Calculate the total number of refresh rate changes
+  optional uint32 refresh_rate_switches = 4;
+
+  // The statistics for each refresh rate value
+  repeated RefreshRateStat refresh_rate_stats = 5;
+}
+
+// End of protos/perfetto/metrics/android/display_metrics.proto
+
+// Begin of protos/perfetto/metrics/android/dma_heap_metric.proto
+
+// dma-buf heap memory stats on Android.
+message AndroidDmaHeapMetric {
+    optional double avg_size_bytes = 1;
+    optional double min_size_bytes = 2;
+    optional double max_size_bytes = 3;
+
+    // Total allocation size.
+    // Essentially the sum of positive allocs.
+    optional double total_alloc_size_bytes = 4;
+}
+
+// End of protos/perfetto/metrics/android/dma_heap_metric.proto
+
+// Begin of protos/perfetto/metrics/android/dvfs_metric.proto
+
+message AndroidDvfsMetric {
+
+  message BandStat {
+    // Operating frequency
+    optional int32 freq_value = 1;
+
+    // Percentage of duration in this operating frequency compared to all frequencies
+    optional double percentage = 2;
+
+    // Total duration in ns when the state was in this operating frequency
+    optional int64 duration_ns = 3;
+  }
+
+  message FrequencyResidency {
+    // Frequency representative name
+    optional string freq_name = 1;
+    // Each band statistics meta
+    repeated BandStat band_stat = 2;
+  }
+
+  // Frequency residency metrics from clock_set_rate ftrace event.
+  repeated FrequencyResidency freq_residencies = 1;
+}
+
+// End of protos/perfetto/metrics/android/dvfs_metric.proto
+
+// Begin of protos/perfetto/metrics/android/fastrpc_metric.proto
+
+// fastrpc memory stats on Android.
+message AndroidFastrpcMetric {
+  message Subsystem {
+    optional string name = 1;
+    optional double avg_size_bytes = 2;
+    optional double min_size_bytes = 3;
+    optional double max_size_bytes = 4;
+
+    // Total allocation size.
+    // Essentially the sum of positive allocs.
+    optional double total_alloc_size_bytes = 5;
+  }
+
+  repeated Subsystem subsystem = 1;
+}
+
+// End of protos/perfetto/metrics/android/fastrpc_metric.proto
+
+// Begin of protos/perfetto/metrics/android/g2d_metric.proto
+
+message G2dMetrics {
+  message G2dInstance {
+    // G2d name.
+    optional string name = 1;
+
+    optional uint32 frame_count = 5;
+    optional uint32 error_count = 6;
+
+    optional double max_dur_ms = 7;
+    optional double min_dur_ms = 8;
+    optional double avg_dur_ms = 9;
+
+    // Removed: was int64 versions of max_dur_ns, min_dur_ns and avg_dur_ns.
+    reserved 2 to 4;
+  }
+  message G2dMetric {
+    // G2D Metric for each G2D Instance.
+    repeated G2dInstance instances = 1;
+
+    // the number of frames processed by G2D
+    optional uint32 frame_count = 5;
+    // the number of error events
+    optional uint32 error_count = 6;
+
+    // max/min/avg G2d frame durations for all instances.
+    optional double max_dur_ms = 7;
+    optional double min_dur_ms = 8;
+    optional double avg_dur_ms = 9;
+
+    // Removed: was int64 versions of max_dur_ns, min_dur_ns and avg_dur_ns.
+    reserved 2 to 4;
+  }
+
+  optional G2dMetric g2d_hw = 1;
+  optional G2dMetric g2d_sw = 2;
+}
+
+// End of protos/perfetto/metrics/android/g2d_metric.proto
+
+// Begin of protos/perfetto/metrics/android/gpu_metric.proto
+
+message AndroidGpuMetric {
+  message Process {
+    // Process name.
+    optional string name = 1;
+
+    // max/min/avg GPU memory used by this process.
+    optional int64 mem_max = 2;
+    optional int64 mem_min = 3;
+    optional int64 mem_avg = 4;
+  }
+
+  // GPU metric for processes using GPU.
+  repeated Process processes = 1;
+
+  // max/min/avg GPU memory used by the entire system.
+  optional int64 mem_max = 2;
+  optional int64 mem_min = 3;
+  optional int64 mem_avg = 4;
+
+  message FrequencyMetric {
+    // Identifier for GPU in a multi-gpu device.
+    optional uint32 gpu_id = 1;
+
+    // max/min/avg GPU frequency for this gpu_id
+    // the calculation of avg is weighted by the duration of each frequency
+    optional int64 freq_max = 2;
+    optional int64 freq_min = 3;
+    optional double freq_avg = 4;
+
+    message MetricsPerFrequency {
+      // Used frequency
+      optional int64 freq = 1;
+
+      // Total duration in ms when the state of GPU was in this frequency
+      optional double dur_ms = 2;
+
+      // Percentage of duration in this frequency compared to all frequencies
+      // in this gpu_id
+      optional double percentage = 3;
+    }
+
+    // Metrics for each used GPU frequency
+    repeated MetricsPerFrequency used_freqs = 5;
+  }
+
+  // GPU frequency metric for each gpu_id
+  repeated FrequencyMetric freq_metrics = 5;
+}
+
+// End of protos/perfetto/metrics/android/gpu_metric.proto
+
+// Begin of protos/perfetto/metrics/android/hwcomposer.proto
+
+message AndroidHwcomposerMetrics {
+  // Counts the number of composition total layers in the trace. (non-weighted average)
+  optional double composition_total_layers = 1;
+
+  // Counts the number of composition dpu layers in the trace. (non-weighted average)
+  optional double composition_dpu_layers = 2;
+
+  // Counts the number of composition gpu layers in the trace. (non-weighted average)
+  optional double composition_gpu_layers = 3;
+
+  // Counts the number of composition dpu cached layers in the trace. (non-weighted average)
+  optional double composition_dpu_cached_layers = 4;
+
+  // Counts the number of composition surfaceflinger cached layers in the trace.
+  // (non-weighted average)
+  optional double composition_sf_cached_layers = 5;
+
+  // Counts how many times validateDisplay is skipped.
+  optional int32 skipped_validation_count = 6;
+
+  // Counts how many times validateDisplay cannot be skipped.
+  optional int32 unskipped_validation_count = 7;
+
+  // Counts how many times validateDisplay is already separated from presentDisplay
+  // since the beginning.
+  optional int32 separated_validation_count = 8;
+
+  // Counts how many unhandled validation cases which might be caused by errors.
+  optional int32 unknown_validation_count = 9;
+
+  // the average of overall hwcomposer execution time.
+  optional double avg_all_execution_time_ms = 10;
+
+  // the average of hwcomposer execution time for skipped validation cases.
+  optional double avg_skipped_execution_time_ms = 11;
+
+  // the average of hwcomposer execution time for unskipped validation cases.
+  optional double avg_unskipped_execution_time_ms = 12;
+
+  // the average of hwcomposer execution time for separated validation cases.
+  optional double avg_separated_execution_time_ms = 13;
+
+  message DpuVoteMetrics {
+    // the thread ID that handles this track
+    optional uint32 tid = 1;
+
+    // the weighted average of DPU Vote Clock
+    optional double avg_dpu_vote_clock = 2;
+
+    // the weighted average of DPU Vote Avg Bandwidth
+    optional double avg_dpu_vote_avg_bw = 3;
+
+    // the weighted average of DPU Vote Peak Bandwidth
+    optional double avg_dpu_vote_peak_bw = 4;
+
+    // the weighted average of DPU Vote RT (Real Time) Bandwidth
+    optional double avg_dpu_vote_rt_bw = 5;
+  }
+
+  // DPU Vote Metrics for each thread track
+  repeated DpuVoteMetrics dpu_vote_metrics = 14;
+}
+
+// End of protos/perfetto/metrics/android/hwcomposer.proto
+
+// Begin of protos/perfetto/metrics/android/hwui_metric.proto
+
+// Android HWUI graphics performance and graphics memory usage metrics.
+message ProcessRenderInfo {
+  // Name of the package launched
+  optional string process_name = 1;
+
+  // CPU time spent on RenderThread in milliseconds.
+  optional int64 rt_cpu_time_ms = 2;
+
+  // Number of frames drawn on RenderThread, followed by max/min/avg CPU time to draw a frame
+  // in nanoseconds.
+  optional uint32 draw_frame_count = 3;
+  optional int64 draw_frame_max = 4;
+  optional int64 draw_frame_min = 5;
+  optional double draw_frame_avg = 6;
+
+  // Number of GPU commands flushes and max/min/avg time per flush in nanoseconds.
+  optional uint32 flush_count = 7;
+  optional int64 flush_max = 8;
+  optional int64 flush_min = 9;
+  optional double flush_avg = 10;
+
+  // Number of View tree preparation counts and max/min/avg time to traverse the tree in
+  // nanoseconds.
+  optional uint32 prepare_tree_count = 11;
+  optional int64 prepare_tree_max = 12;
+  optional int64 prepare_tree_min = 13;
+  optional double prepare_tree_avg = 14;
+
+  // Number of times the GPU rendered a frame and max/min/avg time for GPU to finish rendering in
+  // in nanoseconds.
+  optional uint32 gpu_completion_count = 15;
+  optional int64 gpu_completion_max = 16;
+  optional int64 gpu_completion_min = 17;
+  optional double gpu_completion_avg = 18;
+
+  // Number of times a frame was recorded/serialized in a display list on the UI thread with
+  // max/min/avg time in nanoseconds.
+  optional uint32 ui_record_count = 19;
+  optional int64 ui_record_max = 20;
+  optional int64 ui_record_min = 21;
+  optional double ui_record_avg = 22;
+
+  // number of unique shader programs that were used to render frames, followed by total and average
+  // times to prepare a shader in nanoseconds.
+  optional uint32 shader_compile_count = 23;
+  optional int64 shader_compile_time = 24;
+  optional double shader_compile_avg = 25;
+  // number of shader programs loaded from the disk cache, followed by total time and average time
+  // to prepare a shader in nanoseconds.
+  optional uint32 cache_hit_count = 26;
+  optional int64 cache_hit_time = 27;
+  optional double cache_hit_avg = 28;
+  // number of shader programs compiled/linked, followed by total time and average time to prepare
+  // a shader in nanoseconds.
+  optional uint32 cache_miss_count = 29;
+  optional int64 cache_miss_time = 30;
+  optional double cache_miss_avg = 31;
+
+  // max/min/avg CPU memory used for graphics by HWUI at the end of a frame.
+  optional int64 graphics_cpu_mem_max = 32;
+  optional int64 graphics_cpu_mem_min = 33;
+  optional double graphics_cpu_mem_avg = 34;
+
+  // max/min/avg GPU memory used by HWUI at the end of a frame excluding textures.
+  optional int64 graphics_gpu_mem_max = 35;
+  optional int64 graphics_gpu_mem_min = 36;
+  optional double graphics_gpu_mem_avg = 37;
+
+  // max/min/avg memory used for GPU textures by HWUI at the end of a frame.
+  optional int64 texture_mem_max = 38;
+  optional int64 texture_mem_min = 39;
+  optional double texture_mem_avg = 40;
+
+  // max/min/avg memory used by HWUI at the end of a frame. This is a sum of previous 3 categories.
+  optional int64 all_mem_max = 41;
+  optional int64 all_mem_min = 42;
+  optional double all_mem_avg = 43;
+}
+
+message AndroidHwuiMetric {
+  //  HWUI metrics for processes that have a RenderThread.
+  repeated ProcessRenderInfo process_info = 1;
+}
+
+// End of protos/perfetto/metrics/android/hwui_metric.proto
+
+// Begin of protos/perfetto/metrics/android/ion_metric.proto
+
+// ion memory stats on Android.
+message AndroidIonMetric {
+  message Buffer {
+    optional string name = 1;
+    optional double avg_size_bytes = 2;
+    optional double min_size_bytes = 3;
+    optional double max_size_bytes = 4;
+
+    // Total allocation size.
+    // Essentially the sum of positive allocs (-> new buffers).
+    optional double total_alloc_size_bytes = 5;
+  }
+
+  repeated Buffer buffer = 1;
+}
+
+// End of protos/perfetto/metrics/android/ion_metric.proto
+
+// Begin of protos/perfetto/metrics/android/irq_runtime_metric.proto
+
+// measure max IRQ runtime and IRQ tasks running over threshold.
+message AndroidIrqRuntimeMetric {
+  message IrqSlice {
+    // IRQ name
+    optional string irq_name = 1;
+    // timestamp
+    optional int64 ts = 2;
+    // runtime of IRQ task
+    optional int64 dur = 3;
+  }
+  message ThresholdMetric {
+    // Threshold value
+    optional string threshold = 1;
+    // over threshold count
+    optional int64 over_threshold_count = 2;
+    // anomaly ratio (over threshold count / total count)
+    optional double anomaly_ratio= 3;
+  }
+  message IrqRuntimeMetric {
+    // max runtime of IRQ tasks
+    optional int64 max_runtime = 1;
+    // total IRQ tasks
+    optional int64 total_count = 2;
+    // over threshold metric
+    optional ThresholdMetric threshold_metric = 3;
+    // information for top 10 IRQ tasks
+    repeated IrqSlice longest_irq_slices = 4;
+  }
+
+  // metrics for hardirq and softirq
+  optional IrqRuntimeMetric hw_irq = 1;
+  optional IrqRuntimeMetric sw_irq = 2;
+}
+
+
+// End of protos/perfetto/metrics/android/irq_runtime_metric.proto
+
+// Begin of protos/perfetto/metrics/android/jank_cuj_metric.proto
+
+message AndroidJankCujMetric {
+  repeated Cuj cuj = 1;
+
+  // Next id: 11
+  message Cuj {
+    // ID of the CUJ that is unique within the trace.
+    optional int32 id = 1;
+
+    // Name of the CUJ, extracted from the CUJ trace marker.
+    // For example SHADE_EXPAND_COLLAPSE from J<SHADE_EXPAND_COLLAPSE>.
+    optional string name = 2;
+
+    // Details about the process (uid, version, etc)
+    optional AndroidProcessMetadata process = 3;
+
+    // ts of the CUJ trace marker slice.
+    optional int64 ts = 4;
+
+    // dur of the CUJ trace marker slice.
+    optional int64 dur = 5;
+
+    // Details about each of the frames within the CUJ.
+    repeated Frame frame = 6;
+
+    // Details about each of the SF frames within the CUJ.
+    repeated Frame sf_frame = 10;
+
+    // Metrics extracted from the counters output by FrameTracker
+    // Does not contain the frame_dur percentile information.
+    optional Metrics counter_metrics = 7;
+
+    // Metrics extracted from the frame timeline.
+    optional Metrics timeline_metrics = 8;
+
+    // Metrics extracted from the trace slices.
+    optional Metrics trace_metrics = 9;
+  }
+
+  // Next id: 8
+  message Frame {
+    // Index of the frame within the single user journey.
+    optional int64 frame_number = 1;
+
+    // VSYNC ID of the frame.
+    optional int64 vsync = 2;
+
+    optional int64 ts = 3;
+    optional int64 dur = 4;
+    optional int64 dur_expected = 7;
+
+    // Whether the app process missed the frame deadline.
+    // Only set for the App frames. Always left unset for SF frames.
+    optional bool app_missed = 5;
+
+    // Whether SF missed the frame deadline.
+    optional bool sf_missed = 6;
+  }
+
+  // Next id: 12
+  message Metrics {
+    // Overall number of frames within the CUJ.
+    optional int64 total_frames = 1;
+
+    // Number of missed frames.
+    optional int64 missed_frames = 2;
+
+    // Number of frames missed due to the app missing the deadline.
+    optional int64 missed_app_frames = 3;
+
+    // Number of frames missed due to SF.
+    optional int64 missed_sf_frames = 4;
+
+    // Number of successive frames missed.
+    // Not available in timeline_metrics and trace_metrics.
+    optional int64 missed_frames_max_successive = 5;
+
+    // Max frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_max = 6;
+
+    // Average frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_avg = 7;
+
+    // Median frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_p50 = 8;
+
+    // P90 frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_p90 = 9;
+
+    // P95 frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_p95 = 10;
+
+    // P99 frame duration.
+    // Not available in counter_metrics.
+    optional int64 frame_dur_p99 = 11;
+  }
+}
+
+// End of protos/perfetto/metrics/android/jank_cuj_metric.proto
+
+// Begin of protos/perfetto/metrics/android/java_heap_histogram.proto
+
+message JavaHeapHistogram {
+  // Next id: 9
+  message TypeCount {
+    optional string type_name = 1;
+    optional string category = 4;
+
+    optional uint32 obj_count = 2;
+    optional uint32 reachable_obj_count = 3;
+
+    optional uint32 size_kb = 5;
+    optional uint32 reachable_size_kb = 6;
+    optional uint32 native_size_kb = 7;
+    optional uint32 reachable_native_size_kb = 8;
+  }
+
+  message Sample {
+    optional int64 ts = 1;
+    repeated TypeCount type_count = 2;
+  }
+
+  // Heap stats per process. One sample per dump (with continuous dump you can
+  // have more samples differentiated by ts).
+  message InstanceStats {
+    optional uint32 upid = 1;
+    optional AndroidProcessMetadata process = 2;
+    repeated Sample samples = 3;
+  }
+
+  repeated InstanceStats instance_stats = 1;
+}
+
+// End of protos/perfetto/metrics/android/java_heap_histogram.proto
+
+// Begin of protos/perfetto/metrics/android/java_heap_stats.proto
+
+message JavaHeapStats {
+  message HeapRoots {
+    optional string root_type = 1;
+    optional string type_name = 2;
+    optional int64 obj_count = 3;
+  }
+
+  // Next id: 10
+  message Sample {
+    optional int64 ts = 1;
+    // Size of the Java heap in bytes
+    optional int64 heap_size = 2;
+    // Native size of all the objects (not included in heap_size)
+    optional int64 heap_native_size = 8;
+    optional int64 obj_count = 4;
+    // Size of the reachable objects in bytes.
+    optional int64 reachable_heap_size = 3;
+    // Native size of all the reachable objects (not included in
+    // reachable_heap_size)
+    optional int64 reachable_heap_native_size = 9;
+    optional int64 reachable_obj_count = 5;
+    // Sum of anonymous RSS + swap pages in bytes.
+    optional int64 anon_rss_and_swap_size = 6;
+
+    // ART root objects
+    repeated HeapRoots roots = 7;
+  }
+
+  // Heap stats per process. One sample per dump (can be > 1 if continuous
+  // dump is enabled).
+  message InstanceStats {
+    optional uint32 upid = 1;
+    optional AndroidProcessMetadata process = 2;
+    repeated Sample samples = 3;
+  }
+
+  repeated InstanceStats instance_stats = 1;
+}
+
+// End of protos/perfetto/metrics/android/java_heap_stats.proto
+
+// Begin of protos/perfetto/metrics/android/lmk_metric.proto
+
+// LMK stats on Android.
+message AndroidLmkMetric {
+  message ByOomScore {
+    optional int32 oom_score_adj = 1;
+    optional int32 count = 2;
+  }
+
+  // Total count of LMK events observed in the trace.
+  optional int32 total_count = 1;
+  repeated ByOomScore by_oom_score = 2;
+
+  // OOM reaper kills. Enabled via the oom/mark_victim point. Should never
+  // happen.
+  optional int32 oom_victim_count = 3;
+}
+
+// End of protos/perfetto/metrics/android/lmk_metric.proto
+
+// Begin of protos/perfetto/metrics/android/lmk_reason_metric.proto
+
+// Global process state at LMK time, used to identify potential culprits.
+// TODO: rename to AndroidLmkProcessState
+message AndroidLmkReasonMetric {
+  message Process {
+    optional AndroidProcessMetadata process = 1;
+
+    // OOM score adj of the process.
+    optional int32 oom_score_adj = 2;
+
+    // RSS + swap.
+    optional int64 size = 3;
+
+    optional int64 file_rss_bytes = 4;
+    optional int64 anon_rss_bytes = 5;
+    optional int64 shmem_rss_bytes = 6;
+    optional int64 swap_bytes = 7;
+  }
+  message Lmk {
+    // OOM score adj of the LMK'ed process.
+    optional int32 oom_score_adj = 1;
+
+    // Total size of the ION heap in bytes during this LMK.
+    optional int64 ion_heaps_bytes = 4;
+    // Deprecated. Prefer ion_heaps_bytes.
+    optional int64 system_ion_heap_size = 2;
+
+    // Processes present during this LMK.
+    repeated Process processes = 3;
+  }
+
+  // LMKs present in the trace, ordered on their timestamp.
+  repeated Lmk lmks = 1;
+}
+
+// End of protos/perfetto/metrics/android/lmk_reason_metric.proto
+
+// Begin of protos/perfetto/metrics/android/mem_metric.proto
+
+// Memory metrics on Android.
+message AndroidMemoryMetric {
+  message ProcessMetrics {
+    optional string process_name = 1;
+    optional ProcessMemoryCounters total_counters = 2;
+    repeated PriorityBreakdown priority_breakdown = 3;
+  }
+
+  message PriorityBreakdown {
+    optional string priority = 1;
+    optional ProcessMemoryCounters counters = 2;
+  }
+
+  message ProcessMemoryCounters {
+    optional Counter anon_rss = 1;
+    optional Counter file_rss = 2;
+    optional Counter swap = 3;
+    optional Counter anon_and_swap = 4;
+
+    // Available when ART trace events are available.
+    optional Counter java_heap = 5;
+  }
+
+  message Counter {
+    optional double min = 1;
+    optional double max = 2;
+    optional double avg = 3;
+
+    // Memory growth observed in the counter sequence. In case of multiple
+    // processes with the same name, break ties using max.
+    optional double delta = 4;
+  }
+
+  // Process metrics, grouped by process name
+  repeated ProcessMetrics process_metrics = 1;
+}
+
+// End of protos/perfetto/metrics/android/mem_metric.proto
+
+// Begin of protos/perfetto/metrics/android/mem_unagg_metric.proto
+
+// Unaggregated memory metrics on Android.
+message AndroidMemoryUnaggregatedMetric {
+  message ProcessValues {
+    optional string process_name = 1;
+    optional ProcessMemoryValues mem_values = 2;
+  }
+
+  message ProcessMemoryValues {
+    repeated Value anon_rss = 1;
+    repeated Value file_rss = 2;
+    repeated Value swap = 3;
+    repeated Value anon_and_swap = 4;
+  }
+
+  message Value {
+    optional int64 ts = 1;
+    optional int32 oom_score = 2;
+    optional double value = 3;
+  }
+
+  // Process metrics for every process instance in trace.
+  repeated ProcessValues process_values = 1;
+}
+
+// End of protos/perfetto/metrics/android/mem_unagg_metric.proto
+
+// Begin of protos/perfetto/metrics/android/multiuser_metric.proto
+
+// Metrics for Multiuser events, such as switching users.
+message AndroidMultiuserMetric {
+
+  // Holds the data for a Multiuser event.
+  message EventData {
+    // Duration of the event (in milliseconds).
+    optional int32 duration_ms = 1;
+
+    // CPU usage of each process during the event.
+    message CpuUsage {
+      // The userId of the process (e.g. 0 or 10).
+      optional int32 user_id = 1;
+      // The name of the process.
+      optional string process_name = 2;
+      // The number of CPU cycles (in megacycles) spent by that process during the event.
+      optional int32 cpu_mcycles = 3;
+      // The ratio of this process's cycles to the total for all processes, expressed as a percentage.
+      optional float cpu_percentage = 4;
+      // General identifier for this usage source: determined from the process name, user, etc.
+      // Should be stable across multiple runs (i.e. does not print the user_id directly).
+      optional string identifier = 5;
+    }
+    repeated CpuUsage cpu_usage = 2;
+  }
+
+  // Metrics for a user switch.
+  optional EventData user_switch = 1;
+}
+// End of protos/perfetto/metrics/android/multiuser_metric.proto
+
+// Begin of protos/perfetto/metrics/android/network_metric.proto
+
+message AndroidNetworkMetric {
+  message PacketStatistic {
+    // Packet count.
+    optional int64 packets = 1;
+
+    // Packet Bytes.
+    optional int64 bytes = 2;
+
+    // Timestamp when first packet received or transmitted.
+    optional int64 first_packet_timestamp_ns = 3;
+
+    // Timestamp when last packet received or transmitted.
+    optional int64 last_packet_timestamp_ns = 4;
+
+    // Interval between first & last packet. The minimum interval is 10ms.
+    optional int64 interval_ns = 5;
+
+    // Data Speed.
+    optional double data_rate_kbps = 6;
+  }
+
+  message CorePacketStatistic {
+    optional uint32 id = 1;
+    optional PacketStatistic packet_statistic = 2;
+  }
+
+  message Rx {
+    // Total packets statistic.
+    optional PacketStatistic total = 1;
+
+    // Per core packets statistic.
+    repeated CorePacketStatistic core = 2;
+
+    // GRO aggregation ratio.
+    optional string gro_aggregation_ratio = 3;
+  }
+
+  message Tx {
+    // Total packets statistic.
+    optional PacketStatistic total = 1;
+
+    // Per core packets statistic.
+    repeated CorePacketStatistic core = 2;
+  }
+
+  message NetDevice {
+    // Network device name.
+    optional string name = 1;
+
+    // Ingress traffic statistic.
+    optional Rx rx = 2;
+
+    // Egress traffic statistic
+    optional Tx tx = 3;
+  }
+
+  message NetRxActionStatistic {
+    // SoftIrq NET_RX action count.
+    optional int64 count = 1;
+
+    // SoftIrq NET_RX action was running in millisecond.
+    optional double runtime_ms = 2;
+
+    // SoftIrq NET_RX action average running time.
+    optional double avg_runtime_ms = 3;
+
+    // CPU megacycles (i.e. cycles divided by 1e6).
+    optional int64 mcycles = 4;
+
+    // Average weighted CPU frequency by the time the NET_RX Action
+    // running at each frequency.
+    optional int64 avg_freq_khz = 5;
+  }
+
+  message NetTxActionStatistic {
+    // SoftIrq NET_TX action count.
+    optional int64 count = 1;
+
+    // SoftIrq NET_TX action was running in millisecond.
+    optional double runtime_ms = 2;
+
+    // SoftIrq NET_TX action average running time.
+    optional double avg_runtime_ms = 3;
+
+    // CPU megacycles (i.e. cycles divided by 1e6).
+    optional int64 mcycles = 4;
+
+    // Average weighted CPU frequency by the time the NET_TX Action
+    // running at each frequency.
+    optional int64 avg_freq_khz = 5;
+  }
+
+  message IpiActionStatistic {
+    // SoftIrq IPI action count.
+    optional int64 count = 1;
+
+    // SoftIrq IPI action was running in millisecond.
+    optional double runtime_ms = 2;
+
+    // SoftIrq IPI action average running time.
+    optional double avg_runtime_ms = 3;
+  }
+
+  message CoreNetRxActionStatistic {
+    optional uint32 id = 1;
+    optional NetRxActionStatistic net_rx_action_statistic = 2;
+  }
+
+  message CoreNetTxActionStatistic {
+    optional uint32 id = 1;
+    optional NetTxActionStatistic net_tx_action_statistic = 2;
+  }
+
+  message NetRxAction {
+    // Total NET_RX action statistics.
+    optional NetRxActionStatistic total = 1;
+
+    // Per core NET_RX action statistics.
+    repeated CoreNetRxActionStatistic core = 2;
+
+    // The average packet time moves through the kernel stack.
+    optional double avg_interstack_latency_ms = 3;
+  }
+
+  message NetTxAction {
+    // Total NET_TX action statistics.
+    optional NetTxActionStatistic total = 1;
+
+    // Per core NET_TX action statistics.
+    repeated CoreNetTxActionStatistic core = 2;
+  }
+
+  message IpiAction {
+    // Total IPI action statistics.
+    optional IpiActionStatistic total = 1;
+  }
+
+  // Network device metrics.
+  repeated NetDevice net_devices = 1;
+
+  // SoftIrq NET_RX action metrics.
+  optional NetRxAction net_rx_action = 2;
+
+  // Packet retransmission rate.
+  optional double retransmission_rate = 3;
+
+  // Kfree Skb rate (i.e. kfree_skb count divided by the packet count from all
+  // net devices).
+  optional double kfree_skb_rate = 4;
+
+  // SoftIrq NET_TX action metrics.
+  optional NetTxAction net_tx_action = 5;
+
+  // SoftIrq IPI action metrics.
+  optional IpiAction ipi_action = 6;
+}
+
+// End of protos/perfetto/metrics/android/network_metric.proto
+
+// Begin of protos/perfetto/metrics/android/other_traces.proto
+
+message AndroidOtherTracesMetric {
+  // Uuids of other traces being finalized while the current trace was being
+  // recorded.
+  repeated string finalized_traces_uuid = 1;
+}
+
+// End of protos/perfetto/metrics/android/other_traces.proto
+
+// Begin of protos/perfetto/metrics/android/package_list.proto
+
+message AndroidPackageList {
+  message Package {
+    optional string package_name = 1;
+    optional int64 uid = 2;
+    optional int64 version_code = 3;
+  }
+
+  repeated Package packages = 1;
+}
+
+// End of protos/perfetto/metrics/android/package_list.proto
+
+// Begin of protos/perfetto/metrics/android/powrails_metric.proto
+
+message AndroidPowerRails {
+  // Energy data per Power Rail at given ts.
+  message EnergyData {
+    // Time since device boot(CLOCK_BOTTOMTIME) in milli-seconds.
+    optional int64 timestamp_ms = 1;
+    // Accumulated energy since device boot in microwatt-seconds(uws).
+    optional double energy_uws = 2;
+  }
+
+  message PowerRails {
+    // Name of the rail.
+    optional string name = 1;
+    // Energy data for given rail and for all samples in the trace.
+    repeated EnergyData energy_data = 2;
+    // The average used power between the first and the last sampled
+    // energy data in miliwatt (mw)
+    optional double avg_used_power_mw = 3;
+  }
+
+  // Energy data per Power Rail.
+  repeated PowerRails power_rails = 1;
+
+  // The average used power between the first and last sampled rail across all
+  // the rails in milliwatts (mw).
+  optional double avg_total_used_power_mw = 2;
+}
+// End of protos/perfetto/metrics/android/powrails_metric.proto
+
+// Begin of protos/perfetto/metrics/android/profiler_smaps.proto
+
+message ProfilerSmaps {
+  message Mapping {
+    optional string path = 1;
+    optional int32 size_kb = 2;
+    optional int32 private_dirty_kb = 3;
+    optional int32 swap_kb = 4;
+  }
+
+  message Instance {
+    optional AndroidProcessMetadata process = 1;
+    repeated Mapping mappings = 2;
+  }
+
+  repeated Instance instance = 1;
+}
+
+// End of protos/perfetto/metrics/android/profiler_smaps.proto
+
+// Begin of protos/perfetto/metrics/android/rt_runtime_metric.proto
+
+// measure max RT runtime and RT tasks running over 5ms.
+message AndroidRtRuntimeMetric {
+  message RtSlice {
+    // thread name
+    optional string tname = 1;
+    // timestamp
+    optional int64 ts = 2;
+    // runtime of RT task
+    optional int64 dur = 3;
+  }
+
+  // max runtime of RT tasks
+  optional int64 max_runtime = 1;
+  // how many RT tasks are over 5ms.
+  optional int64 over_5ms_count = 2;
+  // information for top 10 RT tasks
+  repeated RtSlice longest_rt_slices = 3;
+}
+
+
+// End of protos/perfetto/metrics/android/rt_runtime_metric.proto
+
+// Begin of protos/perfetto/metrics/android/simpleperf.proto
+
+// Metric that stores information related to atrace events generated by
+// simpleperf tool
+message AndroidSimpleperfMetric {
+  optional double urgent_ratio = 1;
+
+  message PerfEventMetric {
+    // Simpleperf event name
+    optional string name = 1;
+
+    message Thread {
+      // Thread ID
+      optional int32 tid = 1;
+      // Thread name
+      optional string name = 2;
+      // CPU ID
+      optional int32 cpu = 3;
+      // Total counter value
+      optional double total = 4;
+    }
+
+    message Process {
+      // Process ID
+      optional int32 pid = 1;
+      // Process name
+      optional string name = 2;
+      // Metrics for each thread in this process.
+      repeated Thread threads = 3;
+      // Total counter value over all threads in this process
+      optional double total = 4;
+    }
+
+    // Metrics for each process
+    repeated Process processes = 2;
+
+    // Total counter value over all processes and threads
+    optional double total = 3;
+  }
+
+  repeated PerfEventMetric events = 2;
+}
+
+// End of protos/perfetto/metrics/android/simpleperf.proto
+
+// Begin of protos/perfetto/metrics/android/startup_metric.proto
+
+// Android app startup metrics.
+message AndroidStartupMetric {
+  // A simplified view of the task state durations for a thread
+  // and a span of time.
+  message TaskStateBreakdown {
+    optional int64 running_dur_ns = 1;
+    optional int64 runnable_dur_ns = 2;
+    optional int64 uninterruptible_sleep_dur_ns = 3;
+    optional int64 interruptible_sleep_dur_ns = 4;
+  }
+
+  message McyclesByCoreType {
+    optional int64 little = 1;
+    optional int64 big = 2;
+    optional int64 bigger = 3;
+    optional int64 unknown = 4;
+  }
+
+  message Slice {
+    optional int64 dur_ns = 1;
+    optional double dur_ms = 2;
+  }
+
+  // Timing information spanning the intent received by the
+  // activity manager to the first frame drawn.
+  // Next id: 33.
+  message ToFirstFrame {
+    // The duration between the intent received and first frame.
+    optional int64 dur_ns = 1;
+    optional double dur_ms = 17;
+
+    // Breakdown of time to first frame by task state for the main thread of
+    // the process starting up.
+    optional TaskStateBreakdown main_thread_by_task_state = 2;
+
+    // The mcycles taken by this startup across all CPUs (broken down by core
+    // type).
+    optional McyclesByCoreType mcycles_by_core_type = 26;
+
+    // In this timespan, how many processes (apart from the main activity) were
+    // spawned.
+    optional uint32 other_processes_spawned_count = 3;
+
+    // Total time spent in activity manager between the initial intent
+    // and the end of the activity starter.
+    optional Slice time_activity_manager = 4;
+
+    // The following slices follow the typical steps post-fork.
+    optional Slice time_activity_thread_main = 5;
+    optional Slice time_bind_application = 6;
+    optional Slice time_activity_start = 7;
+    optional Slice time_activity_resume = 8;
+    optional Slice time_activity_restart = 21;
+    optional Slice time_choreographer = 9;
+    optional Slice time_inflate = 22;
+    optional Slice time_get_resources = 23;
+
+    // If we are starting a new process, record the duration from the
+    // intent being received to the time we call the zygote.
+    optional Slice time_before_start_process = 10;
+
+    // The actual duration of the process start (based on the zygote slice).
+    optional Slice time_during_start_process = 11;
+
+    optional Slice to_post_fork = 18;
+    optional Slice to_activity_thread_main = 19;
+    optional Slice to_bind_application = 20;
+
+    optional Slice time_post_fork = 16;
+
+    // The total time spent on opening dex files.
+    optional Slice time_dex_open = 24;
+    // Total time spent verifying classes during app startup.
+    optional Slice time_verify_class = 25;
+
+    // Number of methods that were compiled by JIT during app startup.
+    optional uint32 jit_compiled_methods = 27;
+
+    // Time spent running CPU on jit thread pool.
+    optional Slice time_jit_thread_pool_on_cpu = 28;
+
+    // Time spent on garbage collection.
+    optional Slice time_gc_total = 29;
+    optional Slice time_gc_on_cpu = 30;
+
+    // Time spent in lock contention on the main thread of the process being
+    // started up. This includes *all* types of lock contention not just monitor
+    // contention.
+    optional Slice time_lock_contention_thread_main = 31;
+
+    // Time spent in monitor lock contention on the main thread of the
+    // process being started up. This will be a subset of the time counted by
+    // |time_lock_contention_thread_main|.
+    optional Slice time_monitor_contention_thread_main = 32;
+
+    // Removed: was other_process_to_activity_cpu_ratio.
+    reserved 12;
+
+    // Removed: was uint32 versions of to_post_fork, to_activity_thread_main and
+    // to_bind_application.
+    reserved 13, 14, 15;
+  }
+
+  // Metrics about startup which were developed by looking at experiments using
+  // high-speed cameras (HSC).
+  message HscMetrics {
+    // The duration of the full "startup" as defined by HSC tests.
+    optional Slice full_startup = 1;
+  }
+
+  message Activity {
+    optional string name = 1;
+    optional string method = 2;
+    optional int64 ts_method_start = 4;
+
+    // Field 3 contained Slice with a sum of durations for matching slices.
+    reserved 3;
+  }
+
+  message BinderTransaction {
+    optional Slice duration = 1;
+    optional string thread = 2;
+    optional string destination_thread = 3;
+    optional string destination_process = 4;
+    // From
+    // https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=15;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    optional string flags = 5;
+    // From
+    // https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=14;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    optional string code = 6;
+    // From
+    // https://cs.android.com/android/platform/superproject/+/master:external/perfetto/protos/perfetto/trace/ftrace/binder.proto;l=37;drc=7b6a788162a30802f4c9d8d7a30a54e25edd30f1
+    optional int64 data_size = 7;
+  }
+
+  // Metrics with information about the status of odex files and the outcome
+  // of the loading process.
+  // Multiple files might be loaded for a single startup. Platform might also
+  // decide to discard an odex file and instead load a fallback, for example
+  // in case the OS or apk were updated.
+  message OptimizationStatus {
+    optional string odex_status = 1;
+    optional string compilation_filter = 2;
+    optional string compilation_reason = 3;
+    optional string location = 4;
+  }
+
+  // Contains timestamps of important events which occurred during the
+  // startup.
+  message EventTimestamps {
+    optional int64 intent_received = 1;
+    optional int64 first_frame = 2;
+  }
+
+  // Contains information about the state of a system during the app startup.
+  // Useful to put the startup in context.
+  message SystemState {
+    // Whether the dex2oat64 process was running concurrent to the startup.
+    optional bool dex2oat_running = 1;
+
+    // Whether the installd process was running concurrent to the startup.
+    optional bool installd_running = 2;
+
+    // The number of broadcasts dispatched by the system during the app
+    // launch.
+    optional int64 broadcast_dispatched_count = 3;
+
+    // The number of broadcasts received by an app or the system during the
+    // app launch. Note that multiple packages can be subscribed to the same
+    // broadcast so a single dsipatch can cause multiple packages to receive
+    // and process a broadcast.
+    optional int64 broadcast_received_count = 4;
+
+    // The most active (i.e. consuming the most mcycles) processes during the
+    // app launch excluding the process(es) being launched.
+    // Note: the exact number of returned is an implementation detail and
+    // will likely change over time.
+    repeated string most_active_non_launch_processes = 5;
+  }
+
+  // Next id: 18
+  message Startup {
+    // Random id uniquely identifying an app startup in this trace.
+    optional uint32 startup_id = 1;
+
+    // Startup type (cold / warm / hot)
+    optional string startup_type = 16;
+
+    // Name of the package launched
+    optional string package_name = 2;
+
+    // Name of the process launched
+    optional string process_name = 3;
+
+    // Details about the activities launched
+    repeated Activity activities = 11;
+
+    // Details about slow binder transactions during the startup. The definition
+    // of a slow transaction is an implementation detail.
+    repeated BinderTransaction long_binder_transactions = 14;
+
+    // Did we ask the zygote for a new process
+    optional bool zygote_new_process = 4;
+
+    // Number of processes hosting the activity involved in the launch.
+    // This will usually be 1. If it is 0, it is indicative of a data / process
+    // error. If > 1, the process died during startup and the system respawned
+    // it.
+    optional uint32 activity_hosting_process_count = 6;
+
+    // Contains timestamps of important events which happened during
+    // the startup.
+    optional EventTimestamps event_timestamps = 13;
+
+    // Timing information spanning the intent received by the
+    // activity manager to the first frame drawn.
+    optional ToFirstFrame to_first_frame = 5;
+
+    // Details about the process (uid, version, etc)
+    optional AndroidProcessMetadata process = 7;
+
+    // Metrics about startup which were developed by looking at experiments
+    // using high-speed cameras (HSC).
+    optional HscMetrics hsc = 8;
+
+    // The time taken in the startup from intent received to the start time
+    // of the reportFullyDrawn slice. This should be longer than the time to
+    // first frame as the application decides this after it starts rendering.
+    optional Slice report_fully_drawn = 9;
+
+    // Contains information about the status of odex files.
+    repeated OptimizationStatus optimization_status = 12;
+
+    // Contains information about the state of the rest of the system during the
+    // startup. This is useful for getting context about why a startup might
+    // be slow beyond just what the app is doing.
+    optional SystemState system_state = 15;
+
+    // A list of identified potential causes for slow startup.
+    // Optional.
+    repeated string slow_start_reason = 17;
+
+    reserved 10;
+  }
+
+  repeated Startup startup = 1;
+}
+
+// End of protos/perfetto/metrics/android/startup_metric.proto
+
+// Begin of protos/perfetto/metrics/android/surfaceflinger.proto
+
+message AndroidSurfaceflingerMetric {
+  // Counts the number of missed frames in the trace.
+  optional uint32 missed_frames = 1;
+
+  // Counts the number of missed HWC frames in the trace.
+  optional uint32 missed_hwc_frames = 2;
+
+  // Counts the number of missed GPU frames in the trace.
+  optional uint32 missed_gpu_frames = 3;
+
+  // Calculate the number of missed frames divided by
+  // total frames
+  optional double missed_frame_rate = 4;
+
+  // Calculate the number of missed HWC frames divided by
+  // total HWC frames
+  optional double missed_hwc_frame_rate = 5;
+
+  // Calculate the number of missed GPU frames divided by
+  // total GPU frames
+  optional double missed_gpu_frame_rate = 6;
+
+  // Count the number of times SurfaceFlinger needs to invoke GPU
+  // for rendering some layers
+  optional uint32 gpu_invocations = 7;
+
+  // Calculate the average duration of GPU request by SurfaceFlinger
+  // since it enters the FenceMonitor's queue until it gets completed
+  optional double avg_gpu_waiting_dur_ms = 8;
+
+  // Calculate the total duration when there is at least one GPU request
+  // by SurfaceFlinger that is still waiting for GPU to complete the
+  // request.
+  // This also equals to the total duration of
+  // "waiting for GPU completion <fence_num>" in SurfaceFlinger.
+  optional double total_non_empty_gpu_waiting_dur_ms = 9;
+}
+
+// End of protos/perfetto/metrics/android/surfaceflinger.proto
+
+// Begin of protos/perfetto/metrics/android/sysui_cuj_metrics.proto
+
+// Metric that stores frame information and potential jank root causes
+// for a single Android system UI interaction/user journey.
+message AndroidSysUiCujMetrics {
+  // A list of all frames within the SysUi user journey.
+  repeated Frame frames = 1;
+
+  optional string cuj_name = 2;
+  optional int64 cuj_start = 3;
+  optional int64 cuj_dur = 4;
+
+  // Details about the process (uid, version, etc)
+  optional AndroidProcessMetadata process = 5;
+
+  message Frame {
+    // Index of the frame within the single user journey.
+    optional int64 number = 1;
+    optional int64 vsync = 5;
+    optional int64 ts = 2;
+    optional int64 dur = 3;
+
+    // A list of identified potential causes for jank.
+    // Optional.
+    repeated string jank_cause = 4;
+  }
+}
+
+// End of protos/perfetto/metrics/android/sysui_cuj_metrics.proto
+
+// Begin of protos/perfetto/metrics/android/task_names.proto
+
+message AndroidTaskNames {
+  message Process {
+    optional int64 pid = 1;
+
+    // Process name.
+    optional string process_name = 2;
+
+    // Names of all threads for this process.
+    repeated string thread_name = 3;
+
+    // User id under which this process runs.
+    optional int64 uid = 4;
+
+    // Packages matching the process uid.
+    repeated string uid_package_name = 5;
+  }
+
+  repeated Process process = 1;
+}
+
+// End of protos/perfetto/metrics/android/task_names.proto
+
+// Begin of protos/perfetto/metrics/android/trace_quality.proto
+
+// Metric which checks the data in the trace processor tables is "reasonble"
+// (i.e. we would expect to see it from a real device).
+//
+// This is useful to reject traces which may be valid (so no stat would be
+// recorded) but a human would find the trace nonsensical.
+message AndroidTraceQualityMetric {
+  message Failure {
+    // The name of the failed check.
+    optional string name = 1;
+  }
+  repeated Failure failures = 1;
+}
+// End of protos/perfetto/metrics/android/trace_quality.proto
+
+// Begin of protos/perfetto/metrics/android/unsymbolized_frames.proto
+
+message UnsymbolizedFrames {
+  message Frame {
+    optional string module = 1;
+    optional string build_id = 2;
+    optional int64 address = 3;
+
+    // In some cases (Chrome/Webview) the ID that should be used to query
+    // symbols in Google's internal tera-scale symbolization service is !=
+    // `build_id` and requires some mangling.
+    // This field is == 'build_id` for non-chromium cases, and is the breakpad
+    // module ID (with lowercase hex digics) for chromium cases.
+    optional string google_lookup_id = 4;
+  }
+
+  repeated Frame frames = 1;
+}
+
+// End of protos/perfetto/metrics/android/unsymbolized_frames.proto
+
+// Begin of protos/perfetto/metrics/metrics.proto
+
+// Trace processor metadata
+message TraceMetadata {
+  reserved 1;
+  optional int64 trace_duration_ns = 2;
+  optional string trace_uuid = 3;
+  optional string android_build_fingerprint = 4;
+  optional int64 statsd_triggering_subscription_id = 5;
+  optional int64 trace_size_bytes = 6;
+  repeated string trace_trigger = 7;
+  optional string unique_session_name = 8;
+  optional string trace_config_pbtxt = 9;
+  optional int64 sched_duration_ns = 10;
+}
+
+// Stats counters for the trace.
+// Defined in src/trace_processor/storage/stats.h
+message TraceAnalysisStats {
+  enum Severity {
+    SEVERITY_UNKNOWN = 0;
+    SEVERITY_INFO = 1;
+    SEVERITY_DATA_LOSS = 2;
+    SEVERITY_ERROR = 3;
+  }
+
+  enum Source {
+    SOURCE_UNKNOWN = 0;
+    SOURCE_TRACE = 1;
+    SOURCE_ANALYSIS = 2;
+  }
+
+  message Stat {
+    optional string name = 1;
+    optional uint32 idx = 2;
+    optional Severity severity = 3;
+    optional Source source = 4;
+
+    optional int64 count = 5;
+  }
+
+  repeated Stat stat = 1;
+}
+
+// Root message for all Perfetto-based metrics.
+//
+// Next id: 49
+message TraceMetrics {
+  reserved 4, 10, 13, 14, 16, 19;
+
+  // Battery counters metric on Android.
+  optional AndroidBatteryMetric android_batt = 5;
+
+  // CPU usage per trace, process and thread.
+  optional AndroidCpuMetric android_cpu = 6;
+
+  // Memory metrics on Android (owned by the Android Telemetry team).
+  optional AndroidMemoryMetric android_mem = 1;
+
+  // Memory metrics on Android in unaggregated form. (owned by the Android
+  // Telemetry team).
+  // Note: this generates a lot of data so should not be requested unless it
+  // is clear that this data is necessary.
+  optional AndroidMemoryUnaggregatedMetric android_mem_unagg = 11;
+
+  // Package list.
+  optional AndroidPackageList android_package_list = 12;
+
+  // ion buffer memory metrics.
+  optional AndroidIonMetric android_ion = 9;
+
+  // fastrpc subsystem memory metrics.
+  optional AndroidFastrpcMetric android_fastrpc = 31;
+
+  // Statistics about low memory kills.
+  optional AndroidLmkMetric android_lmk = 8;
+
+  // Power Rails metrics on Android.
+  optional AndroidPowerRails android_powrails = 7;
+
+  // Startup metrics on Android (owned by the Android Telemetry team).
+  optional AndroidStartupMetric android_startup = 2;
+
+  // Trace metadata (applicable to all traces).
+  optional TraceMetadata trace_metadata = 3;
+
+  // Trace stats (applicable to all traces).
+  optional TraceAnalysisStats trace_stats = 33;
+
+  // Returns stack frames missing symbols.
+  optional UnsymbolizedFrames unsymbolized_frames = 15;
+
+  // If the trace contains a heap graph, output allocation statistics.
+  optional JavaHeapStats java_heap_stats = 17;
+
+  // If the trace contains a heap graph, output histogram.
+  optional JavaHeapHistogram java_heap_histogram = 21;
+
+  // Metrics used to find potential culprits of low-memory kills.
+  optional AndroidLmkReasonMetric android_lmk_reason = 18;
+
+  optional AndroidHwuiMetric android_hwui_metric = 20;
+
+  optional AndroidDisplayMetrics display_metrics = 22;
+
+  optional AndroidTaskNames android_task_names = 23;
+
+  // Deprecated was AndroidThreadTimeInStateMetric
+  reserved 24;
+
+  // Metric associated with surfaceflinger.
+  optional AndroidSurfaceflingerMetric android_surfaceflinger = 25;
+
+  // GPU metrics on Android.
+  optional AndroidGpuMetric android_gpu = 26;
+
+  // Frame timing and jank root causes for system UI interactions.
+  optional AndroidSysUiCujMetrics android_sysui_cuj = 27;
+
+  // Interaction and frame timings for CUJs (important UI transitions).
+  optional AndroidJankCujMetric android_jank_cuj = 48;
+
+  // Metric associated with hwcomposer.
+  optional AndroidHwcomposerMetrics android_hwcomposer = 28;
+
+  // Deprecated was AndroidJankMetrics;
+  reserved 29;
+
+  // G2D metrics.
+  optional G2dMetrics g2d = 30;
+
+  // Dmabuf heap metrics.
+  optional AndroidDmaHeapMetric android_dma_heap = 32;
+
+  // Metric to verify the quality of the trace.
+  optional AndroidTraceQualityMetric android_trace_quality = 34;
+
+  // Profiler smaps
+  optional ProfilerSmaps profiler_smaps = 35;
+
+  // Multiuser - metrics for switching users.
+  optional AndroidMultiuserMetric android_multiuser = 36;
+
+  // Metrics related to simpleperf tool
+  optional AndroidSimpleperfMetric android_simpleperf = 37;
+
+  // Metrics for the Camera team.
+  optional AndroidCameraMetric android_camera = 38;
+
+  // Metrics for dynamic voltage and frequency scaling.
+  optional AndroidDvfsMetric android_dvfs = 39;
+
+  // Metrics for network performance.
+  optional AndroidNetworkMetric android_netperf = 40;
+
+  // Metrics for the Camera team.
+  // Note: this generates a lot of data so should not be requested unless it
+  // is clear that this data is necessary.
+  optional AndroidCameraUnaggregatedMetric android_camera_unagg = 41;
+
+  // Metrics for RT runtime.
+  optional AndroidRtRuntimeMetric android_rt_runtime = 42;
+
+  // Metrics for IRQ runtime.
+  optional AndroidIrqRuntimeMetric android_irq_runtime = 43;
+
+  // Metrics for the Trusty team.
+  optional AndroidTrustyWorkqueues android_trusty_workqueues = 44;
+
+  // Summary of other concurrent trace recording.
+  optional AndroidOtherTracesMetric android_other_traces = 45;
+
+  // Per-process Binder transaction metrics.
+  optional AndroidBinderMetric android_binder = 46;
+
+  // Metrics for app deadline missed.
+  optional AndroidFrameTimelineMetric android_frame_timeline_metric = 47;
+
+  // Demo extensions.
+  extensions 450 to 499;
+
+  // Vendor extensions.
+  extensions 500 to 1000;
+
+  // Chrome metrics.
+  extensions 1001 to 2000;
+}
+
+// End of protos/perfetto/metrics/metrics.proto
diff --git a/benchmark/benchmark-macro/src/main/proto/trace_processor.proto b/benchmark/benchmark-macro/src/main/proto/trace_processor.proto
new file mode 100644
index 0000000..774d135
--- /dev/null
+++ b/benchmark/benchmark-macro/src/main/proto/trace_processor.proto
@@ -0,0 +1,273 @@
+/*
+ * Copyright (C) 2018 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+syntax = "proto2";
+
+package perfetto.protos;
+
+import "descriptor.proto";
+
+// This file defines the schema for {,un}marshalling arguments and return values
+// when interfacing to the trace processor binary interface.
+
+// The Trace Processor can be used in three modes:
+// 1. Fully native from C++ or directly using trace_processor_shell.
+//    In this case, this file isn't really relevant because no binary
+//    marshalling is involved. Look at include/trace_processor/trace_processor.h
+//    for the public C++ API definition.
+// 2. Using WASM within the HTML ui. In this case these messages are used to
+//    {,un}marshall calls made through the JS<>WASM interop in
+//    src/trace_processor/rpc/wasm_bridge.cc .
+// 3. Using the HTTP+RPC interface, by running trace_processor_shell -D.
+//    In this case these messages are used to {,un}marshall HTTP requests and
+//    response made through src/trace_processor/rpc/httpd.cc .
+
+enum TraceProcessorApiVersion {
+  // This variable has been introduced in v15 and is used to deal with API
+  // mismatches between UI and trace_processor_shell --httpd. Increment this
+  // every time a new feature that the UI depends on is being introduced (e.g.
+  // new tables, new SQL operators, metrics that are required by the UI).
+  // See also StatusResult.api_version (below).
+  TRACE_PROCESSOR_CURRENT_API_VERSION = 5;
+}
+
+// At lowest level, the wire-format of the RPC procol is a linear sequence of
+// TraceProcessorRpc messages on each side of the byte pipe
+// Each message is prefixed by a tag (field = 1, type = length delimited) and a
+// varint encoding its size (this is so the whole stream can also be read /
+// written as if it was a repeated field of TraceProcessorRpcStream).
+
+message TraceProcessorRpcStream {
+  repeated TraceProcessorRpc msg = 1;
+}
+
+message TraceProcessorRpc {
+  // A monotonic counter used only for debugging purposes, to detect if the
+  // underlying stream is missing or duping data. The counter starts at 0 on
+  // each side of the pipe and is incremented on each message.
+  // Do NOT expect that a response has the same |seq| of its corresponding
+  // request: some requests (e.g., a query returning many rows) can yield more
+  // than one response message, bringing the tx and rq seq our of sync.
+  optional int64 seq = 1;
+
+  // This is returned when some unrecoverable error has been detected by the
+  // peer. The typical case is TraceProcessor detecting that the |seq| sequence
+  // is broken (e.g. when having two tabs open with the same --httpd instance).
+  optional string fatal_error = 5;
+
+  enum TraceProcessorMethod {
+    TPM_UNSPECIFIED = 0;
+    TPM_APPEND_TRACE_DATA = 1;
+    TPM_FINALIZE_TRACE_DATA = 2;
+    TPM_QUERY_STREAMING = 3;
+    // Previously: TPM_QUERY_RAW_DEPRECATED
+    reserved 4;
+    reserved "TPM_QUERY_RAW_DEPRECATED";
+    TPM_COMPUTE_METRIC = 5;
+    TPM_GET_METRIC_DESCRIPTORS = 6;
+    TPM_RESTORE_INITIAL_TABLES = 7;
+    TPM_ENABLE_METATRACE = 8;
+    TPM_DISABLE_AND_READ_METATRACE = 9;
+    TPM_GET_STATUS = 10;
+  }
+
+  oneof type {
+    // Client -> TraceProcessor requests.
+    TraceProcessorMethod request = 2;
+
+    // TraceProcessor -> Client responses.
+    TraceProcessorMethod response = 3;
+
+    // This is sent back instead of filling |response| when the client sends a
+    // |request| which is not known by the TraceProcessor service. This can
+    // happen when the client is newer than the service.
+    TraceProcessorMethod invalid_request = 4;
+  }
+
+  // Request/Response arguments.
+  // Not all requests / responses require an argument.
+
+  oneof args {
+    // TraceProcessorMethod request args.
+
+    // For TPM_APPEND_TRACE_DATA.
+    bytes append_trace_data = 101;
+    // For TPM_QUERY_STREAMING.
+    QueryArgs query_args = 103;
+    // For TPM_COMPUTE_METRIC.
+    ComputeMetricArgs compute_metric_args = 105;
+
+    // TraceProcessorMethod response args.
+    // For TPM_APPEND_TRACE_DATA.
+    AppendTraceDataResult append_result = 201;
+    // For TPM_QUERY_STREAMING.
+    QueryResult query_result = 203;
+    // For TPM_COMPUTE_METRIC.
+    ComputeMetricResult metric_result = 205;
+    // For TPM_GET_METRIC_DESCRIPTORS.
+    DescriptorSet metric_descriptors = 206;
+    // For TPM_DISABLE_AND_READ_METATRACE.
+    DisableAndReadMetatraceResult metatrace = 209;
+    // For TPM_GET_STATUS.
+    StatusResult status = 210;
+  }
+
+  // Previously: RawQueryArgs for TPM_QUERY_RAW_DEPRECATED
+  reserved 104;
+  // Previously: RawQueryResult for TPM_QUERY_RAW_DEPRECATED
+  reserved 204;
+}
+
+message AppendTraceDataResult {
+  optional int64 total_bytes_parsed = 1;
+  optional string error = 2;
+}
+
+message QueryArgs {
+  optional string sql_query = 1;
+
+  // Was time_queued_ns
+  reserved 2;
+}
+
+// Output for the /query endpoint.
+// Returns a query result set, grouping cells into batches. Batching allows a
+// more efficient encoding of results, at the same time allowing to return
+// O(M) results in a pipelined fashion, without full-memory buffering.
+// Batches are split when either a large number of cells (~thousands) is reached
+// or the string/blob payload becomes too large (~hundreds of KB).
+// Data is batched in cells, scanning results by row -> column. e.g. if a query
+// returns 3 columns and 2 rows, the cells will be emitted in this order:
+// R0C0, R0C1, R0C2, R1C0, R1C1, R1C2.
+message QueryResult {
+  // This determines the number and names of columns.
+  repeated string column_names = 1;
+
+  // If non-emty the query returned an error. Note that some cells might still
+  // be present, if the error happened while iterating.
+  optional string error = 2;
+
+  // A batch contains an array of cell headers, stating the type of each cell.
+  // The payload of each cell is stored in the corresponding xxx_cells field
+  // below (unless the cell is NULL).
+  // So if |cells| contains: [VARINT, FLOAT64, VARINT, STRING], the results will
+  // be available as:
+  // [varint_cells[0], float64_cells[0], varint_cells[1], string_cells[0]].
+  message CellsBatch {
+    enum CellType {
+      CELL_INVALID = 0;
+      CELL_NULL = 1;
+      CELL_VARINT = 2;
+      CELL_FLOAT64 = 3;
+      CELL_STRING = 4;
+      CELL_BLOB = 5;
+    }
+    repeated CellType cells = 1 [packed = true];
+
+    repeated int64 varint_cells = 2 [packed = true];
+    repeated double float64_cells = 3 [packed = true];
+    repeated bytes blob_cells = 4;
+
+    // The string cells are concatenated in a single field. Each cell is
+    // NUL-terminated. This is because JS incurs into a non-negligible overhead
+    // when decoding strings and one decode + split('\0') is measurably faster
+    // than decoding N strings. See goto.google.com/postmessage-benchmark .
+    optional string string_cells = 5;
+
+    // If true this is the last batch for the query result.
+    optional bool is_last_batch = 6;
+
+    // Padding field. Used only to re-align and fill gaps in the binary format.
+    reserved 7;
+  }
+  repeated CellsBatch batch = 3;
+
+  // The number of statements in the provided SQL.
+  optional uint32 statement_count = 4;
+
+  // The number of statements which produced output rows in the provided SQL.
+  optional uint32 statement_with_output_count = 5;
+}
+
+// Input for the /status endpoint.
+message StatusArgs {}
+
+// Output for the /status endpoint.
+message StatusResult {
+  // If present and not empty, a trace is already loaded already. This happens
+  // when using the HTTP+RPC mode nad passing a trace file to the shell, via
+  // trace_processor_shell -D trace_file.pftrace .
+  optional string loaded_trace_name = 1;
+
+  // Typically something like "v11.0.123", but could be just "v11" or "unknown",
+  // for binaries built from Bazel or other build configurations. This is for
+  // human presentation only, don't attempt to parse and reason on it.
+  optional string human_readable_version = 2;
+
+  // The API version is incremented every time a change that the UI depends
+  // on is introduced (e.g. adding a new table that the UI queries).
+  optional int32 api_version = 3;
+}
+
+// Input for the /compute_metric endpoint.
+message ComputeMetricArgs {
+  enum ResultFormat {
+    BINARY_PROTOBUF = 0;
+    TEXTPROTO = 1;
+  }
+  repeated string metric_names = 1;
+  optional ResultFormat format = 2;
+}
+
+// Output for the /compute_metric endpoint.
+message ComputeMetricResult {
+  oneof result {
+    // This is meant to contain a perfetto.protos.TraceMetrics. We're using
+    // bytes instead of the actual type because we do not want to generate
+    // protozero code for the metrics protos. We always encode/decode metrics
+    // using a reflection based mechanism that does not require the compiled C++
+    // code. This allows us to read in new protos at runtime.
+    bytes metrics = 1;
+
+    // A perfetto.protos.TraceMetrics formatted as prototext.
+    string metrics_as_prototext = 3;
+  }
+
+  optional string error = 2;
+}
+
+// Input for the /enable_metatrace endpoint.
+message EnableMetatraceArgs {}
+
+// Output for the /enable_metatrace endpoint.
+message EnableMetatraceResult {}
+
+// Input for the /disable_and_read_metatrace endpoint.
+message DisableAndReadMetatraceArgs {}
+
+// Output for the /disable_and_read_metatrace endpoint.
+message DisableAndReadMetatraceResult {
+  // Bytes of perfetto.protos.Trace message. Stored as bytes
+  // to avoid adding a dependency on trace.proto.
+  optional bytes metatrace = 1;
+  optional string error = 2;
+}
+
+// Convenience wrapper for multiple descriptors, similar to FileDescriptorSet
+// in descriptor.proto.
+message DescriptorSet {
+  repeated DescriptorProto descriptors = 1;
+}
diff --git a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/PerfettoTraceProcessorBenchmark.kt b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/PerfettoTraceProcessorBenchmark.kt
index 336899a..4f38392 100644
--- a/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/PerfettoTraceProcessorBenchmark.kt
+++ b/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/benchmark/integration/macrobenchmark/PerfettoTraceProcessorBenchmark.kt
@@ -70,23 +70,32 @@
         runProcessQuery()
     }
 
-    private fun benchmarkWithTrace(block: () -> (Unit)) = benchmarkRule.measureRepeated(
-        packageName = PACKAGE_NAME,
-        metrics = listOf(TraceSectionMetric("perfettoTraceProcessor")),
-        iterations = 5,
-    ) {
-        trace("perfettoTraceProcessor", block)
+    private fun benchmarkWithTrace(block: PerfettoTraceProcessor.() -> Unit) =
+        benchmarkRule.measureRepeated(
+            packageName = PACKAGE_NAME,
+            metrics = listOf(TraceSectionMetric("PerfettoTraceProcessorBenchmark")),
+            iterations = 5,
+        ) {
+            trace("PerfettoTraceProcessorBenchmark") {
+
+                // This will run perfetto trace processor http server on the specified port 10555.
+                // Note that this is an arbitrary number and the default cannot be used because
+                // the macrobenchmark instance of the server is running at the same time.
+                PerfettoTraceProcessor.runServer(
+                    httpServerPort = 10555,
+                    absoluteTracePath = traceFile.absolutePath,
+                    block = block
+                )
+            }
+        }
+
+    private fun PerfettoTraceProcessor.runComputeStartupMetric() {
+        getTraceMetrics("android_startup")
     }
 
-    private fun runComputeStartupMetric() {
-        PerfettoTraceProcessor.getJsonMetrics(
-            traceFile.absolutePath, "android_startup"
-        )
-    }
-
-    private fun runSlicesQuery() {
-        PerfettoTraceProcessor.rawQuery(
-            traceFile.absolutePath, """
+    private fun PerfettoTraceProcessor.runSlicesQuery() {
+        rawQuery(
+            """
                 SELECT slice.name, slice.ts, slice.dur, thread_track.id, thread_track.name
                 FROM slice
                 INNER JOIN thread_track on slice.track_id = thread_track.id
@@ -96,9 +105,9 @@
         )
     }
 
-    private fun runCounterQuery() {
-        PerfettoTraceProcessor.rawQuery(
-            traceFile.absolutePath, """
+    private fun PerfettoTraceProcessor.runCounterQuery() {
+        rawQuery(
+            """
                 SELECT track.name, counter.value, counter.ts
                 FROM track
                 JOIN counter ON track.id = counter.track_id
@@ -106,9 +115,9 @@
         )
     }
 
-    private fun runProcessQuery() {
-        PerfettoTraceProcessor.rawQuery(
-            traceFile.absolutePath, """
+    private fun PerfettoTraceProcessor.runProcessQuery() {
+        rawQuery(
+            """
                 SELECT upid
                 FROM counter
                 JOIN process_counter_track ON process_counter_track.id = counter.track_id
diff --git a/bluetooth/bluetooth-core/src/main/java/androidx/bluetooth/core/AdvertiseData.kt b/bluetooth/bluetooth-core/src/main/java/androidx/bluetooth/core/AdvertiseData.kt
index a948848..799d2d97 100644
--- a/bluetooth/bluetooth-core/src/main/java/androidx/bluetooth/core/AdvertiseData.kt
+++ b/bluetooth/bluetooth-core/src/main/java/androidx/bluetooth/core/AdvertiseData.kt
@@ -17,6 +17,7 @@
 package androidx.bluetooth.core
 
 import android.bluetooth.le.AdvertiseData as FwkAdvertiseData
+import android.annotation.SuppressLint
 import android.os.Build
 import android.os.Bundle
 import android.os.ParcelUuid
@@ -25,42 +26,88 @@
 
 /**
  * TODO: Add docs
- * TODO: Add functions in SDK 33
+ * TODO: Add core's TransportDiscoveryData and use it to support SDK 33
  *
  * Advertise data packet container for Bluetooth LE advertising. This represents the data to be
  * advertised as well as the scan response data for active scans.
  * @hide
  */
+
 class AdvertiseData internal constructor(
     internal val impl: AdvertiseDataImpl
-) : AdvertiseDataImpl by impl {
+) : Bundleable {
+
     companion object {
+        internal const val FIELD_FWK_ADVERTISE_DATA = 0
+        internal const val FIELD_SERVICE_SOLICITATION_UUIDS = 1
+
         val CREATOR: Bundleable.Creator<AdvertiseData> =
-            if (Build.VERSION.SDK_INT < 31) {
-                AdvertiseDataImplApi21.CREATOR
-            } else {
-                AdvertiseDataImplApi31.CREATOR
+            object : Bundleable.Creator<AdvertiseData> {
+                override fun fromBundle(bundle: Bundle): AdvertiseData {
+                    return bundle.getAdvertiseData()
+                }
             }
 
-        internal fun createAdvertiseDataImpl(args: AdvertiseDataArgs): AdvertiseDataImpl {
-            return if (Build.VERSION.SDK_INT < 31) {
-                AdvertiseDataImplApi21(
-                    AdvertiseDataImplApi21.getFwkAdvertiseDataBuilder(args).build(),
-                    args.serviceSolicitationUuids
-                )
-            } else {
-                AdvertiseDataImplApi31(
-                    AdvertiseDataImplApi31.getFwkAdvertiseDataBuilder(args).build()
-                )
+        internal fun keyForField(field: Int): String {
+            return field.toString(Character.MAX_RADIX)
+        }
+
+        internal fun Bundle.putAdvertiseData(data: AdvertiseData) {
+            this.putParcelable(
+                keyForField(FIELD_FWK_ADVERTISE_DATA), data.impl.fwkInstance
+            )
+            if (Build.VERSION.SDK_INT < 31) {
+                if (data.serviceSolicitationUuids != null) {
+                    this.putParcelableArrayList(
+                        keyForField(FIELD_SERVICE_SOLICITATION_UUIDS),
+                        ArrayList(data.serviceSolicitationUuids!!)
+                    )
+                }
             }
         }
+
+        internal fun Bundle.getAdvertiseData(): AdvertiseData {
+            val fwkAdvertiseData =
+                Utils.getParcelableFromBundle(
+                    this,
+                    keyForField(FIELD_FWK_ADVERTISE_DATA),
+                    android.bluetooth.le.AdvertiseData::class.java
+                ) ?: throw IllegalArgumentException(
+                    "Bundle doesn't include a framework advertise data"
+                )
+
+            val args = AdvertiseDataArgs()
+
+            if (Build.VERSION.SDK_INT < 31) {
+                args.serviceSolicitationUuids =
+                    Utils.getParcelableArrayListFromBundle(
+                        this,
+                        keyForField(FIELD_SERVICE_SOLICITATION_UUIDS),
+                        ParcelUuid::class.java
+                    ).toMutableList()
+            }
+            return AdvertiseData(fwkAdvertiseData, args)
+        }
     }
 
-    internal constructor(fwkAdvertiseData: FwkAdvertiseData) : this(
-        if (Build.VERSION.SDK_INT < 31) {
-            AdvertiseDataImplApi21(fwkAdvertiseData)
+    val serviceUuids: MutableList<ParcelUuid>?
+        get() = impl.serviceUuids
+    val serviceSolicitationUuids: MutableList<ParcelUuid>?
+        get() = impl.serviceSolicitationUuids
+    val manufacturerSpecificData: SparseArray<ByteArray>?
+        get() = impl.manufacturerSpecificData
+    val serviceData: MutableMap<ParcelUuid, ByteArray>?
+        get() = impl.serviceData
+    val includeTxPowerLevel: Boolean
+        get() = impl.includeTxPowerLevel
+    val includeDeviceName: Boolean
+        get() = impl.includeDeviceName
+
+    internal constructor(fwkInstance: FwkAdvertiseData) : this(
+        if (Build.VERSION.SDK_INT >= 31) {
+            AdvertiseDataImplApi31(fwkInstance)
         } else {
-            AdvertiseDataImplApi31(fwkAdvertiseData)
+            AdvertiseDataImplApi21(fwkInstance)
         }
     )
 
@@ -71,27 +118,68 @@
         serviceData: MutableMap<ParcelUuid, ByteArray>? = null,
         includeTxPowerLevel: Boolean = false,
         includeDeviceName: Boolean = false
-    ) : this(
-        createAdvertiseDataImpl(AdvertiseDataArgs(
+    ) : this(AdvertiseDataArgs(
             serviceUuids,
             serviceSolicitationUuids,
             manufacturerSpecificData,
             serviceData,
             includeTxPowerLevel,
             includeDeviceName
-        )))
+        ))
+
+    internal constructor(args: AdvertiseDataArgs) : this(args.toFwkAdvertiseData(), args)
+
+    internal constructor(fwkInstance: FwkAdvertiseData, args: AdvertiseDataArgs) : this(
+        if (Build.VERSION.SDK_INT >= 31) {
+            AdvertiseDataImplApi31(fwkInstance)
+        } else {
+            AdvertiseDataImplApi21(fwkInstance, args)
+        }
+    )
+
+    override fun toBundle(): Bundle {
+        val bundle = Bundle()
+        bundle.putAdvertiseData(this)
+        return bundle
+    }
 }
 
 internal data class AdvertiseDataArgs(
     val serviceUuids: MutableList<ParcelUuid>? = null,
-    val serviceSolicitationUuids: MutableList<ParcelUuid>? = null,
+    var serviceSolicitationUuids: MutableList<ParcelUuid>? = null,
     val manufacturerSpecificData: SparseArray<ByteArray>? = null,
     val serviceData: MutableMap<ParcelUuid, ByteArray>? = null,
     var includeTxPowerLevel: Boolean = false,
     var includeDeviceName: Boolean = false,
-)
+) {
+    // "ClassVerificationFailure for FwkAdvertiseData.Builder
+    @SuppressLint("ClassVerificationFailure")
+    internal fun toFwkAdvertiseData(): FwkAdvertiseData {
+        val builder = FwkAdvertiseData.Builder()
+            .setIncludeTxPowerLevel(includeTxPowerLevel)
+            .setIncludeDeviceName(includeDeviceName)
 
-internal interface AdvertiseDataImpl : Bundleable {
+        serviceUuids?.forEach { builder.addServiceUuid(it) }
+
+        if (manufacturerSpecificData != null) {
+            with(manufacturerSpecificData) {
+                for (index in 0 until size()) {
+                    builder.addManufacturerData(keyAt(index), get(keyAt(index)))
+                }
+            }
+        }
+
+        serviceData?.forEach { builder.addServiceData(it.key, it.value) }
+
+        if (Build.VERSION.SDK_INT >= 31 && serviceSolicitationUuids != null) {
+            serviceSolicitationUuids?.forEach { builder.addServiceSolicitationUuid(it) }
+        }
+
+        return builder.build()
+    }
+}
+
+internal interface AdvertiseDataImpl {
     val serviceUuids: MutableList<ParcelUuid>?
     val serviceSolicitationUuids: MutableList<ParcelUuid>?
     val manufacturerSpecificData: SparseArray<ByteArray>?
@@ -105,15 +193,6 @@
 internal abstract class AdvertiseDataFwkImplApi21 internal constructor(
     override val fwkInstance: FwkAdvertiseData
 ) : AdvertiseDataImpl {
-    companion object {
-        internal const val FIELD_FWK_ADVERTISE_DATA = 0
-        internal const val FIELD_SERVICE_SOLICITATION_UUIDS = 1
-
-        internal fun keyForField(field: Int): String {
-            return field.toString(Character.MAX_RADIX)
-        }
-    }
-
     override val serviceUuids: MutableList<ParcelUuid>?
         get() = fwkInstance.serviceUuids
     override val manufacturerSpecificData: SparseArray<ByteArray>?
@@ -130,64 +209,8 @@
     fwkInstance: FwkAdvertiseData,
     override val serviceSolicitationUuids: MutableList<ParcelUuid>? = null
 ) : AdvertiseDataFwkImplApi21(fwkInstance) {
-    companion object {
-        val CREATOR: Bundleable.Creator<AdvertiseData> =
-            object : Bundleable.Creator<AdvertiseData> {
-                override fun fromBundle(bundle: Bundle): AdvertiseData {
-                    val fwkAdvertiseData =
-                        Utils.getParcelableFromBundle(
-                            bundle,
-                            keyForField(FIELD_FWK_ADVERTISE_DATA),
-                            FwkAdvertiseData::class.java
-                        ) ?: throw IllegalArgumentException(
-                            "Bundle doesn't include a framework advertise data"
-                        )
-                    val serviceSolicitationUuids =
-                        Utils.getParcelableArrayListFromBundle(
-                            bundle,
-                            keyForField(FIELD_SERVICE_SOLICITATION_UUIDS),
-                            ParcelUuid::class.java
-                        )
-
-                    return AdvertiseData(AdvertiseDataImplApi21(
-                        fwkAdvertiseData,
-                        serviceSolicitationUuids.toMutableList())
-                    )
-                }
-            }
-
-        internal fun getFwkAdvertiseDataBuilder(args: AdvertiseDataArgs): FwkAdvertiseData.Builder {
-            val builder = FwkAdvertiseData.Builder()
-                .setIncludeTxPowerLevel(args.includeTxPowerLevel)
-                .setIncludeDeviceName(args.includeDeviceName)
-
-            args.serviceUuids?.forEach { builder.addServiceUuid(it) }
-
-            if (args.manufacturerSpecificData != null) {
-                with(args.manufacturerSpecificData) {
-                    for (index in 0 until size()) {
-                        builder.addManufacturerData(keyAt(index), get(keyAt(index)))
-                    }
-                }
-            }
-
-            args.serviceData?.forEach { builder.addServiceData(it.key, it.value) }
-
-            return builder
-        }
-    }
-
-    override fun toBundle(): Bundle {
-        val bundle = Bundle()
-        bundle.putParcelable(keyForField(FIELD_FWK_ADVERTISE_DATA), fwkInstance)
-        if (serviceSolicitationUuids != null) {
-            bundle.putParcelableArrayList(
-                keyForField(FIELD_SERVICE_SOLICITATION_UUIDS),
-                ArrayList(serviceSolicitationUuids)
-            )
-        }
-        return bundle
-    }
+    internal constructor(fwkInstance: FwkAdvertiseData, args: AdvertiseDataArgs) : this(
+        fwkInstance, args.serviceSolicitationUuids)
 }
 
 @RequiresApi(Build.VERSION_CODES.S)
@@ -200,35 +223,5 @@
 
 @RequiresApi(Build.VERSION_CODES.S)
 internal class AdvertiseDataImplApi31 internal constructor(
-    fwkInstance: FwkAdvertiseData
-) : AdvertiseDataFwkImplApi31(fwkInstance) {
-    companion object {
-        val CREATOR: Bundleable.Creator<AdvertiseData> =
-            object : Bundleable.Creator<AdvertiseData> {
-                override fun fromBundle(bundle: Bundle): AdvertiseData {
-                    val fwkAdvertiseData =
-                        Utils.getParcelableFromBundle(
-                            bundle,
-                            keyForField(FIELD_FWK_ADVERTISE_DATA),
-                            android.bluetooth.le.AdvertiseData::class.java
-                        ) ?: throw IllegalArgumentException(
-                            "Bundle doesn't include a framework advertise data"
-                        )
-                    return AdvertiseData(fwkAdvertiseData)
-                }
-            }
-
-        internal fun getFwkAdvertiseDataBuilder(args: AdvertiseDataArgs): FwkAdvertiseData.Builder {
-            val builder = AdvertiseDataImplApi21.getFwkAdvertiseDataBuilder(args)
-
-            args.serviceSolicitationUuids?.forEach { builder.addServiceSolicitationUuid(it) }
-            return builder
-        }
-    }
-
-    override fun toBundle(): Bundle {
-        val bundle = Bundle()
-        bundle.putParcelable(keyForField(FIELD_FWK_ADVERTISE_DATA), fwkInstance)
-        return bundle
-    }
-}
\ No newline at end of file
+    fwkInstance: FwkAdvertiseData,
+) : AdvertiseDataFwkImplApi31(fwkInstance)
\ No newline at end of file
diff --git a/browser/browser/api/current.txt b/browser/browser/api/current.txt
index 934bc45..993cb9b 100644
--- a/browser/browser/api/current.txt
+++ b/browser/browser/api/current.txt
@@ -98,11 +98,18 @@
   }
 
   public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
     method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
     method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
     method public void launchUrl(android.content.Context, android.net.Uri);
     method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
     method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
     field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
@@ -110,6 +117,7 @@
     field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
     field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
     field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_RESIZE_BEHAVIOR";
     field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
     field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
     field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
@@ -160,11 +168,14 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
@@ -175,6 +186,7 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
   }
 
diff --git a/browser/browser/api/public_plus_experimental_current.txt b/browser/browser/api/public_plus_experimental_current.txt
index 934bc45..993cb9b 100644
--- a/browser/browser/api/public_plus_experimental_current.txt
+++ b/browser/browser/api/public_plus_experimental_current.txt
@@ -98,11 +98,18 @@
   }
 
   public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
     method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
     method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
     method public void launchUrl(android.content.Context, android.net.Uri);
     method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
     method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
     field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
@@ -110,6 +117,7 @@
     field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
     field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
     field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_RESIZE_BEHAVIOR";
     field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
     field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
     field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
@@ -160,11 +168,14 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
@@ -175,6 +186,7 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
   }
 
diff --git a/browser/browser/api/restricted_current.txt b/browser/browser/api/restricted_current.txt
index 45da49f..a5f6dc9 100644
--- a/browser/browser/api/restricted_current.txt
+++ b/browser/browser/api/restricted_current.txt
@@ -109,11 +109,18 @@
   }
 
   public final class CustomTabsIntent {
+    method public static int getActivityResizeBehavior(android.content.Intent);
+    method public static int getCloseButtonPosition(android.content.Intent);
     method public static androidx.browser.customtabs.CustomTabColorSchemeParams getColorSchemeParams(android.content.Intent, int);
+    method @Dimension(unit=androidx.annotation.Dimension.PX) public static int getInitialActivityHeightPx(android.content.Intent);
     method public static int getMaxToolbarItems();
+    method @Dimension(unit=androidx.annotation.Dimension.DP) public static int getToolbarCornerRadiusDp(android.content.Intent);
     method public void launchUrl(android.content.Context, android.net.Uri);
     method public static android.content.Intent setAlwaysUseBrowserUI(android.content.Intent?);
     method public static boolean shouldAlwaysUseBrowserUI(android.content.Intent);
+    field public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1; // 0x1
+    field public static final int ACTIVITY_HEIGHT_DEFAULT = 0; // 0x0
+    field public static final int ACTIVITY_HEIGHT_FIXED = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0; // 0x0
     field public static final int CLOSE_BUTTON_POSITION_END = 2; // 0x2
     field public static final int CLOSE_BUTTON_POSITION_START = 1; // 0x1
@@ -121,6 +128,7 @@
     field public static final int COLOR_SCHEME_LIGHT = 1; // 0x1
     field public static final int COLOR_SCHEME_SYSTEM = 0; // 0x0
     field public static final String EXTRA_ACTION_BUTTON_BUNDLE = "android.support.customtabs.extra.ACTION_BUTTON_BUNDLE";
+    field public static final String EXTRA_ACTIVITY_RESIZE_BEHAVIOR = "androidx.browser.customtabs.extra.ACTIVITY_RESIZE_BEHAVIOR";
     field public static final String EXTRA_CLOSE_BUTTON_ICON = "android.support.customtabs.extra.CLOSE_BUTTON_ICON";
     field public static final String EXTRA_CLOSE_BUTTON_POSITION = "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
     field public static final String EXTRA_COLOR_SCHEME = "androidx.browser.customtabs.extra.COLOR_SCHEME";
@@ -171,11 +179,14 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent, boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setActionButton(android.graphics.Bitmap, String, android.app.PendingIntent);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonIcon(android.graphics.Bitmap);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setCloseButtonPosition(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorScheme(int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setColorSchemeParams(int, androidx.browser.customtabs.CustomTabColorSchemeParams);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultColorSchemeParams(androidx.browser.customtabs.CustomTabColorSchemeParams);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setDefaultShareMenuItemEnabled(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setExitAnimations(android.content.Context, @AnimRes int, @AnimRes int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int, int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setInitialActivityHeightPx(@Dimension(unit=androidx.annotation.Dimension.PX) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setInstantAppsEnabled(boolean);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarColor(@ColorInt int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setNavigationBarDividerColor(@ColorInt int);
@@ -186,6 +197,7 @@
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setShowTitle(boolean);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setStartAnimations(android.content.Context, @AnimRes int, @AnimRes int);
     method @Deprecated public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarColor(@ColorInt int);
+    method public androidx.browser.customtabs.CustomTabsIntent.Builder setToolbarCornerRadiusDp(@Dimension(unit=androidx.annotation.Dimension.DP) int);
     method public androidx.browser.customtabs.CustomTabsIntent.Builder setUrlBarHidingEnabled(boolean);
   }
 
diff --git a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
index f4ce52a..1383108 100644
--- a/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
+++ b/browser/browser/src/main/java/androidx/browser/customtabs/CustomTabsIntent.java
@@ -16,6 +16,9 @@
 
 package androidx.browser.customtabs;
 
+import static androidx.annotation.Dimension.DP;
+import static androidx.annotation.Dimension.PX;
+
 import android.app.Activity;
 import android.app.PendingIntent;
 import android.content.Context;
@@ -31,6 +34,7 @@
 
 import androidx.annotation.AnimRes;
 import androidx.annotation.ColorInt;
+import androidx.annotation.Dimension;
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
@@ -342,6 +346,47 @@
             "androidx.browser.customtabs.extra.INITIAL_ACTIVITY_HEIGHT_PX";
 
     /**
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({ACTIVITY_HEIGHT_DEFAULT, ACTIVITY_HEIGHT_ADJUSTABLE, ACTIVITY_HEIGHT_FIXED})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface ActivityResizeBehavior {
+    }
+
+    /**
+     * Applies the default resize behavior for the Custom Tab Activity when it behaves as a
+     * bottom sheet.
+     */
+    public static final int ACTIVITY_HEIGHT_DEFAULT = 0;
+
+    /**
+     * The Custom Tab Activity, when it behaves as a bottom sheet, can be manually resized by the
+     * user.
+     */
+    public static final int ACTIVITY_HEIGHT_ADJUSTABLE = 1;
+
+    /**
+     * The Custom Tab Activity, when it behaves as a bottom sheet, cannot be manually resized by
+     * the user.
+     */
+    public static final int ACTIVITY_HEIGHT_FIXED = 2;
+
+    /**
+     * Maximum value for the ACTIVITY_HEIGHT_* configuration options. For validation purposes only.
+     */
+    private static final int ACTIVITY_HEIGHT_MAX = 2;
+
+    /**
+     * Extra that, if set in combination with
+     * {@link CustomTabsIntent#EXTRA_INITIAL_ACTIVITY_HEIGHT_PX}, defines the resize behavior of
+     * the Custom Tab Activity when it behaves as a bottom sheet.
+     * Default is {@link CustomTabsIntent#ACTIVITY_HEIGHT_DEFAULT}.
+     */
+    public static final String EXTRA_ACTIVITY_RESIZE_BEHAVIOR =
+            "androidx.browser.customtabs.extra.ACTIVITY_RESIZE_BEHAVIOR";
+
+    /**
      * Extra that sets the toolbar's top corner radii in dp. This will only have
      * effect if the custom tab is behaving as a bottom sheet. Currently, this is capped at 16dp.
      */
@@ -349,11 +394,13 @@
             "androidx.browser.customtabs.extra.TOOLBAR_CORNER_RADIUS_DP";
 
     /**
-     * Extra that specifies the position of the close button on the toolbar. Default is
-     * {@link #CLOSE_BUTTON_POSITION_DEFAULT}.
+     * @hide
      */
-    public static final String EXTRA_CLOSE_BUTTON_POSITION =
-            "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
+    @IntDef({CLOSE_BUTTON_POSITION_DEFAULT, CLOSE_BUTTON_POSITION_START, CLOSE_BUTTON_POSITION_END})
+    @Retention(RetentionPolicy.SOURCE)
+    public @interface CloseButtonPosition {
+    }
 
     /** Same as {@link #CLOSE_BUTTON_POSITION_START}. */
     public static final int CLOSE_BUTTON_POSITION_DEFAULT = 0;
@@ -365,6 +412,19 @@
     public static final int CLOSE_BUTTON_POSITION_END = 2;
 
     /**
+     * Maximum value for the CLOSE_BUTTON_POSITION_* configuration options. For validation purposes
+     * only.
+     */
+    private static final int CLOSE_BUTTON_POSITION_MAX = 2;
+
+    /**
+     * Extra that specifies the position of the close button on the toolbar. Default is
+     * {@link #CLOSE_BUTTON_POSITION_DEFAULT}.
+     */
+    public static final String EXTRA_CLOSE_BUTTON_POSITION =
+            "androidx.browser.customtabs.extra.CLOSE_BUTTON_POSITION";
+
+    /**
      * Extra that contains the color of the navigation bar divider.
      * See {@link Builder#setNavigationBarDividerColor}.
      */
@@ -388,6 +448,11 @@
     private static final int MAX_TOOLBAR_ITEMS = 5;
 
     /**
+     * The maximum toolbar corner radius in dp.
+     */
+    private static final int MAX_TOOLBAR_CORNER_RADIUS_DP = 16;
+
+    /**
      * An {@link Intent} used to start the Custom Tabs Activity.
      */
     @NonNull public final Intent intent;
@@ -900,6 +965,80 @@
         }
 
         /**
+         * Sets the Custom Tab Activity's initial height in pixels and the desired resize behavior.
+         * The Custom Tab will behave as a bottom sheet.
+         *
+         * @param initialHeightPx The Custom Tab Activity's initial height in pixels.
+         * @param activityResizeBehavior Desired height behavior.
+         * @see CustomTabsIntent#EXTRA_INITIAL_ACTIVITY_HEIGHT_PX
+         * @see CustomTabsIntent#EXTRA_ACTIVITY_RESIZE_BEHAVIOR
+         * @see CustomTabsIntent#ACTIVITY_HEIGHT_DEFAULT
+         * @see CustomTabsIntent#ACTIVITY_HEIGHT_ADJUSTABLE
+         * @see CustomTabsIntent#ACTIVITY_HEIGHT_FIXED
+         */
+        @NonNull
+        public Builder setInitialActivityHeightPx(@Dimension(unit = PX) int initialHeightPx,
+                @ActivityResizeBehavior int activityResizeBehavior) {
+            if (initialHeightPx <= 0) {
+                throw new IllegalArgumentException("Invalid value for the initialHeightPx "
+                        + "argument");
+            }
+            if (activityResizeBehavior < 0 || activityResizeBehavior > ACTIVITY_HEIGHT_MAX) {
+                throw new IllegalArgumentException("Invalid value for the activityResizeBehavior "
+                        + "argument");
+            }
+
+            mIntent.putExtra(EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, initialHeightPx);
+            mIntent.putExtra(EXTRA_ACTIVITY_RESIZE_BEHAVIOR, activityResizeBehavior);
+            return this;
+        }
+
+        /**
+         * Sets the Custom Tab Activity's initial height in pixels with default resize behavior.
+         * The Custom Tab will behave as a bottom sheet.
+         *
+         * @see CustomTabsIntent.Builder#setInitialActivityHeightPx(int, int)
+         */
+        @NonNull
+        public Builder setInitialActivityHeightPx(@Dimension(unit = PX) int initialHeightPx) {
+            return setInitialActivityHeightPx(initialHeightPx, ACTIVITY_HEIGHT_DEFAULT);
+        }
+
+        /**
+         * Sets the toolbar's top corner radii in dp.
+         *
+         * @param cornerRadiusDp The toolbar's top corner radii in dp.
+         * @see CustomTabsIntent#EXTRA_TOOLBAR_CORNER_RADIUS_DP
+         */
+        @NonNull
+        public Builder setToolbarCornerRadiusDp(@Dimension(unit = DP) int cornerRadiusDp) {
+            if (cornerRadiusDp < 0 || cornerRadiusDp > MAX_TOOLBAR_CORNER_RADIUS_DP) {
+                throw new IllegalArgumentException("Invalid value for the cornerRadiusDp argument");
+            }
+
+            mIntent.putExtra(EXTRA_TOOLBAR_CORNER_RADIUS_DP, cornerRadiusDp);
+            return this;
+        }
+
+        /**
+         * Sets the position of the close button.
+         *
+         * @param position The desired position.
+         * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_DEFAULT
+         * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_START
+         * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_END
+         */
+        @NonNull
+        public Builder setCloseButtonPosition(@CloseButtonPosition int position) {
+            if (position < 0 || position > CLOSE_BUTTON_POSITION_MAX) {
+                throw new IllegalArgumentException("Invalid value for the position argument");
+            }
+
+            mIntent.putExtra(EXTRA_CLOSE_BUTTON_POSITION, position);
+            return this;
+        }
+
+        /**
          * Combines all the options that have been set and returns a new {@link CustomTabsIntent}
          * object.
          */
@@ -1006,4 +1145,59 @@
         }
         return defaults;
     }
+
+    /**
+     * Gets the Custom Tab Activity's resize behavior.
+     *
+     * @param intent Intent to retrieve the resize behavior from.
+     * @return The resize behavior. If {@link CustomTabsIntent#EXTRA_INITIAL_ACTIVITY_HEIGHT_PX}
+     *         is not set as part of the same intent, the value has no effect.
+     * @see CustomTabsIntent#EXTRA_ACTIVITY_RESIZE_BEHAVIOR
+     * @see CustomTabsIntent#ACTIVITY_HEIGHT_DEFAULT
+     * @see CustomTabsIntent#ACTIVITY_HEIGHT_ADJUSTABLE
+     * @see CustomTabsIntent#ACTIVITY_HEIGHT_FIXED
+     */
+    @ActivityResizeBehavior
+    public static int getActivityResizeBehavior(@NonNull Intent intent) {
+        return intent.getIntExtra(EXTRA_ACTIVITY_RESIZE_BEHAVIOR,
+                CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT);
+    }
+
+    /**
+     * Gets the Custom Tab Activity's initial height.
+     *
+     * @param intent Intent to retrieve the initial Custom Tab Activity's height from.
+     * @return The initial Custom Tab Activity's height or 0 if it is not set.
+     * @see CustomTabsIntent#EXTRA_INITIAL_ACTIVITY_HEIGHT_PX
+     */
+    @Dimension(unit = PX)
+    public static int getInitialActivityHeightPx(@NonNull Intent intent) {
+        return intent.getIntExtra(EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 0);
+    }
+
+    /**
+     * Gets the toolbar's top corner radii in dp.
+     *
+     * @param intent Intent to retrieve the toolbar's top corner radii from.
+     * @return The toolbar's top corner radii in dp.
+     * @see CustomTabsIntent#EXTRA_TOOLBAR_CORNER_RADIUS_DP
+     */
+    @Dimension(unit = DP)
+    public static int getToolbarCornerRadiusDp(@NonNull Intent intent) {
+        return intent.getIntExtra(EXTRA_TOOLBAR_CORNER_RADIUS_DP, MAX_TOOLBAR_CORNER_RADIUS_DP);
+    }
+
+    /**
+     * Gets the position of the close button.
+     * @param intent Intent to retrieve the position of the close button from.
+     * @return The position of the close button, or the default position if the extra is not set.
+     * @see CustomTabsIntent#EXTRA_CLOSE_BUTTON_POSITION
+     * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_DEFAULT
+     * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_START
+     * @see CustomTabsIntent#CLOSE_BUTTON_POSITION_END
+     */
+    @CloseButtonPosition
+    public static int getCloseButtonPosition(@NonNull Intent intent) {
+        return intent.getIntExtra(EXTRA_CLOSE_BUTTON_POSITION, CLOSE_BUTTON_POSITION_DEFAULT);
+    }
 }
diff --git a/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java b/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
index 4a5ac59..620bdad 100644
--- a/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
+++ b/browser/browser/src/test/java/androidx/browser/customtabs/CustomTabsIntentTest.java
@@ -151,6 +151,215 @@
                 CustomTabsIntent.EXTRA_NAVIGATION_BAR_DIVIDER_COLOR, 0));
     }
 
+    @Test
+    public void testActivityInitialFixedResizeBehavior() {
+        int heightFixedResizeBehavior = CustomTabsIntent.ACTIVITY_HEIGHT_FIXED;
+        int initialActivityHeight = 200;
+
+        Intent intent = new CustomTabsIntent.Builder()
+                .setInitialActivityHeightPx(initialActivityHeight, heightFixedResizeBehavior)
+                .build()
+                .intent;
+
+        assertEquals("The value of EXTRA_ACTIVITY_FIXED_HEIGHT should be ACTIVITY_HEIGHT_FIXED.",
+                heightFixedResizeBehavior,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_ACTIVITY_RESIZE_BEHAVIOR,
+                        CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT));
+        assertEquals("The height should be the same as the one that was set.",
+                initialActivityHeight,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 0));
+        assertEquals("The value returned by the getter should be the same.",
+                heightFixedResizeBehavior,
+                CustomTabsIntent.getActivityResizeBehavior(intent));
+        assertEquals("The height returned by the getter should be the same.",
+                initialActivityHeight,
+                CustomTabsIntent.getInitialActivityHeightPx(intent));
+    }
+
+    @Test
+    public void testActivityInitialAdjustableResizeBehavior() {
+        int heightAdjustableResizeBehavior = CustomTabsIntent.ACTIVITY_HEIGHT_ADJUSTABLE;
+        int initialActivityHeight = 200;
+
+        Intent intent = new CustomTabsIntent.Builder()
+                .setInitialActivityHeightPx(initialActivityHeight, heightAdjustableResizeBehavior)
+                .build()
+                .intent;
+
+        assertEquals("The value of EXTRA_ACTIVITY_FIXED_HEIGHT should be "
+                        + "ACTIVITY_HEIGHT_ADJUSTABLE.",
+                heightAdjustableResizeBehavior,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_ACTIVITY_RESIZE_BEHAVIOR,
+                        CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT));
+        assertEquals("The height should be the same as the one that was set.",
+                initialActivityHeight,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 0));
+        assertEquals("The value returned by the getter should be the same.",
+                heightAdjustableResizeBehavior,
+                CustomTabsIntent.getActivityResizeBehavior(intent));
+        assertEquals("The height returned by the getter should be the same.",
+                initialActivityHeight,
+                CustomTabsIntent.getInitialActivityHeightPx(intent));
+    }
+
+    @Test
+    public void testActivityInitialHeightCorrectValue() {
+        int initialActivityHeight = 200;
+        int defaultResizeBehavior = CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT;
+
+        Intent intent = new CustomTabsIntent.Builder()
+                .setInitialActivityHeightPx(initialActivityHeight)
+                .build()
+                .intent;
+
+        assertEquals("The height should be the same as the one that was set.",
+                initialActivityHeight,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX, 0));
+        assertEquals("The value of EXTRA_ACTIVITY_RESIZE_BEHAVIOR should be "
+                        + "ACTIVITY_HEIGHT_DEFAULT.",
+                defaultResizeBehavior,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_ACTIVITY_RESIZE_BEHAVIOR,
+                        CustomTabsIntent.ACTIVITY_HEIGHT_FIXED));
+        assertEquals("The height returned by the getter should be the same.",
+                initialActivityHeight,
+                CustomTabsIntent.getInitialActivityHeightPx(intent));
+        assertEquals("The value returned by the getter should be the same.",
+                defaultResizeBehavior,
+                CustomTabsIntent.getActivityResizeBehavior(intent));
+    }
+
+    @Test
+    public void testActivityInitialFixedHeightExtraNotSet() {
+        int defaultInitialActivityHeight = 0;
+        int defaultResizeBehavior = CustomTabsIntent.ACTIVITY_HEIGHT_DEFAULT;
+
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+
+        assertFalse("The EXTRA_INITIAL_ACTIVITY_HEIGHT_PX should not be set.",
+                intent.hasExtra(CustomTabsIntent.EXTRA_INITIAL_ACTIVITY_HEIGHT_PX));
+        assertFalse("The EXTRA_ACTIVITY_RESIZE_BEHAVIOR should not be set.",
+                intent.hasExtra(CustomTabsIntent.EXTRA_ACTIVITY_RESIZE_BEHAVIOR));
+        assertEquals("The getter should return the default value.",
+                defaultInitialActivityHeight,
+                CustomTabsIntent.getInitialActivityHeightPx(intent));
+        assertEquals("The getter should return the default value.",
+                defaultResizeBehavior,
+                CustomTabsIntent.getActivityResizeBehavior(intent));
+    }
+
+    @Test
+    public void testActivityInitialHeightInvalidValuesThrow() {
+        try {
+            new CustomTabsIntent.Builder().setInitialActivityHeightPx(-1);
+            fail("The height of the activity should be higher than 0.");
+        } catch (IllegalArgumentException exception) {
+        }
+
+        try {
+            new CustomTabsIntent.Builder().setInitialActivityHeightPx(100, -1);
+            fail("Underflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+
+        try {
+            new CustomTabsIntent.Builder().setInitialActivityHeightPx(100,
+                    CustomTabsIntent.ACTIVITY_HEIGHT_FIXED + 1);
+            fail("Overflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+    }
+
+    @Test
+    public void testToolbarCornerRadiusDpCorrectValue() {
+        int cornerRadiusDp = 16;
+
+        Intent intent = new CustomTabsIntent.Builder()
+                .setToolbarCornerRadiusDp(cornerRadiusDp)
+                .build()
+                .intent;
+
+        assertEquals("The toolbar corner radius should be the same as the one that was set.",
+                cornerRadiusDp,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_TOOLBAR_CORNER_RADIUS_DP, 0));
+        assertEquals("The toolbar corner radius returned by the getter should be the same.",
+                cornerRadiusDp,
+                CustomTabsIntent.getToolbarCornerRadiusDp(intent));
+    }
+
+    @Test
+    public void testToolbarCornerRadiusDpExtraNotSet() {
+        int defaultCornerRadiusDp = 16;
+
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+
+        assertFalse("The EXTRA_TOOLBAR_CORNER_RADIUS_DP should not be set.",
+                intent.hasExtra(CustomTabsIntent.EXTRA_TOOLBAR_CORNER_RADIUS_DP));
+        assertEquals("The getter should return the default value.",
+                defaultCornerRadiusDp,
+                CustomTabsIntent.getToolbarCornerRadiusDp(intent));
+    }
+
+    @Test
+    public void testToolbarCornerRadiusDpInvalidValueThrows() {
+        try {
+            new CustomTabsIntent.Builder().setToolbarCornerRadiusDp(-1);
+            fail("Underflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+
+        try {
+            new CustomTabsIntent.Builder().setToolbarCornerRadiusDp(17);
+            fail("Overflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+    }
+    @Test
+    public void testCloseButtonPositionCorrectValue() {
+        int closeButtonPosition = CustomTabsIntent.CLOSE_BUTTON_POSITION_START;
+
+        Intent intent = new CustomTabsIntent.Builder()
+                .setCloseButtonPosition(closeButtonPosition)
+                .build()
+                .intent;
+
+        assertEquals("The close button position should be the same as the one that was set.",
+                closeButtonPosition,
+                intent.getIntExtra(CustomTabsIntent.EXTRA_CLOSE_BUTTON_POSITION,
+                        CustomTabsIntent.CLOSE_BUTTON_POSITION_END));
+        assertEquals("The close button position returned by the getter should be the same.",
+                closeButtonPosition,
+                CustomTabsIntent.getCloseButtonPosition(intent));
+    }
+
+    @Test
+    public void testCloseButtonPositionExtraNotSet() {
+        int defaultPosition = CustomTabsIntent.CLOSE_BUTTON_POSITION_DEFAULT;
+
+        Intent intent = new CustomTabsIntent.Builder().build().intent;
+
+        assertFalse("The EXTRA_CLOSE_BUTTON_POSITION should not be set.",
+                intent.hasExtra(CustomTabsIntent.EXTRA_CLOSE_BUTTON_POSITION));
+        assertEquals("The getter should return the default value.",
+                defaultPosition,
+                CustomTabsIntent.getCloseButtonPosition(intent));
+    }
+
+    @Test
+    public void testCloseButtonPositionInvalidValueThrows() {
+        try {
+            new CustomTabsIntent.Builder().setCloseButtonPosition(-1);
+            fail("Underflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+
+        try {
+            new CustomTabsIntent.Builder()
+                    .setCloseButtonPosition(CustomTabsIntent.CLOSE_BUTTON_POSITION_END + 1);
+            fail("Overflow arguments are expected to throw an exception");
+        } catch (IllegalArgumentException exception) {
+        }
+    }
+
     public void throwsError_WhenInvalidShareStateSet() {
         try {
             new CustomTabsIntent.Builder().setShareState(-1);
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
index a64d17c..9237885 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXImplPlugin.kt
@@ -66,7 +66,6 @@
 import org.gradle.api.Plugin
 import org.gradle.api.Project
 import org.gradle.api.Task
-import org.gradle.api.artifacts.repositories.IvyArtifactRepository
 import org.gradle.api.component.SoftwareComponentFactory
 import org.gradle.api.file.DuplicatesStrategy
 import org.gradle.api.plugins.JavaPlugin
@@ -91,6 +90,7 @@
 import org.jetbrains.kotlin.gradle.plugin.KotlinBasePluginWrapper
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
 import org.jetbrains.kotlin.gradle.targets.native.KotlinNativeHostTestRun
+import org.jetbrains.kotlin.gradle.tasks.CInteropProcess
 import org.jetbrains.kotlin.gradle.tasks.KotlinCompile
 import org.jetbrains.kotlin.gradle.tasks.KotlinNativeCompile
 import org.jetbrains.kotlin.gradle.testing.KotlinTaskTestRun
@@ -731,53 +731,35 @@
         if (StudioType.isPlayground(this)) {
             return // playground does not use prebuilts
         }
-        overrideKotlinNativeCompilerRepository()
+        overrideKotlinNativeDistributionUrlToLocalDirectory()
+        overrideKotlinNativeDependenciesUrlToLocalDirectory()
+    }
+
+    private fun Project.overrideKotlinNativeDependenciesUrlToLocalDirectory() {
+        val konanPrebuiltsFolder = getKonanPrebuiltsFolder()
+        // use relative path so it doesn't affect gradle remote cache.
+        val relativeRootPath = konanPrebuiltsFolder.relativeTo(rootProject.projectDir).path
+        val relativeProjectPath = konanPrebuiltsFolder.relativeTo(projectDir).path
         tasks.withType(KotlinNativeCompile::class.java).configureEach {
-            // use relative path so it doesn't affect gradle remote cache.
-            val relativePath = getKonanPrebuiltsFolder().relativeTo(rootProject.projectDir).path
             it.kotlinOptions.freeCompilerArgs += listOf(
-                "-Xoverride-konan-properties=dependenciesUrl=file:$relativePath"
+                "-Xoverride-konan-properties=dependenciesUrl=file:$relativeRootPath"
+            )
+        }
+        tasks.withType(CInteropProcess::class.java).configureEach {
+            it.settings.extraOpts += listOf(
+                "-Xoverride-konan-properties",
+                "dependenciesUrl=file:$relativeProjectPath"
             )
         }
     }
 
-    /**
-     * Until kotlin 1.7, we cannot set the repository URL where KMP plugin downloads the kotlin
-     * native compiler. This method implements a workaround for 1.6.21.
-     * We hijack the repository added by NativeCompilerDownloader to make it point to the konan
-     * prebuilts directory.
-     * After kotlin 1.7, we should use nativeBaseDownloadUrl property:
-     * https://github.com/JetBrains/kotlin/blob/025a21761b326767207b4a373593a3c2d24b8056/libraries/
-     *   tools/kotlin-gradle-plugin/src/common/kotlin/org/jetbrains/kotlin/gradle/plugin/
-     *   KotlinProperties.kt#L223
-     */
-    private fun Project.overrideKotlinNativeCompilerRepository() {
-        this.repositories.whenObjectAdded {
-            if (it is IvyArtifactRepository) {
-                if (it.url.host == "download.jetbrains.com" &&
-                    it.url.path.contains("kotlin/native/builds")
-                ) {
-                    val fileUrl = project.getKonanPrebuiltsFolder().resolve(
-                        "nativeCompilerPrebuilts"
-                    ).resolve(
-                        it.url.path.substringAfter("kotlin/native/builds/")
-                    ).canonicalFile
-                    check(fileUrl.exists()) {
-                        val konanVersion = getVersionByName("kotlinNative")
-                        """
-                        Missing kotlin native compiler prebuilt in $fileUrl. If you are updating
-                        kotlin version, please add the new compiler to the prebuilts/androidx/konan
-                        repository. You can download them by invoking the following script:
-
-                        ../../prebuilts/androidx/konan/download-native-compiler-prebuilts.sh $konanVersion
-
-                        Please don't forget to commit that version into the konan repository.
-                        """.trimIndent()
-                    }
-                    it.url = fileUrl.toURI()
-                }
-            }
-        }
+    private fun Project.overrideKotlinNativeDistributionUrlToLocalDirectory() {
+        val relativePath = getKonanPrebuiltsFolder()
+            .resolve("nativeCompilerPrebuilts")
+            .relativeTo(projectDir)
+            .path
+        val url = "file:$relativePath"
+        extensions.extraProperties["kotlin.native.distribution.baseDownloadUrl"] = url
     }
 
     private fun Project.configureKmpBuildOnServer() {
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
index 5112bec..6597c53 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/AndroidXMultiplatformExtension.kt
@@ -16,12 +16,12 @@
 
 package androidx.build
 
+import groovy.lang.Closure
 import org.gradle.api.Action
 import org.gradle.api.NamedDomainObjectCollection
 import org.gradle.api.Project
 import org.jetbrains.kotlin.gradle.dsl.KotlinMultiplatformExtension
 import org.jetbrains.kotlin.gradle.plugin.KotlinMultiplatformPluginWrapper
-import org.jetbrains.kotlin.gradle.plugin.KotlinSourceSet
 import org.jetbrains.kotlin.gradle.plugin.KotlinTarget
 import org.jetbrains.kotlin.gradle.plugin.mpp.KotlinAndroidTarget
 import org.jetbrains.kotlin.gradle.plugin.KotlinTargetPreset
@@ -39,19 +39,24 @@
 open class AndroidXMultiplatformExtension(val project: Project) {
 
     // Kotlin multiplatform plugin is only applied if at least one target / sourceset is added.
-    private val kotlinExtension: KotlinMultiplatformExtension by lazy {
+    private val kotlinExtensionDelegate = lazy {
         project.validateMultiplatformPluginHasNotBeenApplied()
         project.plugins.apply(KotlinMultiplatformPluginWrapper::class.java)
         project.multiplatformExtension!!
     }
+    private val kotlinExtension: KotlinMultiplatformExtension by kotlinExtensionDelegate
 
-    val sourceSets: NamedDomainObjectCollection<KotlinSourceSet>
-        get() = kotlinExtension.sourceSets
     val presets: NamedDomainObjectCollection<KotlinTargetPreset<*>>
         get() = kotlinExtension.presets
     val targets: NamedDomainObjectCollection<KotlinTarget>
         get() = kotlinExtension.targets
 
+    fun sourceSets(closure: Closure<*>) {
+        if (kotlinExtensionDelegate.isInitialized()) {
+            kotlinExtension.sourceSets.configure(closure)
+        }
+    }
+
     @JvmOverloads
     fun jvm(
         block: Action<KotlinJvmTarget>? = null
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
index 5e0b86a9f..b878c8b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/Ktlint.kt
@@ -19,6 +19,7 @@
 import androidx.build.logging.TERMINAL_RED
 import androidx.build.logging.TERMINAL_RESET
 import java.io.File
+import java.nio.file.Paths
 import javax.inject.Inject
 import org.gradle.api.DefaultTask
 import org.gradle.api.Project
@@ -88,8 +89,12 @@
     "wrapping",
 ).joinToString(",")
 
-private const val ExcludeTestDataFiles = "**/test-data/**/*.kt"
-private const val ExcludeExternalFiles = "**/external/**/*.kt"
+private val ExcludedDirectories = listOf(
+    "test-data",
+    "external",
+)
+
+private val ExcludedDirectoryGlobs = ExcludedDirectories.map { "**/$it/**/*.kt" }
 private const val MainClass = "com.pinterest.ktlint.Main"
 private const val InputDir = "src"
 private const val IncludedFiles = "**/*.kt"
@@ -104,17 +109,19 @@
         task.report = File("${outputDir}ktlint-checkstyle-report.xml")
         task.ktlintClasspath.from(getKtlintConfiguration())
     }
-
-    // afterEvaluate because Gradle's default "check" task doesn't exist yet
-    afterEvaluate {
-        addToCheckTask(lintProvider)
-    }
-    addToBuildOnServer(lintProvider)
-
     tasks.register("ktlintFormat", KtlintFormatTask::class.java) { task ->
         task.report = File("${outputDir}ktlint-format-checkstyle-report.xml")
         task.ktlintClasspath.from(getKtlintConfiguration())
     }
+    // afterEvaluate because Gradle's default "check" task doesn't exist yet
+    afterEvaluate {
+        // multiplatform projects with no enabled platforms do not actually apply the kotlin plugin
+        // and therefore do not have the check task. They are skipped unless a platform is enabled.
+        if (project.tasks.findByName("check") != null) {
+            addToCheckTask(lintProvider)
+            addToBuildOnServer(lintProvider)
+        }
+    }
 }
 
 @CacheableTask
@@ -134,7 +141,7 @@
             return project.fileTree(
                 mutableMapOf(
                     "dir" to InputDir, "include" to IncludedFiles,
-                    "exclude" to listOf(ExcludeTestDataFiles, ExcludeExternalFiles)
+                    "exclude" to ExcludedDirectoryGlobs
                 )
             )
         }
@@ -175,8 +182,7 @@
             subdirectories.map { arguments.add("$it/$InputDir/$IncludedFiles") }
         } ?: arguments.add("$InputDir/$IncludedFiles")
 
-        arguments.add("!$InputDir/$ExcludeTestDataFiles")
-        arguments.add("!$InputDir/$ExcludeExternalFiles")
+        ExcludedDirectoryGlobs.mapTo(arguments) { "!$InputDir/$it" }
         return arguments
     }
 }
@@ -265,7 +271,13 @@
     fun runKtlint() {
         if (files.isEmpty()) throw StopExecutionException()
         val kotlinFiles = files.filter { file ->
-            file.endsWith(".kt") || file.endsWith(".ktx")
+            val isKotlinFile = file.endsWith(".kt") || file.endsWith(".ktx")
+            val inExcludedDir =
+                Paths.get(file).any { subPath ->
+                    ExcludedDirectories.contains(subPath.toString())
+                }
+
+            isKotlinFile && !inExcludedDir
         }
         if (kotlinFiles.isEmpty()) throw StopExecutionException()
         val result = execOperations.javaexec { javaExecSpec ->
@@ -279,10 +291,6 @@
             args.addAll(kotlinFiles)
             if (format) args.add("-F")
 
-            // Note: These exclusions must come after the inputs.
-            args.add("!$ExcludeTestDataFiles")
-            args.add("!$ExcludeExternalFiles")
-
             javaExecSpec.args = args
             javaExecSpec.isIgnoreExitValue = true
         }
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
index 0a33d69..7675b95 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/LintConfiguration.kt
@@ -187,13 +187,6 @@
         // Reenable after b/238892319 is resolved
         disable.add("NotificationPermission")
 
-        // Broken in 7.4.0-alpha04 due to b/236262744
-        disable.add("CustomPermissionTypo")
-        disable.add("KnownPermissionError")
-        disable.add("PermissionNamingConvention")
-        disable.add("ReservedSystemPermission")
-        disable.add("SystemPermissionTypo")
-
         // Disable dependency checks that suggest to change them. We want libraries to be
         // intentional with their dependency version bumps.
         disable.add("KtxExtensionAvailable")
@@ -203,10 +196,6 @@
         // concerned with drawables potentially being a little bit blurry
         disable.add("IconMissingDensityFolder")
 
-        // Disable a check that's only triggered by translation updates which are
-        // outside of library owners' control, b/174655193
-        disable.add("UnusedQuantity")
-
         // Disable until it works for our projects, b/171986505
         disable.add("JavaPluginLanguageLevel")
 
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/GenerateMetadataTask.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/GenerateMetadataTask.kt
index fcc5081..435c07b 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/GenerateMetadataTask.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/GenerateMetadataTask.kt
@@ -18,6 +18,7 @@
 
 import com.google.gson.Gson
 import com.google.gson.GsonBuilder
+import java.io.File
 import java.io.FileWriter
 import java.util.zip.ZipFile
 import org.gradle.api.DefaultTask
@@ -25,13 +26,14 @@
 import org.gradle.api.artifacts.component.ModuleComponentIdentifier
 import org.gradle.api.file.RegularFileProperty
 import org.gradle.api.provider.ListProperty
-import org.gradle.api.provider.Property
 import org.gradle.api.tasks.CacheableTask
 import org.gradle.api.tasks.Input
+import org.gradle.api.tasks.InputFiles
 import org.gradle.api.tasks.OutputFile
+import org.gradle.api.tasks.PathSensitive
+import org.gradle.api.tasks.PathSensitivity
 import org.gradle.api.tasks.TaskAction
 import org.gradle.internal.component.external.model.DefaultModuleComponentIdentifier
-import org.gradle.internal.component.local.model.ComponentFileArtifactIdentifier
 
 @CacheableTask
 abstract class GenerateMetadataTask : DefaultTask() {
@@ -43,41 +45,40 @@
     abstract fun getArtifactIds(): ListProperty<ComponentArtifactIdentifier>
 
     /**
+     * List of files corresponding to artifacts in [getArtifactIds]
+     */
+    @InputFiles
+    @PathSensitive(PathSensitivity.NONE)
+    abstract fun getArtifactFiles(): ListProperty<File>
+
+    /**
      * Location of the generated JSON file
      */
     @get:OutputFile
     abstract val destinationFile: RegularFileProperty
 
-    /**
-     * Location of the prebuilts root directory
-     */
-    @get:Input
-    abstract val prebuiltsRoot: Property<String>
-
     @TaskAction
     fun generate() {
         val entries = arrayListOf<MetadataEntry>()
-        val androidXBasePath = "${prebuiltsRoot.get()}/androidx/internal"
 
-        getArtifactIds().get().forEach { id ->
+        val artifactIds = getArtifactIds().get()
+        val artifactFiles = getArtifactFiles().get()
+        for (i in 0 until artifactIds.size) {
+            val id = artifactIds[i]
+            val file = artifactFiles[i]
 
             // Only process artifact if it can be cast to ModuleComponentIdentifier.
             //
             // In practice, metadata is generated only for docs-public and not docs-tip-of-tree
             // (where id.componentIdentifier is DefaultProjectComponentIdentifier).
-            if (id.componentIdentifier !is DefaultModuleComponentIdentifier) return@forEach
+            if (id.componentIdentifier !is DefaultModuleComponentIdentifier) continue
 
             // Created https://github.com/gradle/gradle/issues/21415 to track surfacing
             // group / module / version in ComponentIdentifier
             val componentId = (id.componentIdentifier as ModuleComponentIdentifier)
 
-            // Locate the .jar file associated with this artifact and fetch the list of files
-            // contained in the .jar file
-            val jarFilename = (id as ComponentFileArtifactIdentifier).fileName
-            val componentIdPath = componentId.group.replace(".", "/")
-            val jarLocation = "$androidXBasePath/$componentIdPath/${componentId.module}/" +
-                "${componentId.version}/$jarFilename"
-            val fileList = ZipFile(jarLocation).entries().toList().map { it.name }
+            // Fetch the list of files contained in the .jar file
+            val fileList = ZipFile(file).entries().toList().map { it.name }
 
             val entry = MetadataEntry(
                 groupId = componentId.group,
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/dackka/MetadataEntry.kt b/buildSrc/private/src/main/kotlin/androidx/build/dackka/MetadataEntry.kt
index 6950d6c..d450f18 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/dackka/MetadataEntry.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/dackka/MetadataEntry.kt
@@ -31,18 +31,6 @@
     @SerializedName("releaseNotesUrl")
     val releaseNotesUrl: String,
 
-    // TODO (b/243175565): remove @Transient once bug is resolved
-    //
-    // Dackka currently fails if extra keys are present in the JSON file; temporarily omit this
-    // field in the JSON output
-    @Transient
     @SerializedName("jarContents")
     val jarContents: List<String>,
-
-    // TODO (b/241582234): Remove when bug is resolved.
-    //
-    // This will no longer be used once Dackka is updated, but is currently needed as Dackka
-    // expects this key to be present.
-    @SerializedName("sourceDir")
-    val sourceDir: String = "TBD/SOURCE/DIR",
 )
\ No newline at end of file
diff --git a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
index a28ac92..6d71deb 100644
--- a/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
+++ b/buildSrc/private/src/main/kotlin/androidx/build/docs/AndroidXDocsImplPlugin.kt
@@ -45,8 +45,6 @@
 import org.gradle.api.artifacts.ComponentMetadataContext
 import org.gradle.api.artifacts.ComponentMetadataRule
 import org.gradle.api.artifacts.Configuration
-import org.gradle.api.artifacts.component.ComponentArtifactIdentifier
-import org.gradle.api.artifacts.result.ResolvedArtifactResult
 import org.gradle.api.attributes.Attribute
 import org.gradle.api.attributes.Category
 import org.gradle.api.attributes.DocsType
@@ -368,18 +366,12 @@
             @Suppress("UnstableApiUsage") // getResolvedArtifacts() is marked @Incubating
             val artifacts = docsConfiguration.incoming.artifacts.resolvedArtifacts
             task.getArtifactIds().set(
-
-                /**
-                 * Transforms the Set of [ResolvedArtifactResult] objects to a List of
-                 * [ComponentArtifactIdentifier] objects.
-                 *
-                 * This follows the guidance from
-                 * https://docs.gradle.org/7.5/userguide/more_about_tasks.html.
-                 */
                 artifacts.map { result -> result.map { it.id } }
             )
+            task.getArtifactFiles().set(
+                artifacts.map { result -> result.map { it.file } }
+            )
             task.destinationFile.set(getMetadataRegularFile(project))
-            task.prebuiltsRoot.set(File(project.getCheckoutRoot(), "prebuilts").absolutePath)
         }
 
         val dackkaTask = project.tasks.register("dackkaDocs", DackkaTask::class.java) { task ->
diff --git a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
index 0a50e7c..3c5edf2 100644
--- a/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
+++ b/buildSrc/public/src/main/kotlin/androidx/build/LibraryType.kt
@@ -69,6 +69,7 @@
         val PUBLISHED_TEST_LIBRARY = PublishedTestLibrary()
         val PUBLISHED_NATIVE_LIBRARY = PublishedNativeLibrary()
         val INTERNAL_TEST_LIBRARY = InternalTestLibrary()
+        val INTERNAL_HOST_TEST_LIBRARY = InternalHostTestLibrary()
         val SAMPLES = Samples()
         val LINT = Lint()
         val COMPILER_DAEMON = CompilerDaemon()
@@ -82,15 +83,21 @@
         val KMP_LIBRARY = KmpLibrary()
         val UNSET = Unset()
     }
-    open class PublishedLibrary : LibraryType(
+    open class PublishedLibrary() : LibraryType(
         publish = Publish.SNAPSHOT_AND_RELEASE,
         sourceJars = true,
         checkApi = RunApiTasks.Yes()
     )
+    open class InternalLibrary(
+        compilationTarget: CompilationTarget = CompilationTarget.DEVICE
+    ) : LibraryType(
+        checkApi = RunApiTasks.No("Internal Library"),
+        compilationTarget = compilationTarget
+    )
 
     class PublishedTestLibrary() : PublishedLibrary()
-    open class InternalLibrary() : LibraryType()
     class InternalTestLibrary() : InternalLibrary()
+    class InternalHostTestLibrary() : InternalLibrary(CompilationTarget.HOST)
     class PublishedNativeLibrary : PublishedLibrary()
     class Samples : LibraryType(
         publish = Publish.SNAPSHOT_AND_RELEASE,
diff --git a/busytown/impl/build-studio-and-androidx.sh b/busytown/impl/build-studio-and-androidx.sh
index 76ecca9..0003d61 100755
--- a/busytown/impl/build-studio-and-androidx.sh
+++ b/busytown/impl/build-studio-and-androidx.sh
@@ -95,6 +95,7 @@
 export JAVA_HOME="$(pwd)/prebuilts/jdk/jdk11/$PREBUILT_JDK/"
 export JAVA_TOOLS_JAR="$(pwd)/prebuilts/jdk/jdk8/$PREBUILT_JDK/lib/tools.jar"
 export LINT_PRINT_STACKTRACE=true
+export USE_ANDROIDX_REMOTE_BUILD_CACHE=gcp
 
 function buildAndroidx() {
   RETURN_CODE=0
diff --git a/camera/camera-camera2-pipe-integration/build.gradle b/camera/camera-camera2-pipe-integration/build.gradle
index 9fcacd4..981353e 100644
--- a/camera/camera-camera2-pipe-integration/build.gradle
+++ b/camera/camera-camera2-pipe-integration/build.gradle
@@ -79,6 +79,7 @@
     androidTestImplementation(project(":camera:camera-testing"))
     androidTestImplementation(project(":concurrent:concurrent-futures-ktx"))
     androidTestImplementation(project(":internal-testutils-truth"))
+    androidTestImplementation("androidx.test.espresso:espresso-core:3.3.0")
 }
 
 android {
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/UseCaseSurfaceManagerDeviceTest.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/UseCaseSurfaceManagerDeviceTest.kt
new file mode 100644
index 0000000..38dc5a0
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/integration/UseCaseSurfaceManagerDeviceTest.kt
@@ -0,0 +1,268 @@
+/*
+ * 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.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.integration
+
+import android.content.Intent
+import android.graphics.ImageFormat
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import android.media.ImageReader
+import android.os.Build
+import android.os.HandlerThread
+import android.view.Surface
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraSurfaceManager
+import androidx.camera.camera2.pipe.integration.impl.Camera2ImplConfig
+import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
+import androidx.camera.camera2.pipe.testing.TestUseCaseCamera
+import androidx.camera.core.impl.DeferrableSurface
+import androidx.camera.core.impl.ImmediateSurface
+import androidx.camera.core.impl.SessionConfig
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.CameraUtil.CameraDeviceHolder
+import androidx.camera.testing.CoreAppTestUtil
+import androidx.camera.testing.activity.Camera2TestActivity
+import androidx.camera.testing.fakes.FakeUseCase
+import androidx.camera.testing.fakes.FakeUseCaseConfig
+import androidx.camera.testing.waitForIdle
+import androidx.core.os.HandlerCompat
+import androidx.test.core.app.ActivityScenario
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.espresso.IdlingResource
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.util.concurrent.MoreExecutors
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+import kotlinx.coroutines.CoroutineName
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.Deferred
+import kotlinx.coroutines.SupervisorJob
+import kotlinx.coroutines.asCoroutineDispatcher
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.withTimeout
+import org.junit.After
+import org.junit.Assume.assumeTrue
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = 21)
+class UseCaseSurfaceManagerDeviceTest {
+
+    @get:Rule
+    val useCamera = CameraUtil.grantCameraPermissionAndPreTest(
+        CameraUtil.PreTestCameraIdList(CameraPipeConfig.defaultConfig())
+    )
+
+    private val executor = MoreExecutors.directExecutor()
+    private val useCaseThreads by lazy {
+        val dispatcher = executor.asCoroutineDispatcher()
+        val cameraScope = CoroutineScope(
+            SupervisorJob() +
+                dispatcher +
+                CoroutineName("UseCaseSurfaceManagerTest")
+        )
+
+        UseCaseThreads(
+            cameraScope,
+            executor,
+            dispatcher
+        )
+    }
+
+    private lateinit var cameraId: String
+    private lateinit var cameraHolder: CameraDeviceHolder
+    private lateinit var testSessionParameters: TestSessionParameters
+    private lateinit var testUseCaseCamera: TestUseCaseCamera
+
+    @Before
+    fun setUp() {
+        val cameraIsList = CameraUtil.getBackwardCompatibleCameraIdListOrThrow()
+        assumeTrue("Not having a valid Camera for testing", cameraIsList.isNotEmpty())
+        cameraId = cameraIsList[0]
+    }
+
+    @After
+    fun tearDown() {
+        if (this::testSessionParameters.isInitialized) {
+            testSessionParameters.cleanup()
+        }
+        if (this::testUseCaseCamera.isInitialized) {
+            testUseCaseCamera.close()
+        }
+        if (this::cameraHolder.isInitialized) {
+            CameraUtil.releaseCameraDevice(cameraHolder)
+            cameraHolder.closedFuture.get(3, TimeUnit.SECONDS)
+        }
+    }
+
+    @Test
+    fun openCloseCameraGraph_deferrableSurfaceUsageCountTest() = runBlocking {
+        // Arrange.
+        testSessionParameters = TestSessionParameters()
+        val useCases = listOf(createFakeUseCase().apply {
+            setupSessionConfig(testSessionParameters.sessionConfig)
+        })
+
+        // Act. Open CameraGraph
+        testUseCaseCamera = TestUseCaseCamera(
+            context = ApplicationProvider.getApplicationContext(),
+            cameraId = cameraId,
+            useCases = useCases,
+            threads = useCaseThreads,
+        )
+        assertThat(
+            testSessionParameters.repeatingOutputDataLatch.await(3, TimeUnit.SECONDS)
+        ).isTrue()
+        val cameraOpenedUsageCount = testSessionParameters.deferrableSurface.useCount
+        // Act. close CameraGraph
+        testUseCaseCamera.useCaseCameraGraphConfig.graph.close()
+        testUseCaseCamera.useCaseSurfaceManager.stopAsync().awaitWithTimeout()
+        val cameraClosedUsageCount = testSessionParameters.deferrableSurface.useCount
+
+        // Assert, verify the usage count of the DeferrableSurface
+        assertThat(cameraOpenedUsageCount).isAtLeast(1)
+        assertThat(cameraClosedUsageCount).isEqualTo(0)
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.M)
+    fun disconnectOpenedCameraGraph_deferrableSurfaceUsageCountTest() = runBlocking {
+        CoreAppTestUtil.prepareDeviceUI(InstrumentationRegistry.getInstrumentation())
+
+        // Arrange.
+        testSessionParameters = TestSessionParameters()
+        val useCases = listOf(createFakeUseCase().apply {
+            setupSessionConfig(testSessionParameters.sessionConfig)
+        })
+        testUseCaseCamera = TestUseCaseCamera(
+            context = ApplicationProvider.getApplicationContext(),
+            cameraId = cameraId,
+            useCases = useCases,
+            threads = useCaseThreads,
+        )
+        val surfaceActiveCountDown = CountDownLatch(1)
+        val surfaceInactiveCountDown = CountDownLatch(1)
+        testUseCaseCamera.cameraPipe.cameraSurfaceManager()
+            .addListener(object : CameraSurfaceManager.SurfaceListener {
+                override fun onSurfaceActive(surface: Surface) {
+                    if (surface == testSessionParameters.deferrableSurface.surface.get()) {
+                        surfaceActiveCountDown.countDown()
+                    }
+                }
+
+                override fun onSurfaceInactive(surface: Surface) {
+                    if (surface == testSessionParameters.deferrableSurface.surface.get()) {
+                        surfaceInactiveCountDown.countDown()
+                    }
+                }
+            })
+        assertThat(surfaceActiveCountDown.await(3, TimeUnit.SECONDS)).isTrue()
+        val cameraOpenedUsageCount = testSessionParameters.deferrableSurface.useCount
+
+        // Act. Launch Camera2Activity to open the camera, it disconnects the CameraGraph.
+        ActivityScenario.launch<Camera2TestActivity>(
+            Intent(
+                ApplicationProvider.getApplicationContext(),
+                Camera2TestActivity::class.java
+            ).apply {
+                putExtra(Camera2TestActivity.EXTRA_CAMERA_ID, cameraId)
+            }
+        ).use {
+            lateinit var previewReady: IdlingResource
+            it.onActivity { activity -> previewReady = activity.mPreviewReady!! }
+            previewReady.waitForIdle()
+
+            assertThat(surfaceInactiveCountDown.await(3, TimeUnit.SECONDS)).isTrue()
+            val cameraClosedUsageCount = testSessionParameters.deferrableSurface.useCount
+
+            // Assert, verify the usage count of the DeferrableSurface
+            assertThat(cameraOpenedUsageCount).isAtLeast(1)
+            assertThat(cameraClosedUsageCount).isEqualTo(0)
+        }
+    }
+
+    private fun createFakeUseCase() = object : FakeUseCase(
+        FakeUseCaseConfig.Builder().setTargetName("UseCase").useCaseConfig
+    ) {
+        fun setupSessionConfig(sessionConfig: SessionConfig) {
+            updateSessionConfig(sessionConfig)
+            notifyActive()
+        }
+    }
+
+    private suspend fun <T> Deferred<T>.awaitWithTimeout(
+        timeMillis: Long = TimeUnit.SECONDS.toMillis(5)
+    ) = withTimeout(timeMillis) {
+        await()
+    }
+
+    private class TestSessionParameters(name: String = "TestSessionParameters") {
+        /** Thread for all asynchronous calls.  */
+        private val handlerThread: HandlerThread = HandlerThread(name).apply {
+            start()
+        }
+
+        /** Image reader that unlocks the latch waiting for the first image data to appear.  */
+        private val  { reader ->
+            reader.acquireNextImage()?.let { image ->
+                image.close()
+                repeatingOutputDataLatch.countDown()
+            }
+        }
+        private val imageReader: ImageReader =
+            ImageReader.newInstance(640, 480, ImageFormat.YUV_420_888, 2).apply {
+                setOnImageAvailableListener(
+                    onImageAvailableListener, HandlerCompat.createAsync(handlerThread.looper)
+                )
+            }
+
+        val deferrableSurface: DeferrableSurface = ImmediateSurface(imageReader.surface)
+
+        /** Latch to wait for first image data to appear.  */
+        val repeatingOutputDataLatch = CountDownLatch(1)
+
+        val sessionConfig: SessionConfig = SessionConfig.Builder().apply {
+            setTemplateType(CameraDevice.TEMPLATE_PREVIEW)
+            addSurface(deferrableSurface)
+            val camera2ConfigBuilder: Camera2ImplConfig.Builder = Camera2ImplConfig.Builder()
+            // Add capture request options for SessionConfig
+            camera2ConfigBuilder.setCaptureRequestOption<Int>(
+                CaptureRequest.CONTROL_AF_MODE, CaptureRequest.CONTROL_AF_MODE_AUTO
+            ).setCaptureRequestOption<Int>(
+                CaptureRequest.CONTROL_AE_MODE, CaptureRequest.CONTROL_AE_MODE_ON
+            )
+            addImplementationOptions(camera2ConfigBuilder.build())
+        }.build()
+
+        /** Clean up resources.  */
+        fun cleanup() {
+            deferrableSurface.close()
+            imageReader.close()
+            handlerThread.quitSafely()
+        }
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
new file mode 100644
index 0000000..1e81e83
--- /dev/null
+++ b/camera/camera-camera2-pipe-integration/src/androidTest/java/androidx/camera/camera2/pipe/testing/TestUseCaseCamera.kt
@@ -0,0 +1,129 @@
+/*
+ * 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.
+ */
+
+@file:RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+
+package androidx.camera.camera2.pipe.testing
+
+import android.content.Context
+import android.hardware.camera2.CameraDevice
+import android.hardware.camera2.CaptureRequest
+import android.hardware.camera2.params.MeteringRectangle
+import androidx.annotation.RequiresApi
+import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraId
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.Request
+import androidx.camera.camera2.pipe.Result3A
+import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
+import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
+import androidx.camera.camera2.pipe.integration.config.CameraConfig
+import androidx.camera.camera2.pipe.integration.config.UseCaseCameraConfig
+import androidx.camera.camera2.pipe.integration.impl.CameraCallbackMap
+import androidx.camera.camera2.pipe.integration.impl.CameraPipeCameraProperties
+import androidx.camera.camera2.pipe.integration.impl.CapturePipeline
+import androidx.camera.camera2.pipe.integration.impl.ComboRequestListener
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCamera
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControl
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraRequestControlImpl
+import androidx.camera.camera2.pipe.integration.impl.UseCaseCameraState
+import androidx.camera.camera2.pipe.integration.impl.UseCaseSurfaceManager
+import androidx.camera.camera2.pipe.integration.impl.UseCaseThreads
+import androidx.camera.core.UseCase
+import androidx.camera.core.impl.Config
+import kotlinx.coroutines.Deferred
+
+/**
+ * Open a [CameraGraph] for the desired [cameraId] and [useCases]
+ */
+class TestUseCaseCamera(
+    private val context: Context,
+    private val cameraId: String,
+    private val threads: UseCaseThreads,
+    private val useCases: List<UseCase>,
+    private val cameraConfig: CameraConfig = CameraConfig(CameraId(cameraId)),
+    val cameraPipe: CameraPipe = CameraPipe(CameraPipe.Config(context)),
+    val callbackMap: CameraCallbackMap = CameraCallbackMap(),
+    val useCaseSurfaceManager: UseCaseSurfaceManager = UseCaseSurfaceManager(
+        threads,
+        cameraPipe,
+    ),
+) : UseCaseCamera {
+    val useCaseCameraGraphConfig = UseCaseCameraConfig(useCases).provideUseCaseGraphConfig(
+        callbackMap = callbackMap,
+        cameraConfig = cameraConfig,
+        cameraPipe = cameraPipe,
+        requestListener = ComboRequestListener(),
+        useCaseSurfaceManager = useCaseSurfaceManager,
+    )
+
+    override val requestControl: UseCaseCameraRequestControl = UseCaseCameraRequestControlImpl(
+        configAdapter = CaptureConfigAdapter(
+            CameraPipeCameraProperties(cameraPipe, cameraConfig),
+            useCaseCameraGraphConfig,
+            threads
+        ),
+        capturePipeline = object : CapturePipeline {
+            override var template: Int = CameraDevice.TEMPLATE_PREVIEW
+
+            override suspend fun submitStillCaptures(
+                requests: List<Request>,
+                captureMode: Int,
+                flashType: Int,
+                flashMode: Int
+            ): List<Deferred<Void?>> {
+                throw NotImplementedError("Not implemented")
+            }
+        },
+        state = UseCaseCameraState(useCaseCameraGraphConfig, threads),
+        threads = threads,
+        useCaseGraphConfig = useCaseCameraGraphConfig,
+    ).apply {
+        SessionConfigAdapter(useCases).getValidSessionConfigOrNull()?.let { sessionConfig ->
+            setSessionConfigAsync(sessionConfig)
+        }
+    }
+
+    override var runningUseCases = useCases.toSet()
+
+    override fun <T> setParameterAsync(
+        key: CaptureRequest.Key<T>,
+        value: T,
+        priority: Config.OptionPriority
+    ): Deferred<Unit> {
+        throw NotImplementedError("Not implemented")
+    }
+
+    override fun setParametersAsync(
+        values: Map<CaptureRequest.Key<*>, Any>,
+        priority: Config.OptionPriority
+    ): Deferred<Unit> {
+        throw NotImplementedError("Not implemented")
+    }
+
+    override suspend fun startFocusAndMeteringAsync(
+        aeRegions: List<MeteringRectangle>,
+        afRegions: List<MeteringRectangle>,
+        awbRegions: List<MeteringRectangle>
+    ): Deferred<Result3A> {
+        throw NotImplementedError("Not implemented")
+    }
+
+    override fun close() {
+        useCaseSurfaceManager.stopAsync()
+        useCaseCameraGraphConfig.graph.close()
+    }
+}
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
index 0575219..dace968 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCamera.kt
@@ -106,7 +106,7 @@
 
     override fun close() {
         debug { "Closing $this" }
-        useCaseSurfaceManager.stop()
+        useCaseSurfaceManager.stopAsync()
         useCaseGraphConfig.graph.close()
     }
 
diff --git a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManager.kt b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManager.kt
index 2b3cfaa..2a66c56 100644
--- a/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManager.kt
+++ b/camera/camera-camera2-pipe-integration/src/main/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManager.kt
@@ -19,8 +19,11 @@
 package androidx.camera.camera2.pipe.integration.impl
 
 import android.view.Surface
+import androidx.annotation.GuardedBy
 import androidx.annotation.RequiresApi
 import androidx.camera.camera2.pipe.CameraGraph
+import androidx.camera.camera2.pipe.CameraPipe
+import androidx.camera.camera2.pipe.CameraSurfaceManager
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.core.Log
 import androidx.camera.camera2.pipe.integration.adapter.SessionConfigAdapter
@@ -29,6 +32,7 @@
 import androidx.camera.core.impl.utils.futures.Futures
 import androidx.concurrent.futures.await
 import javax.inject.Inject
+import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.Deferred
 import kotlinx.coroutines.async
 import kotlinx.coroutines.isActive
@@ -37,15 +41,27 @@
 private const val TIMEOUT_GET_SURFACE_IN_MS = 5_000L
 
 /**
- * Configure the [DeferrableSurface]s to the [CameraGraph].
+ * Configure the [DeferrableSurface]s to the [CameraGraph] and monitor the usage.
  */
 @UseCaseCameraScope
 class UseCaseSurfaceManager @Inject constructor(
     private val threads: UseCaseThreads,
-) {
+    private val cameraPipe: CameraPipe,
+) : CameraSurfaceManager.SurfaceListener {
+
+    private val lock = Any()
+
+    @GuardedBy("lock")
+    private val activeSurfaceMap = mutableMapOf<Surface, DeferrableSurface>()
+
+    @GuardedBy("lock")
+    private var configuredSurfaceMap: Map<Surface, DeferrableSurface>? = null
 
     private var setupSurfaceDeferred: Deferred<Unit>? = null
 
+    @GuardedBy("lock")
+    private var stopDeferred: CompletableDeferred<Unit>? = null
+
     /**
      * Async set up the Surfaces to the [CameraGraph]
      */
@@ -55,7 +71,10 @@
         surfaceToStreamMap: Map<DeferrableSurface, StreamId>,
         timeoutMillis: Long = TIMEOUT_GET_SURFACE_IN_MS,
     ): Deferred<Unit> {
-        setupSurfaceDeferred = threads.scope.async {
+        check(setupSurfaceDeferred == null)
+        check(synchronized(lock) { stopDeferred == null && configuredSurfaceMap == null })
+
+        return threads.scope.async {
             check(sessionConfigAdapter.isSessionConfigValid())
 
             val deferrableSurfaces = sessionConfigAdapter.deferrableSurfaces
@@ -70,6 +89,13 @@
             }
 
             if (areSurfacesValid(surfaces)) {
+                synchronized(lock) {
+                    configuredSurfaceMap = deferrableSurfaces.associateBy { deferrableSurface ->
+                        surfaces[deferrableSurfaces.indexOf(deferrableSurface)]!!
+                    }
+                    setSurfaceListener()
+                }
+
                 surfaceToStreamMap.forEach {
                     val stream = it.value
                     val surface = surfaces[deferrableSurfaces.indexOf(it.key)]
@@ -86,16 +112,65 @@
                     deferrableSurfaces[surfaces.indexOf(null)]
                 )
             }
+        }.also { completeDeferred ->
+            setupSurfaceDeferred = completeDeferred
+            completeDeferred.invokeOnCompletion {
+                setupSurfaceDeferred = null
+            }
         }
-
-        return setupSurfaceDeferred!!
     }
 
     /**
-     * Cancel the Surface set up.
+     * Cancel the Surface set up and stop the monitoring of Surface usage.
      */
-    fun stop() {
+    fun stopAsync(): Deferred<Unit> {
         setupSurfaceDeferred?.cancel()
+
+        return synchronized(lock) {
+            configuredSurfaceMap = null
+            stopDeferred = stopDeferred ?: CompletableDeferred<Unit>().apply {
+                invokeOnCompletion { synchronized(lock) { stopDeferred = null } }
+            }
+            stopDeferred!!
+        }.also {
+            tryClearSurfaceListener()
+        }
+    }
+
+    override fun onSurfaceActive(surface: Surface) {
+        synchronized(lock) {
+            configuredSurfaceMap?.get(surface)?.let {
+                if (!activeSurfaceMap.containsKey(surface)) {
+                    Log.debug { "SurfaceActive $it in ${this@UseCaseSurfaceManager}" }
+                    activeSurfaceMap[surface] = it
+                    it.incrementUseCount()
+                }
+            }
+        }
+    }
+
+    override fun onSurfaceInactive(surface: Surface) {
+        synchronized(lock) {
+            activeSurfaceMap.remove(surface)?.let {
+                Log.debug { "SurfaceInactive $it in ${this@UseCaseSurfaceManager}" }
+                it.decrementUseCount()
+                tryClearSurfaceListener()
+            }
+        }
+    }
+
+    private fun setSurfaceListener() {
+        cameraPipe.cameraSurfaceManager().addListener(this)
+    }
+
+    private fun tryClearSurfaceListener() {
+        synchronized(lock) {
+            if (activeSurfaceMap.isEmpty() && configuredSurfaceMap == null) {
+                Log.debug { "${this@UseCaseSurfaceManager} remove surface listener" }
+                cameraPipe.cameraSurfaceManager().removeListener(this)
+                stopDeferred?.complete(Unit)
+            }
+        }
     }
 
     private suspend fun getSurfaces(
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
index e8b6896..76187df 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseCameraTest.kt
@@ -18,6 +18,7 @@
 
 import android.hardware.camera2.CameraDevice
 import android.os.Build
+import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.adapter.CaptureConfigAdapter
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -30,6 +31,7 @@
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.testing.fakes.FakeUseCase
 import androidx.camera.testing.fakes.FakeUseCaseConfig
+import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.CoroutineScope
@@ -97,8 +99,13 @@
             )
         }
         val useCaseCamera = UseCaseCameraImpl(
-            fakeUseCaseGraphConfig, arrayListOf(fakeUseCase),
-            UseCaseSurfaceManager(useCaseThreads), requestControl
+            useCaseGraphConfig = fakeUseCaseGraphConfig,
+            useCases = arrayListOf(fakeUseCase),
+            useCaseSurfaceManager = UseCaseSurfaceManager(
+                useCaseThreads,
+                CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext()))
+            ),
+            requestControl = requestControl
         ).also {
             it.runningUseCases = setOf(fakeUseCase)
         }
diff --git a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManagerTest.kt b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManagerTest.kt
index 8b0f358..0a0fbcf 100644
--- a/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManagerTest.kt
+++ b/camera/camera-camera2-pipe-integration/src/test/java/androidx/camera/camera2/pipe/integration/impl/UseCaseSurfaceManagerTest.kt
@@ -19,6 +19,7 @@
 import android.hardware.camera2.CameraDevice
 import android.os.Build
 import android.view.Surface
+import androidx.camera.camera2.pipe.CameraPipe
 import androidx.camera.camera2.pipe.StreamId
 import androidx.camera.camera2.pipe.integration.adapter.FakeTestUseCase
 import androidx.camera.camera2.pipe.integration.adapter.RobolectricCameraPipeTestRunner
@@ -29,6 +30,7 @@
 import androidx.camera.core.impl.DeferrableSurface
 import androidx.camera.core.impl.SessionConfig
 import androidx.camera.testing.fakes.FakeUseCaseConfig
+import androidx.test.core.app.ApplicationProvider
 import androidx.testutils.MainDispatcherRule
 import com.google.common.truth.Truth.assertThat
 import com.google.common.util.concurrent.ListenableFuture
@@ -111,6 +113,7 @@
         // Act
         UseCaseSurfaceManager(
             useCaseThreads,
+            CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
         ).setupAsync(
             graph = fakeGraph,
             sessionConfigAdapter = SessionConfigAdapter(
@@ -162,6 +165,7 @@
         // Act
         UseCaseSurfaceManager(
             useCaseThreads,
+            CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
         ).setupAsync(
             graph = fakeGraph,
             sessionConfigAdapter = SessionConfigAdapter(
@@ -219,6 +223,7 @@
         // Act
         UseCaseSurfaceManager(
             useCaseThreads,
+            CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
         ).setupAsync(
             graph = fakeGraph,
             sessionConfigAdapter = SessionConfigAdapter(
@@ -278,6 +283,7 @@
         )
         val useCaseSurfaceManager = UseCaseSurfaceManager(
             useCaseThreads,
+            CameraPipe(CameraPipe.Config(ApplicationProvider.getApplicationContext())),
         )
         val deferred = useCaseSurfaceManager.setupAsync(
             graph = fakeGraph,
@@ -290,7 +296,7 @@
         neverCompleteDeferrableSurface.provideSurfaceIsCalledDeferred.await()
 
         // Act.
-        useCaseSurfaceManager.stop()
+        useCaseSurfaceManager.stopAsync()
 
         // Assert, verify no further error/setSurface for the stopped case.
         assertThat(deferred.isCancelled).isTrue()
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
index 32321f4..aaef6b0 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerDeviceTest.kt
@@ -272,7 +272,7 @@
         surfaceCombinationYuvPrivYuv.addSurfaceConfig(
             SurfaceConfig.create(
                 SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS
+                SurfaceConfig.ConfigSize.VGA
             )
         )
         surfaceCombinationYuvPrivYuv.addSurfaceConfig(
@@ -307,7 +307,7 @@
         surfaceCombinationYuvYuvYuv.addSurfaceConfig(
             SurfaceConfig.create(
                 SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS
+                SurfaceConfig.ConfigSize.VGA
             )
         )
         surfaceCombinationYuvYuvYuv.addSurfaceConfig(
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
new file mode 100644
index 0000000..00106c1
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/GuaranteedConfigurationsUtil.java
@@ -0,0 +1,438 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.camera2.internal;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.impl.SurfaceCombination;
+import androidx.camera.core.impl.SurfaceConfig;
+import androidx.camera.core.impl.SurfaceConfig.ConfigSize;
+import androidx.camera.core.impl.SurfaceConfig.ConfigType;
+
+import java.util.ArrayList;
+import java.util.List;
+
+/**
+ * Utility class for providing hardcoded sets of supported stream combinations based on the
+ * hardware level and capabilities of the device.
+ *
+ * <pre>
+ * @see <a href="https://developer.android.com/reference/android/hardware/camera2/CameraDevice">
+ *     CameraDevice</a>
+ * </pre>
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class GuaranteedConfigurationsUtil {
+
+    private GuaranteedConfigurationsUtil() {
+    }
+
+    /**
+     * Returns the at least supported stream combinations for legacy devices.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getLegacySupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, MAXIMUM)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination1);
+
+        // (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination2);
+
+        // (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination3);
+
+        // Below two combinations are all supported in the combination
+        // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination4);
+
+        // (YUV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination5);
+
+        // (PRIV, PREVIEW) + (PRIV, PREVIEW)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        combinationList.add(surfaceCombination6);
+
+        // (PRIV, PREVIEW) + (YUV, PREVIEW)
+        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        combinationList.add(surfaceCombination7);
+
+        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination8);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the at least supported stream combinations for limited-level devices
+     * in addition to those for legacy devices.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getLimitedSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, PREVIEW) + (PRIV, RECORD)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination1);
+
+        // (PRIV, PREVIEW) + (YUV, RECORD)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination2);
+
+        // (YUV, PREVIEW) + (YUV, RECORD)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination3);
+
+        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination4);
+
+        // (PRIV, PREVIEW) + (YUV, RECORD) + (JPEG, RECORD)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
+        combinationList.add(surfaceCombination5);
+
+        // (YUV, PREVIEW) + (YUV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination6);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the at least supported stream combinations for full-level devices
+     * in addition to those for limited-level and legacy devices.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getFullSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, PREVIEW) + (PRIV, MAXIMUM)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination1);
+
+        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination2);
+
+        // (YUV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination3);
+
+        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination4);
+
+        // (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.VGA));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination5);
+
+        // (YUV, ANALYSIS) + (YUV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.VGA));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination6);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the at least supported stream combinations for RAW-capability devices
+     * on both full and limited devices.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getRAWSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination1);
+
+        // (PRIV, PREVIEW) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination2);
+
+        // (YUV, PREVIEW) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination3);
+
+        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination4.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination4);
+
+        // (PRIV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination5.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination5);
+
+        // (YUV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination6.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination6);
+
+        // (PRIV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        surfaceCombination7.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination7);
+
+        // (YUV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        surfaceCombination8.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination8);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the at least supported stream combinations for BURST-capability devices
+     * in addition to those for limited device. Note that all FULL-level devices support the
+     * BURST capability, and the below list is a strict subset of the list for FULL-level
+     * devices, so this table is only relevant for LIMITED-level devices that support the
+     * BURST_CAPTURE capability.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getBurstSupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, PREVIEW) + (PRIV, MAXIMUM)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination1);
+
+        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination2);
+
+        // (YUV, PREVIEW) + (YUV, MAXIMUM)
+        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
+        surfaceCombination3.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination3);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the at least supported stream combinations for level-3 devices
+     * in addition to tje combinations for full and for RAW capability.
+     */
+    @NonNull
+    public static List<SurfaceCombination> getLevel3SupportedCombinationList() {
+        List<SurfaceCombination> combinationList = new ArrayList<>();
+
+        // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.VGA));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
+        surfaceCombination1.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination1);
+
+        // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
+        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.VGA));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
+        surfaceCombination2.addSurfaceConfig(
+                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
+        combinationList.add(surfaceCombination2);
+
+        return combinationList;
+    }
+
+    /**
+     * Returns the supported stream combinations based on the hardware level and capabilities of
+     * the device.
+     */
+    @NonNull
+    public static List<SurfaceCombination> generateSupportedCombinationList(int hardwareLevel,
+            boolean isRawSupported, boolean isBurstCaptureSupported) {
+        List<SurfaceCombination> surfaceCombinations = new ArrayList<>();
+        surfaceCombinations.addAll(getLegacySupportedCombinationList());
+
+        if (hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
+                || hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+                || hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
+            surfaceCombinations.addAll(getLimitedSupportedCombinationList());
+        }
+
+        if (hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
+                || hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
+            surfaceCombinations.addAll(getFullSupportedCombinationList());
+        }
+
+        if (isRawSupported) {
+            surfaceCombinations.addAll(getRAWSupportedCombinationList());
+        }
+
+        if (isBurstCaptureSupported
+                && hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED) {
+            surfaceCombinations.addAll(getBurstSupportedCombinationList());
+        }
+
+        if (hardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
+            surfaceCombinations.addAll(getLevel3SupportedCombinationList());
+        }
+        return surfaceCombinations;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
index 642e2ad..3234d7c 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/SupportedSurfaceCombination.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2019 The Android Open Source Project
+ * 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.
@@ -16,6 +16,9 @@
 
 package androidx.camera.camera2.internal;
 
+import static androidx.camera.core.internal.utils.SizeUtil.VGA_SIZE;
+import static androidx.camera.core.internal.utils.SizeUtil.getArea;
+
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
@@ -48,18 +51,16 @@
 import androidx.camera.core.impl.ImageOutputConfig;
 import androidx.camera.core.impl.SurfaceCombination;
 import androidx.camera.core.impl.SurfaceConfig;
-import androidx.camera.core.impl.SurfaceConfig.ConfigSize;
-import androidx.camera.core.impl.SurfaceConfig.ConfigType;
 import androidx.camera.core.impl.SurfaceSizeDefinition;
 import androidx.camera.core.impl.UseCaseConfig;
 import androidx.camera.core.impl.utils.CameraOrientationUtil;
 import androidx.camera.core.impl.utils.CompareSizesByArea;
+import androidx.camera.core.internal.utils.AspectRatioUtil;
 import androidx.core.util.Preconditions;
 
 import java.util.ArrayList;
 import java.util.Arrays;
 import java.util.Collections;
-import java.util.Comparator;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Map;
@@ -76,11 +77,9 @@
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 final class SupportedSurfaceCombination {
     private static final String TAG = "SupportedSurfaceCombination";
-    private static final Size DEFAULT_SIZE = new Size(640, 480);
     private static final Size ZERO_SIZE = new Size(0, 0);
     private static final Size QUALITY_1080P_SIZE = new Size(1920, 1080);
     private static final Size QUALITY_480P_SIZE = new Size(720, 480);
-    private static final int ALIGN16 = 16;
     private static final Rational ASPECT_RATIO_4_3 = new Rational(4, 3);
     private static final Rational ASPECT_RATIO_3_4 = new Rational(3, 4);
     private static final Rational ASPECT_RATIO_16_9 = new Rational(16, 9);
@@ -98,7 +97,8 @@
     private final Map<Integer, List<Size>> mExcludedSizeListCache = new HashMap<>();
     private boolean mIsRawSupported = false;
     private boolean mIsBurstCaptureSupported = false;
-    private SurfaceSizeDefinition mSurfaceSizeDefinition;
+    @VisibleForTesting
+    SurfaceSizeDefinition mSurfaceSizeDefinition;
     private Map<Integer, Size[]> mOutputSizesCache = new HashMap<>();
     @NonNull
     private final DisplayInfoManager mDisplayInfoManager;
@@ -125,6 +125,21 @@
         } catch (CameraAccessExceptionCompat e) {
             throw CameraUnavailableExceptionHelper.createFrom(e);
         }
+
+        int[] availableCapabilities =
+                mCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
+
+        if (availableCapabilities != null) {
+            for (int capability : availableCapabilities) {
+                if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) {
+                    mIsRawSupported = true;
+                } else if (capability
+                        == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
+                    mIsBurstCaptureSupported = true;
+                }
+            }
+        }
+
         generateSupportedCombinationList();
         generateSurfaceSizeDefinition();
         checkCustomization();
@@ -171,29 +186,7 @@
      * @return new {@link SurfaceConfig} object
      */
     SurfaceConfig transformSurfaceConfig(int imageFormat, Size size) {
-        ConfigType configType = getConfigType(imageFormat);
-        ConfigSize configSize = ConfigSize.NOT_SUPPORT;
-
-        Size maxSize = fetchMaxSize(imageFormat);
-
-        // Compare with surface size definition to determine the surface configuration size
-        if (size.getWidth() * size.getHeight()
-                <= mSurfaceSizeDefinition.getAnalysisSize().getWidth()
-                * mSurfaceSizeDefinition.getAnalysisSize().getHeight()) {
-            configSize = ConfigSize.ANALYSIS;
-        } else if (size.getWidth() * size.getHeight()
-                <= mSurfaceSizeDefinition.getPreviewSize().getWidth()
-                * mSurfaceSizeDefinition.getPreviewSize().getHeight()) {
-            configSize = ConfigSize.PREVIEW;
-        } else if (size.getWidth() * size.getHeight()
-                <= mSurfaceSizeDefinition.getRecordSize().getWidth()
-                * mSurfaceSizeDefinition.getRecordSize().getHeight()) {
-            configSize = ConfigSize.RECORD;
-        } else if (size.getWidth() * size.getHeight() <= maxSize.getWidth() * maxSize.getHeight()) {
-            configSize = ConfigSize.MAXIMUM;
-        }
-
-        return SurfaceConfig.create(configType, configSize);
+        return SurfaceConfig.transformSurfaceConfig(imageFormat, size, mSurfaceSizeDefinition);
     }
 
     /**
@@ -222,8 +215,9 @@
         // supported combination first
         for (UseCaseConfig<?> useCaseConfig : newUseCaseConfigs) {
             surfaceConfigs.add(
-                    transformSurfaceConfig(useCaseConfig.getInputFormat(),
-                            new Size(640, 480)));
+                    SurfaceConfig.transformSurfaceConfig(useCaseConfig.getInputFormat(),
+                            new Size(640, 480),
+                            mSurfaceSizeDefinition));
         }
 
         if (!checkSupported(surfaceConfigs)) {
@@ -235,7 +229,9 @@
         }
 
         // Get the index order list by the use case priority for finding stream configuration
-        List<Integer> useCasesPriorityOrder = getUseCasesPriorityOrder(newUseCaseConfigs);
+        List<Integer> useCasesPriorityOrder =
+                getUseCasesPriorityOrder(
+                        newUseCaseConfigs);
         List<List<Size>> supportedOutputSizesList = new ArrayList<>();
 
         // Collect supported output sizes for all use cases
@@ -247,7 +243,8 @@
 
         // Get all possible size arrangements
         List<List<Size>> allPossibleSizeArrangements =
-                getAllPossibleSizeArrangements(supportedOutputSizesList);
+                getAllPossibleSizeArrangements(
+                        supportedOutputSizesList);
 
         Map<UseCaseConfig<?>, Size> suggestedResolutionsMap = null;
         // Transform use cases to SurfaceConfig list and find the first (best) workable combination
@@ -263,7 +260,9 @@
                 Size size = possibleSizeList.get(i);
                 UseCaseConfig<?> newUseCase =
                         newUseCaseConfigs.get(useCasesPriorityOrder.get(i));
-                surfaceConfigList.add(transformSurfaceConfig(newUseCase.getInputFormat(), size));
+                surfaceConfigList.add(
+                        SurfaceConfig.transformSurfaceConfig(newUseCase.getInputFormat(), size,
+                                mSurfaceSizeDefinition));
             }
 
             // Check whether the SurfaceConfig combination can be supported
@@ -336,11 +335,6 @@
         return outputRatio;
     }
 
-    @VisibleForTesting
-    SurfaceSizeDefinition getSurfaceSizeDefinition() {
-        return mSurfaceSizeDefinition;
-    }
-
     private Size fetchMaxSize(int imageFormat) {
         Size size = mMaxSizeCache.get(imageFormat);
         if (size != null) {
@@ -409,8 +403,8 @@
         Arrays.sort(outputSizes, new CompareSizesByArea(true));
 
         Size targetSize = getTargetSize(imageOutputConfig);
-        Size minSize = DEFAULT_SIZE;
-        int defaultSizeArea = getArea(DEFAULT_SIZE);
+        Size minSize = VGA_SIZE;
+        int defaultSizeArea = getArea(VGA_SIZE);
         int maxSizeArea = getArea(maxSize);
         // When maxSize is smaller than 640x480, set minSize as 0x0. It means the min size bound
         // will be ignored. Otherwise, set the minimal size according to min(DEFAULT_SIZE,
@@ -472,7 +466,7 @@
             // Sort the aspect ratio key set by the target aspect ratio.
             List<Rational> aspectRatios = new ArrayList<>(aspectRatioSizeListMap.keySet());
             Collections.sort(aspectRatios,
-                    new CompareAspectRatiosByDistanceToTargetRatio(aspectRatio));
+                    new AspectRatioUtil.CompareAspectRatiosByDistanceToTargetRatio(aspectRatio));
 
             // Put available sizes into final result list by aspect ratio distance to target ratio.
             for (Rational rational : aspectRatios) {
@@ -487,35 +481,12 @@
         }
 
         supportedResolutions = mResolutionCorrector.insertOrPrioritize(
-                getConfigType(config.getInputFormat()),
+                SurfaceConfig.getConfigType(config.getInputFormat()),
                 supportedResolutions);
 
         return supportedResolutions;
     }
 
-
-    /**
-     * Gets {@link ConfigType} from image format.
-     *
-     * <p> PRIV refers to any target whose available sizes are found using
-     * StreamConfigurationMap.getOutputSizes(Class) with no direct application-visible format,
-     * YUV refers to a target Surface using the ImageFormat.YUV_420_888 format, JPEG refers to
-     * the ImageFormat.JPEG format, and RAW refers to the ImageFormat.RAW_SENSOR format.
-     */
-    @NonNull
-    private SurfaceConfig.ConfigType getConfigType(int imageFormat) {
-
-        if (imageFormat == ImageFormat.YUV_420_888) {
-            return SurfaceConfig.ConfigType.YUV;
-        } else if (imageFormat == ImageFormat.JPEG) {
-            return SurfaceConfig.ConfigType.JPEG;
-        } else if (imageFormat == ImageFormat.RAW_SENSOR) {
-            return SurfaceConfig.ConfigType.RAW;
-        } else {
-            return SurfaceConfig.ConfigType.PRIV;
-        }
-    }
-
     @Nullable
     private Size getTargetSize(@NonNull ImageOutputConfig imageOutputConfig) {
         int targetRotation = imageOutputConfig.getTargetRotation(Surface.ROTATION_0);
@@ -568,64 +539,6 @@
                 : true;
     }
 
-    static boolean hasMatchingAspectRatio(Size resolution, Rational aspectRatio) {
-        boolean isMatch = false;
-        if (aspectRatio == null) {
-            isMatch = false;
-        } else if (aspectRatio.equals(
-                new Rational(resolution.getWidth(), resolution.getHeight()))) {
-            isMatch = true;
-        } else if (getArea(resolution) >= getArea(DEFAULT_SIZE)) {
-            // Only do mod 16 calculation if the size is equal to or larger than 640x480. It is
-            // because the aspect ratio will be affected critically by mod 16 calculation if the
-            // size is small. It may result in unexpected outcome such like 256x144 will be
-            // considered as 18.5:9.
-            isMatch = isPossibleMod16FromAspectRatio(resolution, aspectRatio);
-        }
-        return isMatch;
-    }
-
-    /**
-     * For codec performance improvement, OEMs may make the supported sizes to be mod16 alignment
-     * . It means that the width or height of the supported size will be multiple of 16. The
-     * result number after applying mod16 alignment can be the larger or smaller number that is
-     * multiple of 16 and is closest to the original number. For example, a standard 16:9
-     * supported size is 1920x1080. It may become 1920x1088 on some devices because 1088 is
-     * multiple of 16. This function uses the target aspect ratio to calculate the possible
-     * original width or height inversely. And then, checks whether the possibly original width or
-     * height is in the range that the mod16 aligned height or width can support.
-     */
-    private static boolean isPossibleMod16FromAspectRatio(Size resolution, Rational aspectRatio) {
-        int width = resolution.getWidth();
-        int height = resolution.getHeight();
-
-        Rational invAspectRatio = new Rational(/* numerator= */aspectRatio.getDenominator(),
-                /* denominator= */aspectRatio.getNumerator());
-        if (width % 16 == 0 && height % 16 == 0) {
-            return ratioIntersectsMod16Segment(Math.max(0, height - ALIGN16), width, aspectRatio)
-                    || ratioIntersectsMod16Segment(Math.max(0, width - ALIGN16), height,
-                    invAspectRatio);
-        } else if (width % 16 == 0) {
-            return ratioIntersectsMod16Segment(height, width, aspectRatio);
-        } else if (height % 16 == 0) {
-            return ratioIntersectsMod16Segment(width, height, invAspectRatio);
-        }
-        return false;
-    }
-
-    private static int getArea(Size size) {
-        return size.getWidth() * size.getHeight();
-    }
-
-    private static boolean ratioIntersectsMod16Segment(int height, int mod16Width,
-            Rational aspectRatio) {
-        Preconditions.checkArgument(mod16Width % 16 == 0);
-        double aspectRatioWidth =
-                height * aspectRatio.getNumerator() / (double) aspectRatio.getDenominator();
-        return aspectRatioWidth > Math.max(0, mod16Width - ALIGN16) && aspectRatioWidth < (
-                mod16Width + ALIGN16);
-    }
-
     private Map<Rational, List<Size>> groupSizesByAspectRatio(List<Size> sizes) {
         Map<Rational, List<Size>> aspectRatioSizeListMap = new HashMap<>();
 
@@ -642,7 +555,7 @@
             for (Rational key : aspectRatioSizeListMap.keySet()) {
                 // Put the size into all groups that is matched in mod16 condition since a size
                 // may match multiple aspect ratio in mod16 algorithm.
-                if (hasMatchingAspectRatio(outputSize, key)) {
+                if (AspectRatioUtil.hasMatchingAspectRatio(outputSize, key)) {
                     matchedKey = key;
 
                     List<Size> sizeList = aspectRatioSizeListMap.get(matchedKey);
@@ -854,371 +767,10 @@
         return Collections.max(Arrays.asList(outputSizes), new CompareSizesByArea());
     }
 
-    List<SurfaceCombination> getLegacySupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (PRIV, MAXIMUM)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination1);
-
-        // (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination2);
-
-        // (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination3);
-
-        // Below two combinations are all supported in the combination
-        // (PRIV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination4);
-
-        // (YUV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination5);
-
-        // (PRIV, PREVIEW) + (PRIV, PREVIEW)
-        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        combinationList.add(surfaceCombination6);
-
-        // (PRIV, PREVIEW) + (YUV, PREVIEW)
-        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
-        surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        combinationList.add(surfaceCombination7);
-
-        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination8);
-
-        return combinationList;
-    }
-
-    List<SurfaceCombination> getLimitedSupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (PRIV, PREVIEW) + (PRIV, RECORD)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination1);
-
-        // (PRIV, PREVIEW) + (YUV, RECORD)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination2);
-
-        // (YUV, PREVIEW) + (YUV, RECORD)
-        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination3);
-
-        // (PRIV, PREVIEW) + (PRIV, RECORD) + (JPEG, RECORD)
-        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.RECORD));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination4);
-
-        // (PRIV, PREVIEW) + (YUV, RECORD) + (JPEG, RECORD)
-        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.RECORD));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.RECORD));
-        combinationList.add(surfaceCombination5);
-
-        // (YUV, PREVIEW) + (YUV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination6);
-
-        return combinationList;
-    }
-
-    List<SurfaceCombination> getFullSupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (PRIV, PREVIEW) + (PRIV, MAXIMUM)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination1);
-
-        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination2);
-
-        // (YUV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination3);
-
-        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (JPEG, MAXIMUM)
-        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination4);
-
-        // (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination5);
-
-        // (YUV, ANALYSIS) + (YUV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination6);
-
-        return combinationList;
-    }
-
-    List<SurfaceCombination> getRAWSupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination1);
-
-        // (PRIV, PREVIEW) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination2);
-
-        // (YUV, PREVIEW) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination3);
-
-        // (PRIV, PREVIEW) + (PRIV, PREVIEW) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination4 = new SurfaceCombination();
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination4.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination4);
-
-        // (PRIV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination5 = new SurfaceCombination();
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination5.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination5);
-
-        // (YUV, PREVIEW) + (YUV, PREVIEW) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination6 = new SurfaceCombination();
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination6.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination6);
-
-        // (PRIV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination7 = new SurfaceCombination();
-        surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        surfaceCombination7.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination7);
-
-        // (YUV, PREVIEW) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination8 = new SurfaceCombination();
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        surfaceCombination8.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination8);
-
-        return combinationList;
-    }
-
-    List<SurfaceCombination> getBurstSupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (PRIV, PREVIEW) + (PRIV, MAXIMUM)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination1);
-
-        // (PRIV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination2);
-
-        // (YUV, PREVIEW) + (YUV, MAXIMUM)
-        SurfaceCombination surfaceCombination3 = new SurfaceCombination();
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.PREVIEW));
-        surfaceCombination3.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination3);
-
-        return combinationList;
-    }
-
-    List<SurfaceCombination> getLevel3SupportedCombinationList() {
-        List<SurfaceCombination> combinationList = new ArrayList<>();
-
-        // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (YUV, MAXIMUM) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination1 = new SurfaceCombination();
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.ANALYSIS));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.MAXIMUM));
-        surfaceCombination1.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination1);
-
-        // (PRIV, PREVIEW) + (PRIV, ANALYSIS) + (JPEG, MAXIMUM) + (RAW, MAXIMUM)
-        SurfaceCombination surfaceCombination2 = new SurfaceCombination();
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.PREVIEW));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.PRIV, ConfigSize.ANALYSIS));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.MAXIMUM));
-        surfaceCombination2.addSurfaceConfig(
-                SurfaceConfig.create(ConfigType.RAW, ConfigSize.MAXIMUM));
-        combinationList.add(surfaceCombination2);
-
-        return combinationList;
-    }
-
     private void generateSupportedCombinationList() {
-        mSurfaceCombinations.addAll(getLegacySupportedCombinationList());
-
-        if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED
-                || mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
-                || mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
-            mSurfaceCombinations.addAll(getLimitedSupportedCombinationList());
-        }
-
-        if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL
-                || mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
-            mSurfaceCombinations.addAll(getFullSupportedCombinationList());
-        }
-
-        int[] availableCapabilities =
-                mCharacteristics.get(CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES);
-
-        if (availableCapabilities != null) {
-            for (int capability : availableCapabilities) {
-                if (capability == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_RAW) {
-                    mIsRawSupported = true;
-                } else if (capability
-                        == CameraCharacteristics.REQUEST_AVAILABLE_CAPABILITIES_BURST_CAPTURE) {
-                    mIsBurstCaptureSupported = true;
-                }
-            }
-        }
-
-        if (mIsRawSupported) {
-            mSurfaceCombinations.addAll(getRAWSupportedCombinationList());
-        }
-
-        if (mIsBurstCaptureSupported
-                && mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LIMITED) {
-            mSurfaceCombinations.addAll(getBurstSupportedCombinationList());
-        }
-
-        if (mHardwareLevel == CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_3) {
-            mSurfaceCombinations.addAll(getLevel3SupportedCombinationList());
-        }
+        mSurfaceCombinations.addAll(
+                GuaranteedConfigurationsUtil.generateSupportedCombinationList(mHardwareLevel,
+                        mIsRawSupported, mIsBurstCaptureSupported));
 
         mSurfaceCombinations.addAll(
                 mExtraSupportedSurfaceCombinationsContainer.get(mCameraId, mHardwareLevel));
@@ -1360,27 +912,4 @@
 
         return excludedSizes;
     }
-
-    /** Comparator based on how close they are to the target aspect ratio. */
-    @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-    static final class CompareAspectRatiosByDistanceToTargetRatio implements Comparator<Rational> {
-        private Rational mTargetRatio;
-
-        CompareAspectRatiosByDistanceToTargetRatio(Rational targetRatio) {
-            mTargetRatio = targetRatio;
-        }
-
-        @Override
-        public int compare(Rational lhs, Rational rhs) {
-            if (lhs.equals(rhs)) {
-                return 0;
-            }
-
-            final Float lhsRatioDelta = Math.abs(lhs.floatValue() - mTargetRatio.floatValue());
-            final Float rhsRatioDelta = Math.abs(rhs.floatValue() - mTargetRatio.floatValue());
-
-            int result = (int) Math.signum(lhsRatioDelta - rhsRatioDelta);
-            return result;
-        }
-    }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
index 11364a4..4a30df9 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ExtraSupportedSurfaceCombinationsQuirk.java
@@ -275,7 +275,7 @@
         // (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
         SurfaceCombination surfaceCombination = new SurfaceCombination();
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS));
+                SurfaceConfig.ConfigSize.VGA));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
                 SurfaceConfig.ConfigSize.PREVIEW));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
@@ -289,7 +289,7 @@
         // (YUV, ANALYSIS) + (YUV, PREVIEW) + (YUV, MAXIMUM)
         SurfaceCombination surfaceCombination = new SurfaceCombination();
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS));
+                SurfaceConfig.ConfigSize.VGA));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
                 SurfaceConfig.ConfigSize.PREVIEW));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManagerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManagerTest.java
index bac238d..93ef831 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManagerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2DeviceSurfaceManagerTest.java
@@ -180,7 +180,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLegacySupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLegacySupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -199,7 +199,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -218,7 +218,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -237,7 +237,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -256,7 +256,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -275,7 +275,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -294,7 +294,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -313,7 +313,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -332,7 +332,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -351,7 +351,7 @@
                         mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -430,7 +430,7 @@
         SurfaceConfig surfaceConfig = mSurfaceManager.transformSurfaceConfig(
                 LEGACY_CAMERA_ID, ImageFormat.YUV_420_888, mAnalysisSize);
         SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS);
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.VGA);
         assertEquals(expectedSurfaceConfig, surfaceConfig);
     }
 
@@ -462,23 +462,12 @@
     }
 
     @Test
-    public void transformSurfaceConfigWithYUVNotSupportSize() {
-        SurfaceConfig surfaceConfig = mSurfaceManager.transformSurfaceConfig(
-                LEGACY_CAMERA_ID,
-                ImageFormat.YUV_420_888,
-                new Size(mMaximumSize.getWidth() + 1, mMaximumSize.getHeight() + 1));
-        SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.NOT_SUPPORT);
-        assertEquals(expectedSurfaceConfig, surfaceConfig);
-    }
-
-    @Test
     public void transformSurfaceConfigWithJPEGAnalysisSize() {
         SurfaceConfig surfaceConfig =
                 mSurfaceManager.transformSurfaceConfig(
                         LEGACY_CAMERA_ID, ImageFormat.JPEG, mAnalysisSize);
         SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(SurfaceConfig.ConfigType.JPEG, ConfigSize.ANALYSIS);
+                SurfaceConfig.create(SurfaceConfig.ConfigType.JPEG, ConfigSize.VGA);
         assertEquals(expectedSurfaceConfig, surfaceConfig);
     }
 
@@ -512,18 +501,6 @@
         assertEquals(expectedSurfaceConfig, surfaceConfig);
     }
 
-    @Test
-    public void transformSurfaceConfigWithJPEGNotSupportSize() {
-        SurfaceConfig surfaceConfig =
-                mSurfaceManager.transformSurfaceConfig(
-                        LEGACY_CAMERA_ID,
-                        ImageFormat.JPEG,
-                        new Size(mMaximumSize.getWidth() + 1, mMaximumSize.getHeight() + 1));
-        SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.NOT_SUPPORT);
-        assertEquals(expectedSurfaceConfig, surfaceConfig);
-    }
-
     private void setupCamera() {
         mCameraFactory = new FakeCameraFactory();
 
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
index fd72902..82399e1 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSurfaceCombinationTest.java
@@ -16,8 +16,6 @@
 
 package androidx.camera.camera2.internal;
 
-import static androidx.camera.camera2.internal.SupportedSurfaceCombination.hasMatchingAspectRatio;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.mockito.ArgumentMatchers.anyInt;
@@ -226,7 +224,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLegacySupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLegacySupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -243,7 +241,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLegacySupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLegacySupportedCombinationList();
 
         boolean isSupported =
                 isAllSubConfigListSupported(supportedSurfaceCombination, combinationList);
@@ -258,7 +256,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -275,7 +273,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -292,7 +290,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -309,7 +307,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -326,7 +324,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         boolean isSupported =
                 isAllSubConfigListSupported(supportedSurfaceCombination, combinationList);
@@ -341,7 +339,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -358,7 +356,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -375,7 +373,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -392,7 +390,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         boolean isSupported =
                 isAllSubConfigListSupported(supportedSurfaceCombination, combinationList);
@@ -407,7 +405,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -425,7 +423,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLimitedSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLimitedSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -443,7 +441,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLegacySupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLegacySupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -461,7 +459,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getFullSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getFullSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -479,7 +477,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getRAWSupportedCombinationList();
+                GuaranteedConfigurationsUtil.getRAWSupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -496,7 +494,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         for (SurfaceCombination combination : combinationList) {
             boolean isSupported =
@@ -513,7 +511,7 @@
                 mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
 
         List<SurfaceCombination> combinationList =
-                supportedSurfaceCombination.getLevel3SupportedCombinationList();
+                GuaranteedConfigurationsUtil.getLevel3SupportedCombinationList();
 
         boolean isSupported =
                 isAllSubConfigListSupported(supportedSurfaceCombination, combinationList);
@@ -844,10 +842,10 @@
 
         androidx.camera.core.VideoCapture videoCapture =
                 new androidx.camera.core.VideoCapture.Builder()
-                // Override the default max resolution in VideoCapture
-                .setMaxResolution(mRecordSize)
-                .setSupportedResolutions(videoResolutionsPairs)
-                .build();
+                        // Override the default max resolution in VideoCapture
+                        .setMaxResolution(mRecordSize)
+                        .setSupportedResolutions(videoResolutionsPairs)
+                        .build();
         Preview preview = new Preview.Builder()
                 .setSupportedResolutions(previewResolutionsPairs)
                 .build();
@@ -905,8 +903,8 @@
                 .build();
         androidx.camera.core.VideoCapture videoCapture =
                 new androidx.camera.core.VideoCapture.Builder()
-                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
-                .build();
+                        .setTargetAspectRatio(AspectRatio.RATIO_16_9)
+                        .build();
         Preview preview = new Preview.Builder()
                 .setTargetAspectRatio(AspectRatio.RATIO_16_9)
                 .build();
@@ -1104,44 +1102,6 @@
     }
 
     @Test
-    public void setTargetAspectRatioForMixedUseCases() throws CameraUnavailableException {
-        setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_FULL);
-        SupportedSurfaceCombination supportedSurfaceCombination = new SupportedSurfaceCombination(
-                mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
-
-        Preview preview = new Preview.Builder()
-                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
-                .build();
-        ImageCapture imageCapture = new ImageCapture.Builder()
-                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
-                .build();
-        ImageAnalysis imageAnalysis = new ImageAnalysis.Builder()
-                .setTargetAspectRatio(AspectRatio.RATIO_16_9)
-                .build();
-
-        List<UseCase> useCases = new ArrayList<>();
-        useCases.add(imageCapture);
-        useCases.add(preview);
-        useCases.add(imageAnalysis);
-        Map<UseCase, UseCaseConfig<?>> useCaseToConfigMap =
-                Configs.useCaseConfigMapWithDefaultSettingsFromUseCaseList(
-                        mCameraFactory.getCamera(CAMERA_ID).getCameraInfoInternal(),
-                        useCases,
-                        mUseCaseConfigFactory);
-        Map<UseCaseConfig<?>, Size> suggestedResolutionMap =
-                supportedSurfaceCombination.getSuggestedResolutions(Collections.emptyList(),
-                        new ArrayList<>(useCaseToConfigMap.values()));
-
-        Size previewSize = suggestedResolutionMap.get(useCaseToConfigMap.get(preview));
-        Size imageCaptureSize = suggestedResolutionMap.get(useCaseToConfigMap.get(imageCapture));
-        Size imageAnalysisSize = suggestedResolutionMap.get(useCaseToConfigMap.get(imageAnalysis));
-
-        assertThat(hasMatchingAspectRatio(previewSize, ASPECT_RATIO_16_9)).isTrue();
-        assertThat(hasMatchingAspectRatio(imageCaptureSize, ASPECT_RATIO_16_9)).isTrue();
-        assertThat(hasMatchingAspectRatio(imageAnalysisSize, ASPECT_RATIO_16_9)).isTrue();
-    }
-
-    @Test
     public void throwsWhenSetBothTargetResolutionAndAspectRatioForDifferentUseCases() {
         setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
 
@@ -1198,8 +1158,8 @@
                 .build();
         androidx.camera.core.VideoCapture videoCapture =
                 new androidx.camera.core.VideoCapture.Builder()
-                .setSupportedResolutions(formatResolutionsPairList)
-                .build();
+                        .setSupportedResolutions(formatResolutionsPairList)
+                        .build();
         Preview preview = new Preview.Builder()
                 .setSupportedResolutions(formatResolutionsPairList)
                 .build();
@@ -1279,7 +1239,7 @@
                 supportedSurfaceCombination.transformSurfaceConfig(
                         ImageFormat.YUV_420_888, mAnalysisSize);
         SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.ANALYSIS);
+                SurfaceConfig.create(ConfigType.YUV, ConfigSize.VGA);
         assertThat(surfaceConfig).isEqualTo(expectedSurfaceConfig);
     }
 
@@ -1323,20 +1283,6 @@
     }
 
     @Test
-    public void transformSurfaceConfigWithYUVNotSupportSize() throws CameraUnavailableException {
-        setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
-        SupportedSurfaceCombination supportedSurfaceCombination = new SupportedSurfaceCombination(
-                mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
-        SurfaceConfig surfaceConfig =
-                supportedSurfaceCombination.transformSurfaceConfig(
-                        ImageFormat.YUV_420_888,
-                        new Size(mMaximumSize.getWidth() + 1, mMaximumSize.getHeight() + 1));
-        SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.YUV, ConfigSize.NOT_SUPPORT);
-        assertThat(surfaceConfig).isEqualTo(expectedSurfaceConfig);
-    }
-
-    @Test
     public void transformSurfaceConfigWithJPEGAnalysisSize() throws CameraUnavailableException {
         setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
         SupportedSurfaceCombination supportedSurfaceCombination = new SupportedSurfaceCombination(
@@ -1345,7 +1291,7 @@
                 supportedSurfaceCombination.transformSurfaceConfig(
                         ImageFormat.JPEG, mAnalysisSize);
         SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.ANALYSIS);
+                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.VGA);
         assertThat(surfaceConfig).isEqualTo(expectedSurfaceConfig);
     }
 
@@ -1389,20 +1335,6 @@
     }
 
     @Test
-    public void transformSurfaceConfigWithJPEGNotSupportSize() throws CameraUnavailableException {
-        setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
-        SupportedSurfaceCombination supportedSurfaceCombination = new SupportedSurfaceCombination(
-                mContext, CAMERA_ID, mCameraManagerCompat, mMockCamcorderProfileHelper);
-        SurfaceConfig surfaceConfig =
-                supportedSurfaceCombination.transformSurfaceConfig(
-                        ImageFormat.JPEG,
-                        new Size(mMaximumSize.getWidth() + 1, mMaximumSize.getHeight() + 1));
-        SurfaceConfig expectedSurfaceConfig =
-                SurfaceConfig.create(ConfigType.JPEG, ConfigSize.NOT_SUPPORT);
-        assertThat(surfaceConfig).isEqualTo(expectedSurfaceConfig);
-    }
-
-    @Test
     public void getMaximumSizeForImageFormat() throws CameraUnavailableException {
         setupCamera(CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY);
         SupportedSurfaceCombination supportedSurfaceCombination = new SupportedSurfaceCombination(
@@ -2393,7 +2325,7 @@
 
         // Checks the determined RECORD size
         assertThat(
-                supportedSurfaceCombination.getSurfaceSizeDefinition().getRecordSize()).isEqualTo(
+                supportedSurfaceCombination.mSurfaceSizeDefinition.getRecordSize()).isEqualTo(
                 mLegacyVideoMaximumVideoSize);
     }
 
@@ -2642,7 +2574,7 @@
                 };
 
         CameraXConfig cameraXConfig = CameraXConfig.Builder.fromConfig(
-                Camera2Config.defaultConfig())
+                        Camera2Config.defaultConfig())
                 .setDeviceSurfaceManagerProvider(surfaceManagerProvider)
                 .setCameraFactoryProvider((ignored0, ignored1, ignored2) -> mCameraFactory)
                 .build();
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
index 20a0527..b3043ad 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/workaround/ExtraSupportedSurfaceCombinationsContainerTest.java
@@ -131,7 +131,7 @@
         // (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
         SurfaceCombination surfaceCombination = new SurfaceCombination();
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS));
+                SurfaceConfig.ConfigSize.VGA));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
                 SurfaceConfig.ConfigSize.PREVIEW));
         surfaceCombination.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
@@ -143,7 +143,7 @@
         // (YUV, ANALYSIS) + (PRIV, PREVIEW) + (YUV, MAXIMUM)
         SurfaceCombination surfaceCombination1 = new SurfaceCombination();
         surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS));
+                SurfaceConfig.ConfigSize.VGA));
         surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.PRIV,
                 SurfaceConfig.ConfigSize.PREVIEW));
         surfaceCombination1.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
@@ -152,7 +152,7 @@
         // (YUV, ANALYSIS) + (YUV, PREVIEW) + (YUV, MAXIMUM)
         SurfaceCombination surfaceCombination2 = new SurfaceCombination();
         surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
-                SurfaceConfig.ConfigSize.ANALYSIS));
+                SurfaceConfig.ConfigSize.VGA));
         surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
                 SurfaceConfig.ConfigSize.PREVIEW));
         surfaceCombination2.addSurfaceConfig(SurfaceConfig.create(SurfaceConfig.ConfigType.YUV,
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
index 001238d..1ce7421 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ImageProcessingUtilTest.java
@@ -21,8 +21,7 @@
 import static androidx.camera.testing.ImageProxyUtil.createYUV420ImagePlanes;
 
 import static com.google.common.truth.Truth.assertThat;
-
-import static java.lang.Math.abs;
+import static com.google.common.truth.Truth.assertWithMessage;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
@@ -32,8 +31,12 @@
 import android.graphics.PixelFormat;
 import android.media.ImageWriter;
 
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
 import androidx.camera.testing.fakes.FakeImageInfo;
 import androidx.camera.testing.fakes.FakeImageProxy;
+import androidx.core.math.MathUtils;
+import androidx.core.util.Preconditions;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
 import androidx.test.filters.SmallTest;
@@ -45,6 +48,7 @@
 
 import java.io.ByteArrayOutputStream;
 import java.nio.ByteBuffer;
+import java.nio.IntBuffer;
 
 /**
  * Unit test for {@link ImageProcessingUtil}.
@@ -67,6 +71,10 @@
     private ByteBuffer mYRotatedBuffer;
     private ByteBuffer mURotatedBuffer;
     private ByteBuffer mVRotatedBuffer;
+    private static final int[] YUV_WHITE_STUDIO_SWING_BT601 = {/*y=*/235, /*u=*/128, /*v=*/128};
+    private static final int[] YUV_BLACK_STUDIO_SWING_BT601 = {/*y=*/16, /*u=*/128, /*v=*/128};
+    private static final int[] YUV_BLUE_STUDIO_SWING_BT601 = {/*y=*/16, /*u=*/240, /*v=*/128};
+    private static final int[] YUV_RED_STUDIO_SWING_BT601 = {/*y=*/16, /*u=*/128, /*v=*/240};
 
     private FakeImageProxy mYUVImageProxy;
     private SafeCloseImageReaderProxy mRGBImageReaderProxy;
@@ -140,27 +148,7 @@
         Bitmap bitmap = BitmapFactory.decodeByteArray(outputBytes, 0, outputBytes.length);
         assertThat(bitmap.getWidth()).isEqualTo(WIDTH);
         assertThat(bitmap.getHeight()).isEqualTo(HEIGHT);
-        assertBitmapColor(bitmap, Color.RED);
-    }
-
-    /**
-     * Asserts that the given {@link Bitmap} is almost the given color.
-     *
-     * <p>Loops through all the pixels and verify that they are the given color. A small error
-     * is allowed because JPEG is lossy.
-     */
-    private void assertBitmapColor(Bitmap bitmap, int color) {
-        for (int i = 0; i < bitmap.getWidth(); i++) {
-            for (int j = 0; j < bitmap.getHeight(); j++) {
-                int pixelColor = bitmap.getPixel(i, j);
-                // Compare the R, G and B of the pixel to the given color.
-                for (int shift = 16; shift > 0; shift -= 8) {
-                    int pixelRgb = (pixelColor >> shift) & 0xFF;
-                    int rgb = (color >> shift) & 0xFF;
-                    assertThat(abs(pixelRgb - rgb)).isLessThan(JPEG_ENCODE_ERROR_TOLERANCE);
-                }
-            }
-        }
+        assertBitmapColor(bitmap, Color.RED, JPEG_ENCODE_ERROR_TOLERANCE);
     }
 
     /**
@@ -171,7 +159,6 @@
         // Draw a solid color
         Canvas canvas = new Canvas(bitmap);
         canvas.drawColor(color);
-        canvas.save();
         // Encode to JPEG and return.
         ByteArrayOutputStream stream = new ByteArrayOutputStream();
         bitmap.compress(Bitmap.CompressFormat.JPEG, 100, stream);
@@ -362,4 +349,168 @@
         assertThat(yuvImageProxy.getWidth()).isEqualTo(HEIGHT);
         assertThat(yuvImageProxy.getHeight()).isEqualTo(WIDTH);
     }
+
+    @Test
+    public void yuvToRGBUsesBT601FullSwing() {
+        // Check that studio swing YUV colors do not scale to full range RGB colors
+        // White
+        int referenceColorRgb = yuvBt601FullSwingToRGB(
+                YUV_WHITE_STUDIO_SWING_BT601[0],
+                YUV_WHITE_STUDIO_SWING_BT601[1],
+                YUV_WHITE_STUDIO_SWING_BT601[2]);
+        assertSolidYUVColorConvertedToRGBMatchesReferenceRGB(YUV_WHITE_STUDIO_SWING_BT601,
+                referenceColorRgb);
+
+        // Black
+        referenceColorRgb = yuvBt601FullSwingToRGB(
+                YUV_BLACK_STUDIO_SWING_BT601[0],
+                YUV_BLACK_STUDIO_SWING_BT601[1],
+                YUV_BLACK_STUDIO_SWING_BT601[2]);
+        assertSolidYUVColorConvertedToRGBMatchesReferenceRGB(YUV_BLACK_STUDIO_SWING_BT601,
+                referenceColorRgb);
+
+        // Blue
+        referenceColorRgb = yuvBt601FullSwingToRGB(
+                YUV_BLUE_STUDIO_SWING_BT601[0],
+                YUV_BLUE_STUDIO_SWING_BT601[1],
+                YUV_BLUE_STUDIO_SWING_BT601[2]);
+        assertSolidYUVColorConvertedToRGBMatchesReferenceRGB(YUV_BLUE_STUDIO_SWING_BT601,
+                referenceColorRgb);
+
+        // Red
+        referenceColorRgb = yuvBt601FullSwingToRGB(
+                YUV_RED_STUDIO_SWING_BT601[0],
+                YUV_RED_STUDIO_SWING_BT601[1],
+                YUV_RED_STUDIO_SWING_BT601[2]);
+        assertSolidYUVColorConvertedToRGBMatchesReferenceRGB(YUV_RED_STUDIO_SWING_BT601,
+                referenceColorRgb);
+    }
+
+    private void assertSolidYUVColorConvertedToRGBMatchesReferenceRGB(int[] yuvColor,
+            int referenceColorRgb) {
+        ImageProxy yuvImageProxy = createYuvImageProxyWithPlanes();
+
+        fillYuvImageProxyWithYUVColor(yuvImageProxy, yuvColor[0], yuvColor[1], yuvColor[2]);
+
+        try (ImageProxy rgbImageProxy = ImageProcessingUtil.convertYUVToRGB(
+                yuvImageProxy,
+                mRGBImageReaderProxy,
+                mRgbConvertedBuffer,
+                /*rotation=*/0,
+                /* {
+            assertRGBImageProxyColor(rgbImageProxy, referenceColorRgb);
+        }
+    }
+
+    private static void fillYuvImageProxyWithYUVColor(@NonNull ImageProxy imageProxy,
+            @IntRange(from = 0, to = 255) int y,
+            @IntRange(from = 0, to = 255) int u,
+            @IntRange(from = 0, to = 255) int v) {
+        Preconditions.checkArgumentInRange(y, 0, 255, "y");
+        Preconditions.checkArgumentInRange(u, 0, 255, "u");
+        Preconditions.checkArgumentInRange(v, 0, 255, "v");
+
+        // Fill in planes
+        // Y plane
+        fillPlane(imageProxy.getPlanes()[0], (byte) y);
+        // U plane
+        fillPlane(imageProxy.getPlanes()[1], (byte) u);
+        // V plane
+        fillPlane(imageProxy.getPlanes()[2], (byte) v);
+    }
+
+    private static void fillPlane(@NonNull ImageProxy.PlaneProxy plane, byte value) {
+        ByteBuffer buffer = plane.getBuffer();
+        int pixelStride = plane.getPixelStride();
+        // Ignore row stride here, we don't need to be efficient, so we'll fill the padding also.
+        buffer.rewind();
+        while (buffer.hasRemaining()) {
+            int nextPosition = Math.min(buffer.capacity(), buffer.position() + pixelStride);
+            buffer.put(value);
+            buffer.position(nextPosition);
+        }
+    }
+
+    @NonNull
+    private ImageProxy createYuvImageProxyWithPlanes() {
+        FakeImageProxy yuvImageProxy = new FakeImageProxy(new FakeImageInfo());
+        yuvImageProxy.setWidth(WIDTH);
+        yuvImageProxy.setHeight(HEIGHT);
+        yuvImageProxy.setFormat(ImageFormat.YUV_420_888);
+
+        yuvImageProxy.setPlanes(createYUV420ImagePlanes(
+                WIDTH,
+                HEIGHT,
+                PIXEL_STRIDE_Y,
+                PIXEL_STRIDE_UV,
+                /*flipUV=*/true,
+                /*incrementValue=*/false));
+
+        return yuvImageProxy;
+    }
+
+    private static void assertRGBImageProxyColor(ImageProxy rgbImageProxy,
+            int referenceColorRgb) {
+        // Convert to Bitmap
+        ImageProxy.PlaneProxy pixelPlane = Preconditions.checkNotNull(rgbImageProxy).getPlanes()[0];
+        IntBuffer rgbPixelBuf = pixelPlane.getBuffer().asIntBuffer();
+        int rgbBufLength = rgbPixelBuf.capacity();
+        int[] rgbPixelArray = new int[rgbBufLength];
+        rgbPixelBuf.get(rgbPixelArray);
+
+        // the output array is in the order of ABGR (LITTLE ENDIAN) if BIG_ENDIAN is not set
+        for (int i = 0; i < rgbBufLength; i++) {
+            int alpha = (rgbPixelArray[i] >> 24) & 0xff;
+            int blue = (rgbPixelArray[i] >> 16) & 0xff;
+            int green = (rgbPixelArray[i] >> 8) & 0xff;
+            int red = (rgbPixelArray[i] >> 0) & 0xff;
+            rgbPixelArray[i] =
+                    (alpha & 0xff) << 24 | (red & 0xff) << 16 | (green & 0xff) << 8 | (blue & 0xff);
+        }
+
+        Bitmap rgbBitmap = Bitmap.createBitmap(rgbPixelArray, 0,
+                pixelPlane.getRowStride() / pixelPlane.getPixelStride(),
+                WIDTH, HEIGHT, Bitmap.Config.ARGB_8888);
+
+        assertBitmapColor(rgbBitmap, referenceColorRgb, 0);
+    }
+
+    /**
+     * Asserts that the given {@link Bitmap} is almost the given color.
+     *
+     * <p>Loops through all the pixels and verify that they are the given color. The color is
+     * assumed to be correct if the difference between each pixel's color and the provided color
+     * is at most {@code perChannelTolerance}.
+     */
+    private static void assertBitmapColor(Bitmap bitmap, int color, int perChannelTolerance) {
+        for (int i = 0; i < bitmap.getWidth(); i++) {
+            for (int j = 0; j < bitmap.getHeight(); j++) {
+                int pixelColor = bitmap.getPixel(i, j);
+
+                // Compare the R, G and B of the pixel to the given color.
+                for (int shift = 16; shift > 0; shift -= 8) {
+                    int pixelRgb = (pixelColor >> shift) & 0xFF;
+                    int rgb = (color >> shift) & 0xFF;
+                    assertWithMessage(String.format("Color from bitmap (#%08X) does not match "
+                                    + "reference color (#%08X) at col %d, row %d [tolerance: %d]",
+                            pixelColor, color, i, j, perChannelTolerance)
+                    ).that((double) pixelRgb).isWithin(perChannelTolerance).of(rgb);
+                }
+            }
+        }
+    }
+
+    private static int yuvBt601FullSwingToRGB(
+            @IntRange(from = 0, to = 255) int y,
+            @IntRange(from = 0, to = 255) int u,
+            @IntRange(from = 0, to = 255) int v) {
+        // Shift u and v to the range [-128, 127]
+        int _u = u - 128;
+        int _v = v - 128;
+        int r = (int) MathUtils.clamp((y + 0.000000 * _u + 1.402000 * _v), 0, 255);
+        int g = (int) MathUtils.clamp((y - 0.344136 * _u - 0.714136 * _v), 0, 255);
+        int b = (int) MathUtils.clamp((y + 1.772000 * _u + 0.000000 * _v), 0, 255);
+
+        return Color.rgb(r, g, b);
+    }
 }
diff --git a/camera/camera-core/src/main/cpp/CMakeLists.txt b/camera/camera-core/src/main/cpp/CMakeLists.txt
index f3576ee..82acb5c 100644
--- a/camera/camera-core/src/main/cpp/CMakeLists.txt
+++ b/camera/camera-core/src/main/cpp/CMakeLists.txt
@@ -27,18 +27,3 @@
 find_package(libyuv REQUIRED)
 
 target_link_libraries(image_processing_util_jni PRIVATE ${log-lib} ${android-lib} libyuv::yuv)
-
-
-set(CMAKE_CXX_FLAGS "${CMAKE_CXX_FLAGS} -Wall -Werror")
-
-add_library(
-        camerax_core_opengl_renderer_jni SHARED
-        jni_hooks.cpp
-        opengl_renderer_jni.cpp)
-
-find_library(log-lib log)
-find_library(android-lib android)
-find_library(opengl-lib GLESv2)
-find_library(egl-lib EGL)
-
-target_link_libraries(camerax_core_opengl_renderer_jni ${log-lib} ${android-lib} ${opengl-lib} ${egl-lib})
diff --git a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
index 8e57292..630fd66 100644
--- a/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
+++ b/camera/camera-core/src/main/cpp/image_processing_util_jni.cc
@@ -73,6 +73,35 @@
     return mode;
 }
 
+// Helper function to convert Android420 to ABGR with options to choose full swing or studio swing.
+static int Android420ToABGR(const uint8_t* src_y,
+                            int src_stride_y,
+                            const uint8_t* src_u,
+                            int src_stride_u,
+                            const uint8_t* src_v,
+                            int src_stride_v,
+                            int src_pixel_stride_uv,
+                            uint8_t* dst_abgr,
+                            int dst_stride_abgr,
+                            bool is_full_swing,
+                            int width,
+                            int height) {
+    return Android420ToARGBMatrix(src_y,
+                                  src_stride_y,
+                                  src_v,
+                                  src_stride_v,
+                                  src_u,
+                                  src_stride_u,
+                                  src_pixel_stride_uv,
+                                  dst_abgr,
+                                  dst_stride_abgr,
+                                  is_full_swing
+                                      ? &libyuv::kYvuJPEGConstants : &libyuv::kYvuI601Constants,
+                                  width,
+                                  height);
+}
+
+
 extern "C" {
 JNIEXPORT jint Java_androidx_camera_core_ImageProcessingUtil_nativeShiftPixel(
         JNIEnv* env,
@@ -240,21 +269,22 @@
         }
 
         // Convert yuv to rgb except the last line.
-        result = libyuv::Android420ToABGR(src_y_ptr + start_offset_y,
-                                          src_stride_y,
-                                          src_u_ptr + start_offset_u,
-                                          src_stride_u,
-                                          src_v_ptr + start_offset_v,
-                                          src_stride_v,
-                                          src_pixel_stride_uv,
-                                          dst_ptr,
-                                          dst_stride_y,
-                                          width,
-                                          height - 1);
+        result = Android420ToABGR(src_y_ptr + start_offset_y,
+                                  src_stride_y,
+                                  src_u_ptr + start_offset_u,
+                                  src_stride_u,
+                                  src_v_ptr + start_offset_v,
+                                  src_stride_v,
+                                  src_pixel_stride_uv,
+                                  dst_ptr,
+                                  dst_stride_y,
+                                  /* is_full_swing = */true,
+                                  width,
+                                  height - 1);
         if (result == 0) {
             // Convert the last row with (width - 1) pixels
             // since the last pixel's yuv data is missing.
-            result = libyuv::Android420ToABGR(
+            result = Android420ToABGR(
                     src_y_ptr + start_offset_y + src_stride_y * (height - 1),
                     src_stride_y - 1,
                     src_u_ptr + start_offset_u + src_stride_u * (height - 2) / 2,
@@ -264,6 +294,7 @@
                     src_pixel_stride_uv,
                     dst_ptr + dst_stride_y * (height - 1),
                     dst_stride_y,
+                    /* is_full_swing = */true,
                     width - 1,
                     1);
         }
@@ -285,7 +316,7 @@
             }
         }
     } else {
-        result = libyuv::Android420ToABGR(src_y_ptr + start_offset_y,
+        result = Android420ToABGR(src_y_ptr + start_offset_y,
                                           src_stride_y,
                                           src_u_ptr + start_offset_u,
                                           src_stride_u,
@@ -294,6 +325,7 @@
                                           src_pixel_stride_uv,
                                           dst_ptr,
                                           dst_stride_y,
+                                          /* is_full_swing = */true,
                                           width,
                                           height);
     }
diff --git a/camera/camera-core/src/main/cpp/jni_hooks.cpp b/camera/camera-core/src/main/cpp/jni_hooks.cpp
deleted file mode 100644
index f5d05db..0000000
--- a/camera/camera-core/src/main/cpp/jni_hooks.cpp
+++ /dev/null
@@ -1,7 +0,0 @@
-#include <jni.h>
-
-extern "C" {
-JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *jvm, void *reserved) {
-    return JNI_VERSION_1_6;
-}
-}
\ No newline at end of file
diff --git a/camera/camera-core/src/main/cpp/opengl_renderer_jni.cpp b/camera/camera-core/src/main/cpp/opengl_renderer_jni.cpp
deleted file mode 100644
index f12082d..0000000
--- a/camera/camera-core/src/main/cpp/opengl_renderer_jni.cpp
+++ /dev/null
@@ -1,621 +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.
- */
-
-#include <android/log.h>
-#include <android/native_window.h>
-#include <android/native_window_jni.h>
-#include <EGL/egl.h>
-#include <EGL/eglext.h>
-#include <EGL/eglplatform.h>
-#include <GLES2/gl2.h>
-#include <GLES2/gl2ext.h>
-#include <jni.h>
-
-#include <cassert>
-#include <iomanip>
-#include <sstream>
-#include <string>
-#include <utility>
-#include <vector>
-
-namespace {
-    auto constexpr LOG_TAG = "OpenGlRendererJni";
-
-    std::string GLErrorString(GLenum error) {
-        switch (error) {
-            case GL_NO_ERROR:
-                return "GL_NO_ERROR";
-            case GL_INVALID_ENUM:
-                return "GL_INVALID_ENUM";
-            case GL_INVALID_VALUE:
-                return "GL_INVALID_VALUE";
-            case GL_INVALID_OPERATION:
-                return "GL_INVALID_OPERATION";
-            case GL_STACK_OVERFLOW_KHR:
-                return "GL_STACK_OVERFLOW";
-            case GL_STACK_UNDERFLOW_KHR:
-                return "GL_STACK_UNDERFLOW";
-            case GL_OUT_OF_MEMORY:
-                return "GL_OUT_OF_MEMORY";
-            case GL_INVALID_FRAMEBUFFER_OPERATION:
-                return "GL_INVALID_FRAMEBUFFER_OPERATION";
-            default: {
-                std::ostringstream oss;
-                oss << "<Unknown GL Error 0x" << std::setfill('0') <<
-                    std::setw(4) << std::right << std::hex << error << ">";
-                return oss.str();
-            }
-        }
-    }
-
-    std::string EGLErrorString(EGLenum error) {
-        switch (error) {
-            case EGL_SUCCESS:
-                return "EGL_SUCCESS";
-            case EGL_NOT_INITIALIZED:
-                return "EGL_NOT_INITIALIZED";
-            case EGL_BAD_ACCESS:
-                return "EGL_BAD_ACCESS";
-            case EGL_BAD_ALLOC:
-                return "EGL_BAD_ALLOC";
-            case EGL_BAD_ATTRIBUTE:
-                return "EGL_BAD_ATTRIBUTE";
-            case EGL_BAD_CONTEXT:
-                return "EGL_BAD_CONTEXT";
-            case EGL_BAD_CONFIG:
-                return "EGL_BAD_CONFIG";
-            case EGL_BAD_CURRENT_SURFACE:
-                return "EGL_BAD_CURRENT_SURFACE";
-            case EGL_BAD_DISPLAY:
-                return "EGL_BAD_DISPLAY";
-            case EGL_BAD_SURFACE:
-                return "EGL_BAD_SURFACE";
-            case EGL_BAD_MATCH:
-                return "EGL_BAD_MATCH";
-            case EGL_BAD_PARAMETER:
-                return "EGL_BAD_PARAMETER";
-            case EGL_BAD_NATIVE_PIXMAP:
-                return "EGL_BAD_NATIVE_PIXMAP";
-            case EGL_BAD_NATIVE_WINDOW:
-                return "EGL_BAD_NATIVE_WINDOW";
-            case EGL_CONTEXT_LOST:
-                return "EGL_CONTEXT_LOST";
-            default: {
-                std::ostringstream oss;
-                oss << "<Unknown EGL Error 0x" << std::setfill('0') <<
-                    std::setw(4) << std::right << std::hex << error << ">";
-                return oss.str();
-            }
-        }
-    }
-}
-
-#ifdef NDEBUG
-#define CHECK_GL(gl_func) [&]() { return gl_func; }()
-#else
-namespace {
-    class CheckGlErrorOnExit {
-    public:
-        explicit CheckGlErrorOnExit(std::string glFunStr, unsigned int lineNum) :
-                mGlFunStr(std::move(glFunStr)),
-                mLineNum(lineNum) {}
-
-        ~CheckGlErrorOnExit() {
-            GLenum err = glGetError();
-            if (err != GL_NO_ERROR) {
-                __android_log_assert(nullptr, LOG_TAG, "OpenGL Error: %s at %s [%s:%d]",
-                                     GLErrorString(err).c_str(), mGlFunStr.c_str(), __FILE__,
-                                     mLineNum);
-            }
-        }
-
-        CheckGlErrorOnExit(const CheckGlErrorOnExit &) = delete;
-
-        CheckGlErrorOnExit &operator=(const CheckGlErrorOnExit &) = delete;
-
-    private:
-        std::string mGlFunStr;
-        unsigned int mLineNum;
-    };  // class CheckGlErrorOnExit
-}   // namespace
-#define CHECK_GL(glFunc)                                                    \
-  [&]() {                                                                   \
-    auto assertOnExit = CheckGlErrorOnExit(#glFunc, __LINE__);              \
-    return glFunc;                                                          \
-  }()
-#endif
-
-namespace {
-    const std::vector<std::string> VAR_NAMES = {
-            "fragCoord",    // 0
-            "sampler",      // 1
-    };
-
-    const std::string VERTEX_SHADER_SRC = R"SRC(
-      attribute vec4 position;
-      attribute vec4 texCoords;
-      uniform mat4 texTransform;
-      varying vec2 )SRC" + VAR_NAMES[0] + R"SRC(;
-      void main() {
-        )SRC" + VAR_NAMES[0] + R"SRC(= (texTransform * texCoords).xy;
-        gl_Position = position;
-      }
-)SRC";
-
-    const std::string FRAGMENT_SHADER_SRC = R"SRC(
-      #extension GL_OES_EGL_image_external : require
-      precision mediump float;
-      uniform samplerExternalOES )SRC" + VAR_NAMES[1] + R"SRC(;
-      varying vec2 )SRC" + VAR_NAMES[0] + R"SRC(;
-      void main() {
-        gl_FragColor = texture2D()SRC" + VAR_NAMES[1] + ", " + VAR_NAMES[0] + R"SRC();
-      }
-)SRC";
-
-    // We use two triangles drawn with GL_TRIANGLE_STRIP to create the surface which will be
-    // textured with the camera frame. This could also be done with a quad (GL_QUADS) on a
-    // different version of OpenGL or with a scaled single triangle in which we would inscribe
-    // the camera texture.
-    //
-    //                       (-1,1)           (1,1)
-    //                          +---------------+
-    //                          | \_            |
-    //                          |    \_         |
-    //                          |       +       |
-    //                          |         \_    |
-    //                          |            \_ |
-    //                          +---------------+
-    //                       (-1,-1)          (1,-1)
-    constexpr GLfloat VERTICES[] = {
-            -1.0f, -1.0f, // Lower-left
-            1.0f, -1.0f, // Lower-right
-            -1.0f,  1.0f, // Upper-left (notice order here. We're drawing triangles, not a quad.)
-            1.0f,  1.0f  // Upper-right
-    };
-    constexpr GLfloat TEX_COORDS[] = {
-            0.0f, 0.0f, // Lower-left
-            1.0f, 0.0f, // Lower-right
-            0.0f, 1.0f, // Upper-left (order must match the VERTICES)
-            1.0f, 1.0f  // Upper-right
-    };
-
-    struct NativeContext {
-        EGLDisplay display;
-        EGLConfig config;
-        EGLContext context;
-        std::pair<ANativeWindow *, EGLSurface> windowSurface;
-        EGLSurface pbufferSurface;
-        GLuint program;
-        GLint positionHandle;
-        GLint texCoordsHandle;
-        GLint samplerHandle;
-        GLint texTransformHandle;
-        GLuint textureId;
-
-        NativeContext(EGLDisplay display, EGLConfig config, EGLContext context,
-                      ANativeWindow *window, EGLSurface surface,
-                      EGLSurface pbufferSurface)
-                : display(display),
-                  config(config),
-                  context(context),
-                  windowSurface(std::make_pair(window, surface)),
-                  pbufferSurface(pbufferSurface),
-                  program(0),
-                  positionHandle(-1),
-                  texCoordsHandle(1),
-                  samplerHandle(-1),
-                  texTransformHandle(-1),
-                  textureId(0) {}
-    };
-
-    const char *ShaderTypeString(GLenum shaderType) {
-        switch (shaderType) {
-            case GL_VERTEX_SHADER:
-                return "GL_VERTEX_SHADER";
-            case GL_FRAGMENT_SHADER:
-                return "GL_FRAGMENT_SHADER";
-            default:
-                return "<Unknown shader type>";
-        }
-    }
-
-    // Returns a handle to the shader
-    GLuint CompileShader(GLenum shaderType, const char *shaderSrc) {
-        GLuint shader = CHECK_GL(glCreateShader(shaderType));
-        assert(shader);
-        CHECK_GL(glShaderSource(shader, 1, &shaderSrc, /*length=*/nullptr));
-        CHECK_GL(glCompileShader(shader));
-        GLint compileStatus = 0;
-        CHECK_GL(glGetShaderiv(shader, GL_COMPILE_STATUS, &compileStatus));
-        if (!compileStatus) {
-            GLint logLength = 0;
-            CHECK_GL(glGetShaderiv(shader, GL_INFO_LOG_LENGTH, &logLength));
-            std::vector<char> logBuffer(logLength);
-            if (logLength > 0) {
-                CHECK_GL(glGetShaderInfoLog(shader, logLength, /*length=*/nullptr,
-                                            &logBuffer[0]));
-            }
-            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
-                                "Unable to compile %s shader:\n %s.",
-                                ShaderTypeString(shaderType),
-                                logLength > 0 ? &logBuffer[0] : "(unknown error)");
-            CHECK_GL(glDeleteShader(shader));
-            return 0;
-        }
-        assert(shader);
-        return shader;
-    }
-
-    // Returns a handle to the output program
-    GLuint CreateGlProgram(GLuint vertexShader, GLuint fragmentShader) {
-        GLuint program = CHECK_GL(glCreateProgram());
-        assert(program);
-        CHECK_GL(glAttachShader(program, vertexShader));
-        CHECK_GL(glAttachShader(program, fragmentShader));
-        CHECK_GL(glLinkProgram(program));
-        GLint linkStatus = 0;
-        CHECK_GL(glGetProgramiv(program, GL_LINK_STATUS, &linkStatus));
-        if (!linkStatus) {
-            GLint logLength = 0;
-            CHECK_GL(glGetProgramiv(program, GL_INFO_LOG_LENGTH, &logLength));
-            std::vector<char> logBuffer(logLength);
-            if (logLength > 0) {
-                CHECK_GL(glGetProgramInfoLog(program, logLength, /*length=*/nullptr,
-                                             &logBuffer[0]));
-            }
-            __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
-                                "Unable to link program:\n %s.",
-                                logLength > 0 ? &logBuffer[0] : "(unknown error)");
-            CHECK_GL(glDeleteProgram(program));
-            return 0;
-        }
-        assert(program);
-        return program;
-    }
-
-    void DestroySurface(NativeContext *nativeContext) {
-        if (nativeContext->windowSurface.first) {
-            eglMakeCurrent(nativeContext->display, nativeContext->pbufferSurface,
-                           nativeContext->pbufferSurface, nativeContext->context);
-            eglDestroySurface(nativeContext->display,
-                              nativeContext->windowSurface.second);
-            nativeContext->windowSurface.second = nullptr;
-            ANativeWindow_release(nativeContext->windowSurface.first);
-            nativeContext->windowSurface.first = nullptr;
-        }
-    }
-
-    void ThrowException(JNIEnv *env, const char *exceptionName, const char *msg) {
-        jclass exClass = env->FindClass(exceptionName);
-        assert(exClass != nullptr);
-
-        [[maybe_unused]] jint throwSuccess = env->ThrowNew(exClass, msg);
-        assert(throwSuccess == JNI_OK);
-    }
-
-}  // namespace
-
-extern "C" {
-JNIEXPORT jobject JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_getShaderVariableNames(
-        JNIEnv *env, jclass clazz) {
-
-    jclass arrayListCls = env->FindClass("java/util/ArrayList");
-    jmethodID arrayListConstructor = env->GetMethodID(arrayListCls, "<init>", "(I)V");
-    jmethodID arrayListAddId  = env->GetMethodID(arrayListCls, "add", "(Ljava/lang/Object;)Z");
-
-    jobject jobject = env->NewObject(arrayListCls, arrayListConstructor, (int) VAR_NAMES.size());
-    for (const std::string& name: VAR_NAMES) {
-        jstring element = env->NewStringUTF(name.c_str());
-        env->CallBooleanMethod(jobject, arrayListAddId, element);
-        env->DeleteLocalRef(element);
-    }
-    env->DeleteLocalRef(arrayListCls);
-    return jobject;
-}
-
-JNIEXPORT jlong JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_initContext(
-        JNIEnv *env, jclass clazz, jstring jcustomFragmentShader) {
-    EGLDisplay eglDisplay = eglGetDisplay(EGL_DEFAULT_DISPLAY);
-    assert(eglDisplay != EGL_NO_DISPLAY);
-
-    EGLint majorVer;
-    EGLint minorVer;
-    EGLBoolean initSuccess = eglInitialize(eglDisplay, &majorVer, &minorVer);
-    if (initSuccess != EGL_TRUE) {
-        ThrowException(env, "java/lang/RuntimeException",
-                       "EGL Error: eglInitialize failed.");
-        return 0;
-    }
-
-    // Print debug EGL information
-    const char *eglVendorString = eglQueryString(eglDisplay, EGL_VENDOR);
-    const char *eglVersionString = eglQueryString(eglDisplay, EGL_VERSION);
-    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "EGL Initialized [Vendor: %s, Version: %s]",
-                        eglVendorString == nullptr ? "Unknown" : eglVendorString,
-                        eglVersionString == nullptr
-                        ? "Unknown" : eglVersionString);
-
-    int configAttribs[] = {EGL_RENDERABLE_TYPE,
-                           EGL_OPENGL_ES2_BIT,
-                           EGL_SURFACE_TYPE,
-                           EGL_WINDOW_BIT | EGL_PBUFFER_BIT,
-                           EGL_RECORDABLE_ANDROID,
-                           EGL_TRUE,
-                           EGL_NONE};
-    EGLConfig config;
-    EGLint numConfigs;
-    EGLint configSize = 1;
-    EGLBoolean chooseConfigSuccess =
-            eglChooseConfig(eglDisplay, static_cast<EGLint *>(configAttribs), &config,
-                            configSize, &numConfigs);
-    if (chooseConfigSuccess != EGL_TRUE) {
-        ThrowException(env, "java/lang/IllegalArgumentException",
-                       "EGL Error: eglChooseConfig failed. ");
-        return 0;
-    }
-    assert(numConfigs > 0);
-
-    int contextAttribs[] = {EGL_CONTEXT_CLIENT_VERSION, 2, EGL_NONE};
-    EGLContext eglContext = eglCreateContext(
-            eglDisplay, config, EGL_NO_CONTEXT, static_cast<EGLint *>(contextAttribs));
-    assert(eglContext != EGL_NO_CONTEXT);
-
-    // Create 1x1 pixmap to use as a surface until one is set.
-    int pbufferAttribs[] = {EGL_WIDTH, 1, EGL_HEIGHT, 1, EGL_NONE};
-    EGLSurface eglPbuffer =
-            eglCreatePbufferSurface(eglDisplay, config, pbufferAttribs);
-    assert(eglPbuffer != EGL_NO_SURFACE);
-
-    eglMakeCurrent(eglDisplay, eglPbuffer, eglPbuffer, eglContext);
-
-    //Print debug OpenGL information
-    const GLubyte *glVendorString = CHECK_GL(glGetString(GL_VENDOR));
-    const GLubyte *glVersionString = CHECK_GL(glGetString(GL_VERSION));
-    const GLubyte *glslVersionString = CHECK_GL(glGetString(GL_SHADING_LANGUAGE_VERSION));
-    const GLubyte *glRendererString = CHECK_GL(glGetString(GL_RENDERER));
-    __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "OpenGL Initialized [Vendor: %s, Version: %s,"
-                                                    " GLSL Version: %s, Renderer: %s]",
-                        glVendorString == nullptr ? "Unknown" : (const char *) glVendorString,
-                        glVersionString == nullptr ? "Unknown" : (const char *) glVersionString,
-                        glslVersionString == nullptr ? "Unknown" : (const char *) glslVersionString,
-                        glRendererString == nullptr ? "Unknown" : (const char *) glRendererString);
-
-    auto *nativeContext =
-            new NativeContext(eglDisplay, config, eglContext, /*window=*/nullptr,
-                    /*surface=*/nullptr, eglPbuffer);
-
-    // Compile vertex shader
-    GLuint vertexShader = CompileShader(GL_VERTEX_SHADER, VERTEX_SHADER_SRC.c_str());
-    assert(vertexShader);
-
-    // Compile fragment shader
-    const char* fragmentShaderSrc;
-    if (jcustomFragmentShader != nullptr) {
-        fragmentShaderSrc = env->GetStringUTFChars(jcustomFragmentShader, nullptr);
-        __android_log_print(ANDROID_LOG_DEBUG, LOG_TAG, "Custom fragment shader = %s",
-                            fragmentShaderSrc);
-        // A simple check to workaround custom shader doesn't contain required variable.
-        // See b/241193761.
-        if (!strstr(fragmentShaderSrc, VAR_NAMES[0].c_str())) {
-            ThrowException(env, "java/lang/IllegalArgumentException", std::string(
-                    "Missing required variable '" + VAR_NAMES[0] +
-                    "' in the custom fragment shader").c_str());
-            env->ReleaseStringUTFChars(jcustomFragmentShader, fragmentShaderSrc);
-            return 0;
-        }
-    } else {
-        fragmentShaderSrc = FRAGMENT_SHADER_SRC.c_str();
-    }
-    GLuint fragmentShader = CompileShader(GL_FRAGMENT_SHADER, fragmentShaderSrc);
-    if (jcustomFragmentShader != nullptr) {
-        env->ReleaseStringUTFChars(jcustomFragmentShader, fragmentShaderSrc);
-    }
-    if (!fragmentShader && jcustomFragmentShader != nullptr) {
-        ThrowException(env, "java/lang/IllegalArgumentException",
-                       "Unable to compile custom fragment shader.");
-        return 0;
-    }
-    assert(fragmentShader);
-
-    nativeContext->program = CreateGlProgram(vertexShader, fragmentShader);
-    if (nativeContext->program == 0 && jcustomFragmentShader != nullptr) {
-        ThrowException(env, "java/lang/IllegalArgumentException",
-                       "Unable to create GL program with custom shader.");
-        return 0;
-    }
-    assert(nativeContext->program);
-
-    nativeContext->positionHandle =
-            CHECK_GL(glGetAttribLocation(nativeContext->program, "position"));
-    assert(nativeContext->positionHandle != -1);
-
-    nativeContext->texCoordsHandle =
-            CHECK_GL(glGetAttribLocation(nativeContext->program, "texCoords"));
-    assert(nativeContext->texCoordsHandle != -1);
-
-    nativeContext->samplerHandle =
-            CHECK_GL(glGetUniformLocation(nativeContext->program, VAR_NAMES[1].c_str()));
-    if (nativeContext->samplerHandle == -1 && jcustomFragmentShader != nullptr) {
-        CHECK_GL(glDeleteProgram(nativeContext->program));
-        nativeContext->program = 0;
-        ThrowException(env, "java/lang/IllegalArgumentException",
-                       std::string(
-                               "Unable to get sampler handle by name: " + VAR_NAMES[1]).c_str());
-        return 0;
-    }
-    assert(nativeContext->samplerHandle != -1);
-
-    nativeContext->texTransformHandle =
-            CHECK_GL(glGetUniformLocation(nativeContext->program, "texTransform"));
-    assert(nativeContext->texTransformHandle != -1);
-
-    CHECK_GL(glGenTextures(1, &(nativeContext->textureId)));
-
-    return reinterpret_cast<jlong>(nativeContext);
-}
-
-JNIEXPORT jboolean JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_setWindowSurface(
-        JNIEnv *env, jclass clazz, jlong context, jobject jsurface) {
-    auto *nativeContext = reinterpret_cast<NativeContext *>(context);
-
-    // Destroy previously connected surface
-    DestroySurface(nativeContext);
-
-    // Null surface may have just been passed in to destroy previous surface.
-    if (!jsurface) {
-        return JNI_FALSE;
-    }
-
-    ANativeWindow *nativeWindow = ANativeWindow_fromSurface(env, jsurface);
-    if (nativeWindow == nullptr) {
-        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG, "Failed to set window surface: Unable to "
-                                                        "acquire native window.");
-        return JNI_FALSE;
-    }
-
-    EGLSurface surface =
-            eglCreateWindowSurface(nativeContext->display, nativeContext->config,
-                                   nativeWindow, /*attrib_list=*/nullptr);
-    assert(surface != EGL_NO_SURFACE);
-
-    nativeContext->windowSurface = std::make_pair(nativeWindow, surface);
-
-    eglMakeCurrent(nativeContext->display, surface, surface,
-                   nativeContext->context);
-
-    CHECK_GL(glViewport(0, 0, ANativeWindow_getWidth(nativeWindow),
-                        ANativeWindow_getHeight(nativeWindow)));
-    CHECK_GL(glScissor(0, 0, ANativeWindow_getWidth(nativeWindow),
-                       ANativeWindow_getHeight(nativeWindow)));
-
-    return JNI_TRUE;
-}
-
-JNIEXPORT jint JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_getTexName(
-        JNIEnv *env, jclass clazz, jlong context) {
-    auto *nativeContext = reinterpret_cast<NativeContext *>(context);
-    return (jint) nativeContext->textureId;
-}
-
-JNIEXPORT jboolean JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_renderTexture(
-        JNIEnv *env, jclass clazz, jlong context, jlong timestampNs,
-        jfloatArray jtexTransformArray) {
-    auto *nativeContext = reinterpret_cast<NativeContext *>(context);
-
-    GLint vertexComponents = 2;
-    GLenum vertexType = GL_FLOAT;
-    GLboolean normalized = GL_FALSE;
-    GLsizei vertexStride = 0;
-    CHECK_GL(glVertexAttribPointer(nativeContext->positionHandle,
-                                   vertexComponents, vertexType, normalized,
-                                   vertexStride, VERTICES));
-    CHECK_GL(glEnableVertexAttribArray(nativeContext->positionHandle));
-
-    CHECK_GL(glVertexAttribPointer(nativeContext->texCoordsHandle,
-                                   vertexComponents, vertexType, normalized,
-                                   vertexStride, TEX_COORDS));
-    CHECK_GL(glEnableVertexAttribArray(nativeContext->texCoordsHandle));
-
-    CHECK_GL(glUseProgram(nativeContext->program));
-
-    GLsizei numMatrices = 1;
-    GLboolean transpose = GL_FALSE;
-
-    CHECK_GL(glUniform1i(nativeContext->samplerHandle, 0));
-
-    numMatrices = 1;
-    transpose = GL_FALSE;
-    GLfloat *texTransformArray =
-            env->GetFloatArrayElements(jtexTransformArray, nullptr);
-    CHECK_GL(glUniformMatrix4fv(nativeContext->texTransformHandle, numMatrices,
-                                transpose, texTransformArray));
-    env->ReleaseFloatArrayElements(jtexTransformArray, texTransformArray,
-                                   JNI_ABORT);
-
-    CHECK_GL(glBindTexture(GL_TEXTURE_EXTERNAL_OES, nativeContext->textureId));
-
-    // This will typically fail if the EGL surface has been detached abnormally. In that case we
-    // will return JNI_FALSE below.
-    glDrawArrays(GL_TRIANGLE_STRIP, 0, 4);
-
-    // Check that all GL operations completed successfully. If not, log an error and return.
-    GLenum glError = glGetError();
-    if (glError != GL_NO_ERROR) {
-        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
-                            "Failed to draw frame due to OpenGL error: %s",
-                            GLErrorString(glError).c_str());
-        return JNI_FALSE;
-    }
-
-    // Disable vertex array, texture, and program.
-    CHECK_GL(glDisableVertexAttribArray(nativeContext->positionHandle));
-
-    CHECK_GL(glDisableVertexAttribArray(nativeContext->texCoordsHandle));
-
-    CHECK_GL(glBindTexture(GL_TEXTURE_EXTERNAL_OES, 0));
-
-    CHECK_GL(glUseProgram(0));
-
-// Only attempt to set presentation time if EGL_EGLEXT_PROTOTYPES is defined.
-// Otherwise, we'll ignore the timestamp.
-#ifdef EGL_EGLEXT_PROTOTYPES
-    eglPresentationTimeANDROID(nativeContext->display,
-                               nativeContext->windowSurface.second, timestampNs);
-#endif  // EGL_EGLEXT_PROTOTYPES
-    EGLBoolean swapped = eglSwapBuffers(nativeContext->display,
-                                        nativeContext->windowSurface.second);
-    if (!swapped) {
-        EGLenum eglError = eglGetError();
-        __android_log_print(ANDROID_LOG_ERROR, LOG_TAG,
-                            "Failed to swap buffers with EGL error: %s",
-                            EGLErrorString(eglError).c_str());
-        return JNI_FALSE;
-    }
-
-    return JNI_TRUE;
-}
-
-JNIEXPORT void JNICALL
-Java_androidx_camera_core_processing_OpenGlRenderer_closeContext(
-        JNIEnv *env, jclass clazz, jlong context) {
-    auto *nativeContext = reinterpret_cast<NativeContext *>(context);
-
-    if (nativeContext->program) {
-        CHECK_GL(glDeleteProgram(nativeContext->program));
-        nativeContext->program = 0;
-    }
-
-    DestroySurface(nativeContext);
-
-    eglDestroySurface(nativeContext->display, nativeContext->pbufferSurface);
-
-    eglMakeCurrent(nativeContext->display, EGL_NO_SURFACE, EGL_NO_SURFACE,
-                   EGL_NO_CONTEXT);
-
-    eglDestroyContext(nativeContext->display, nativeContext->context);
-
-    eglTerminate(nativeContext->display);
-
-    delete nativeContext;
-}
-}  // extern "C"
-
-#undef CHECK_GL
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
index 30d9548..6511daa 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImageCapture.java
@@ -74,6 +74,7 @@
 import androidx.annotation.MainThread;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.OptIn;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.RestrictTo.Scope;
@@ -1483,8 +1484,11 @@
         @Override
         public void onImageClose(@NonNull ImageProxy image) {
             synchronized (mLock) {
+                // TODO: mLock can be removed if all methods and callbacks in
+                //  ImageCaptureRequestProcessor are used in the main thread.
+                //  Side note: TakePictureManager already handles the requests in the main thread.
                 mOutstandingImages--;
-                processNextRequest();
+                CameraXExecutors.mainThreadExecutor().execute(this::processNextRequest);
             }
         }
 
@@ -1546,7 +1550,7 @@
                             processNextRequest();
                         }
                     }
-                }, CameraXExecutors.directExecutor());
+                }, CameraXExecutors.mainThreadExecutor());
             }
         }
 
@@ -1886,6 +1890,7 @@
      *
      * <p> This is the new {@link #createPipeline}.
      */
+    @OptIn(markerClass = ExperimentalZeroShutterLag.class)
     @MainThread
     private SessionConfig.Builder createPipelineWithNode(@NonNull String cameraId,
             @NonNull ImageCaptureConfig config, @NonNull Size resolution) {
@@ -1898,6 +1903,9 @@
         mTakePictureManager = new TakePictureManager(mImageCaptureControl, mImagePipeline);
 
         SessionConfig.Builder sessionConfigBuilder = mImagePipeline.createSessionConfigBuilder();
+        if (Build.VERSION.SDK_INT >= 23 && getCaptureMode() == CAPTURE_MODE_ZERO_SHUTTER_LAG) {
+            getCameraControl().addZslConfig(sessionConfigBuilder);
+        }
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
             // TODO(b/143915543): Ensure this never gets called by a camera that is not attached
             //  to this use case so we don't need to do this check.
@@ -1934,6 +1942,7 @@
                 getSensorToBufferTransformMatrix(),
                 getRelativeRotation(requireNonNull(getCamera())),
                 getJpegQualityInternal(),
+                getCaptureMode(),
                 mSessionConfigBuilder.getSingleCameraCaptureCallbacks()));
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ImmutableImageInfo.java b/camera/camera-core/src/main/java/androidx/camera/core/ImmutableImageInfo.java
index 8184d1c..fdb6811 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ImmutableImageInfo.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ImmutableImageInfo.java
@@ -20,16 +20,26 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
 import androidx.camera.core.impl.TagBundle;
 import androidx.camera.core.impl.utils.ExifData;
 
 import com.google.auto.value.AutoValue;
 
+/**
+ * @hide
+ */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 @AutoValue
-abstract class ImmutableImageInfo implements ImageInfo {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public abstract class ImmutableImageInfo implements ImageInfo {
+
+    /**
+     * Creates an instance of {@link ImmutableImageInfo}.
+     */
+    @NonNull
     public static ImageInfo create(@NonNull TagBundle tag, long timestamp,
-            int rotationDegrees, Matrix sensorToBufferTransformMatrix) {
+            int rotationDegrees, @NonNull Matrix sensorToBufferTransformMatrix) {
         return new AutoValue_ImmutableImageInfo(
                 tag,
                 timestamp,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java b/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
index 9f1d180f..c6eb095 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/MetadataImageReader.java
@@ -65,9 +65,23 @@
         }
     };
 
+    /**
+     * Used to record how many OnImageAvailable callback has been called but the images are not
+     * acquired yet.
+     */
+    @GuardedBy("mLock")
+    private int mUnAcquiredAvailableImageCount = 0;
+
     // Callback when Image is ready from the underlying ImageReader.
     private ImageReaderProxy.OnImageAvailableListener mTransformedListener =
-            (reader) -> imageIncoming(reader);
+            (reader) -> {
+                synchronized (mLock) {
+                    // Increases the un-acquired images count when receiving the image available
+                    // callback.
+                    mUnAcquiredAvailableImageCount++;
+                }
+                imageIncoming(reader);
+            };
 
     @GuardedBy("mLock")
     private boolean mClosed = false;
@@ -258,6 +272,7 @@
             mImageReaderProxy.clearOnImageAvailableListener();
             mListener = null;
             mExecutor = null;
+            mUnAcquiredAvailableImageCount = 0;
         }
     }
 
@@ -303,6 +318,12 @@
                 }
             }
             mAcquiredImageProxies.remove(image);
+
+            // Calls the imageIncoming() function to acquire next images from the image reader if
+            // the un-acquired available image count is larger than 0.
+            if (mUnAcquiredAvailableImageCount > 0) {
+                imageIncoming(mImageReaderProxy);
+            }
         }
     }
 
@@ -319,9 +340,18 @@
                 return;
             }
 
-            // Acquire all currently pending images in order to prevent backing up of the queue.
+            int numAcquired = mPendingImages.size() + mMatchedImageProxies.size();
+            // Do not acquire the next image if unclosed images count has reached the max images
+            // count.
+            if (numAcquired >= imageReader.getMaxImages()) {
+                Logger.d(TAG, "Skip to acquire the next image because the acquired image count "
+                        + "has reached the max images count.");
+                return;
+            }
+
+            // Acquires currently pending images as more as possible to prevent backing up of the
+            // queue. MetadataImageReader's user also needs to close the acquired images ASAP.
             // However don't use acquireLatestImage() to make sure that all images are matched.
-            int numAcquired = 0;
             ImageProxy image;
             do {
                 image = null;
@@ -331,16 +361,20 @@
                     Logger.d(TAG, "Failed to acquire next image.", e);
                 } finally {
                     if (image != null) {
+                        // Decreases the un-acquired images count after successfully acquiring an
+                        // image.
+                        mUnAcquiredAvailableImageCount--;
                         numAcquired++;
                         // Add the incoming Image to pending list and do the matching logic.
                         mPendingImages.put(image.getImageInfo().getTimestamp(), image);
                         matchImages();
                     }
                 }
-                // Only acquire maxImages number of images in case the producer pushing images into
-                // the queue is faster than the rater at which images are acquired to prevent
-                // acquiring images indefinitely.
-            } while (image != null && numAcquired < imageReader.getMaxImages());
+                // Only acquires more images if the un-acquired available images count is larger
+                // than 0 and the currently non-closed acquired images count doesn't exceed the
+                // max images count.
+            } while (image != null && mUnAcquiredAvailableImageCount > 0
+                    && numAcquired < imageReader.getMaxImages());
         }
     }
 
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
index c19b400..fd52b2c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/Preview.java
@@ -187,9 +187,7 @@
     @VisibleForTesting
     @Nullable
     SurfaceRequest mCurrentSurfaceRequest;
-    // Flag indicates that there is a SurfaceRequest created by Preview but hasn't sent to the
-    // caller.
-    private boolean mHasUnsentSurfaceRequest = false;
+
     // The attached surface size. Same as getAttachedSurfaceResolution() but is available during
     // createPipeline().
     @Nullable
@@ -234,10 +232,9 @@
                 isRGBA8888SurfaceRequired);
         mCurrentSurfaceRequest = surfaceRequest;
 
-        if (sendSurfaceRequestIfReady()) {
-            sendTransformationInfoIfReady();
-        } else {
-            mHasUnsentSurfaceRequest = true;
+        if (mSurfaceProvider != null) {
+            // Only send surface request if the provider is set.
+            sendSurfaceRequest();
         }
 
         if (captureProcessor != null) {
@@ -330,10 +327,9 @@
         // Send the app Surface to the app.
         mSessionDeferrableSurface = cameraSurface;
         mCurrentSurfaceRequest = appSurface.createSurfaceRequest(camera);
-        if (sendSurfaceRequestIfReady()) {
-            sendTransformationInfoIfReady();
-        } else {
-            mHasUnsentSurfaceRequest = true;
+        if (mSurfaceProvider != null) {
+            // Only send surface request if the provider is set.
+            sendSurfaceRequest();
         }
 
         // Send the camera Surface to the camera2.
@@ -389,7 +385,17 @@
             @NonNull String cameraId,
             @NonNull PreviewConfig config,
             @NonNull Size resolution) {
-        sessionConfigBuilder.addSurface(mSessionDeferrableSurface);
+        // TODO(b/245309800): Add the Surface if post-processing pipeline is used. Post-processing
+        //  pipeline always provide a Surface.
+
+        // Not to add deferrable surface if the surface provider is not set, as that means the
+        // surface will never be provided. For simplicity, the same rule also applies to
+        // SurfaceEffectNode and CaptureProcessor cases, since no surface provider also means no
+        // output target for these two cases.
+        if (mSurfaceProvider != null) {
+            sessionConfigBuilder.addSurface(mSessionDeferrableSurface);
+        }
+
         sessionConfigBuilder.addErrorListener((sessionConfig, error) -> {
             // Ensure the attached camera has not changed before resetting.
             // TODO(b/143915543): Ensure this never gets called by a camera that is not attached
@@ -439,7 +445,8 @@
         SurfaceProvider surfaceProvider = mSurfaceProvider;
         Rect cropRect = getCropRect(mSurfaceSize);
         SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
-        if (cameraInternal != null && surfaceProvider != null && cropRect != null) {
+        if (cameraInternal != null && surfaceProvider != null && cropRect != null
+                && surfaceRequest != null) {
             // TODO: when SurfaceEffectNode exists, use SettableSurface.setRotationDegrees(int)
             //  instead. However, this requires PreviewView to rely on relative rotation but not
             //  target rotation.
@@ -490,35 +497,24 @@
             mSurfaceProviderExecutor = executor;
             notifyActive();
 
-            if (mHasUnsentSurfaceRequest) {
-                if (sendSurfaceRequestIfReady()) {
-                    sendTransformationInfoIfReady();
-                    mHasUnsentSurfaceRequest = false;
-                }
-            } else {
-                // No pending SurfaceRequest. It could be a previous request has already been
-                // sent, which means the caller wants to replace the Surface. Or, it could be the
-                // pipeline has not started. Or the use case may have been detached from the camera.
-                // Either way, try updating session config and let createPipeline() sends a
-                // new SurfaceRequest.
-                if (getAttachedSurfaceResolution() != null) {
-                    updateConfigAndOutput(getCameraId(), (PreviewConfig) getCurrentConfig(),
-                            getAttachedSurfaceResolution());
-                    notifyReset();
-                }
+            // It could be a previous request has already been sent, which means the caller wants
+            // to replace the Surface. Or, it could be the pipeline has not started. Or the use
+            // case may have been detached from the camera. Either way, try updating session
+            // config and let createPipeline() sends a new SurfaceRequest.
+            if (getAttachedSurfaceResolution() != null) {
+                updateConfigAndOutput(getCameraId(), (PreviewConfig) getCurrentConfig(),
+                        getAttachedSurfaceResolution());
+                notifyReset();
             }
         }
     }
 
-    private boolean sendSurfaceRequestIfReady() {
-        final SurfaceRequest surfaceRequest = mCurrentSurfaceRequest;
-        final SurfaceProvider surfaceProvider = mSurfaceProvider;
-        if (surfaceProvider != null && surfaceRequest != null) {
-            mSurfaceProviderExecutor.execute(
-                    () -> surfaceProvider.onSurfaceRequested(surfaceRequest));
-            return true;
-        }
-        return false;
+    private void sendSurfaceRequest() {
+        final SurfaceProvider surfaceProvider = Preconditions.checkNotNull(mSurfaceProvider);
+        final SurfaceRequest surfaceRequest = Preconditions.checkNotNull(mCurrentSurfaceRequest);
+
+        mSurfaceProviderExecutor.execute(() -> surfaceProvider.onSurfaceRequested(surfaceRequest));
+        sendTransformationInfoIfReady();
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingSurface.java b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingSurface.java
index 2352e36..23148dc 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingSurface.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingSurface.java
@@ -37,6 +37,7 @@
 import androidx.camera.core.impl.SingleImageProxyBundle;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.impl.utils.futures.FutureCallback;
+import androidx.camera.core.impl.utils.futures.FutureChain;
 import androidx.camera.core.impl.utils.futures.Futures;
 
 import com.google.common.util.concurrent.ListenableFuture;
@@ -69,14 +70,10 @@
     @NonNull
     private final Size mResolution;
 
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
-    final MetadataImageReader mInputImageReader;
+    private final MetadataImageReader mInputImageReader;
 
     // The Surface that is backed by mInputImageReader
-    @SuppressWarnings("WeakerAccess") /* synthetic accessor */
-    @GuardedBy("mLock")
-    final Surface mInputSurface;
+    private final Surface mInputSurface;
 
     private final Handler mImageReaderHandler;
 
@@ -177,9 +174,11 @@
     @Override
     @NonNull
     public ListenableFuture<Surface> provideSurface() {
-        synchronized (mLock) {
-            return Futures.immediateFuture(mInputSurface);
-        }
+        // Before returning input surface to configure the capture session, ensures
+        // output surface is ready because output surface will be needed just before
+        // configuring capture session.
+        return  FutureChain.from(mOutputDeferrableSurface.getSurface())
+                .transform(outputSurface -> mInputSurface, CameraXExecutors.directExecutor());
     }
 
     /**
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java b/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
index 99e7789..50d5f19 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/SettableImageProxy.java
@@ -23,12 +23,16 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
 
 /**
  * An {@link ImageProxy} which overwrites the {@link ImageInfo}.
+ *
+ * @hide
  */
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
-final class SettableImageProxy extends ForwardingImageProxy {
+@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+public final class SettableImageProxy extends ForwardingImageProxy {
     private final Object mLock = new Object();
     private final ImageInfo mImageInfo;
 
@@ -56,7 +60,8 @@
      * @param resolution The resolution to overwrite with.
      * @param imageInfo The {@link ImageInfo} to overwrite with.
      */
-    SettableImageProxy(ImageProxy imageProxy, @Nullable Size resolution, ImageInfo imageInfo) {
+    public SettableImageProxy(@NonNull ImageProxy imageProxy, @Nullable Size resolution,
+            @NonNull ImageInfo imageInfo) {
         super(imageProxy);
         if (resolution == null) {
             mWidth = super.getWidth();
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
index d4d1276..4fb0c31 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Bitmap2JpegBytes.java
@@ -16,6 +16,8 @@
 
 package androidx.camera.core.imagecapture;
 
+import static java.util.Objects.requireNonNull;
+
 import android.graphics.Bitmap;
 import android.graphics.ImageFormat;
 import android.os.Build;
@@ -46,7 +48,7 @@
         packet.getData().compress(Bitmap.CompressFormat.JPEG, in.getJpegQuality(), outputStream);
         packet.getData().recycle();
         return Packet.of(outputStream.toByteArray(),
-                packet.getExif(),
+                requireNonNull(packet.getExif()),
                 ImageFormat.JPEG,
                 packet.getSize(),
                 packet.getCropRect(),
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java
new file mode 100644
index 0000000..d7a26f8
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/Image2JpegBytes.java
@@ -0,0 +1,52 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture;
+
+import static androidx.camera.core.internal.utils.ImageUtil.jpegImageToJpegByteArray;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.processing.Packet;
+import androidx.camera.core.processing.Processor;
+
+/**
+ * Converts a {@link ImageProxy} to JPEG bytes.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+final class Image2JpegBytes implements Processor<Packet<ImageProxy>, Packet<byte[]>> {
+
+    @NonNull
+    @Override
+    public Packet<byte[]> process(@NonNull Packet<ImageProxy> inputPacket)
+            throws ImageCaptureException {
+        // TODO(b/240998060): For YUV input image is YUV, convert it to JPEG bytes.
+        byte[] jpegBytes = jpegImageToJpegByteArray(inputPacket.getData());
+        inputPacket.getData().close();
+        return Packet.of(
+                jpegBytes,
+                inputPacket.getExif(),
+                inputPacket.getFormat(),
+                inputPacket.getSize(),
+                inputPacket.getCropRect(),
+                inputPacket.getRotationDegrees(),
+                inputPacket.getSensorToBufferTransform());
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
index 3b7e8d2..993fcff 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ImagePipeline.java
@@ -18,6 +18,7 @@
 
 import static androidx.camera.core.CaptureBundles.singleDefaultCaptureBundle;
 import static androidx.camera.core.impl.utils.Threads.checkMainThread;
+import static androidx.camera.core.impl.utils.TransformUtils.hasCropping;
 
 import static java.util.Objects.requireNonNull;
 
@@ -37,6 +38,7 @@
 import androidx.camera.core.impl.ImageCaptureConfig;
 import androidx.camera.core.impl.SessionConfig;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
+import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability;
 import androidx.core.util.Pair;
 
 import java.util.ArrayList;
@@ -51,6 +53,8 @@
 @RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
 public class ImagePipeline {
 
+    static final ExifRotationAvailability EXIF_ROTATION_AVAILABILITY =
+            new ExifRotationAvailability();
     // Use case configs.
     @NonNull
     private final ImageCaptureConfig mUseCaseConfig;
@@ -97,7 +101,6 @@
     public SessionConfig.Builder createSessionConfigBuilder() {
         SessionConfig.Builder builder = SessionConfig.Builder.createFrom(mUseCaseConfig);
         builder.addNonRepeatingSurface(mPipelineIn.getSurface());
-        // TODO(b/242536140): enable ZSL.
         return builder;
     }
 
@@ -188,11 +191,14 @@
             builder.addSurface(mPipelineIn.getSurface());
 
             // Only sets the JPEG rotation and quality for JPEG format. Some devices do not
-            // handle these configs for non-JPEG images.See b/204375890.
+            // handle these configs for non-JPEG images. See b/204375890.
             if (mPipelineIn.getFormat() == ImageFormat.JPEG) {
-                // TODO(b/242536202): handle ExifRotationAvailability
+                if (EXIF_ROTATION_AVAILABILITY.isRotationOptionSupported()) {
+                    builder.addImplementationOption(CaptureConfig.OPTION_ROTATION,
+                            takePictureRequest.getRotationDegrees());
+                }
                 builder.addImplementationOption(CaptureConfig.OPTION_JPEG_QUALITY,
-                        takePictureRequest.getJpegQuality());
+                        getCameraRequestJpegQuality(takePictureRequest));
             }
 
             // Add the implementation options required by the CaptureStage
@@ -208,6 +214,29 @@
         return new CameraRequest(captureConfigs, takePictureCallback);
     }
 
+    /**
+     * Returns the JPEG quality for camera request.
+     *
+     * <p>If there is JPEG encoding in post-processing, use max quality for the camera request to
+     * minimize quality loss.
+     *
+     * <p>However this results in poor performance during cropping than setting 95 (b/206348741).
+     */
+    int getCameraRequestJpegQuality(@NonNull TakePictureRequest request) {
+        boolean isOnDisk = request.getOnDiskCallback() != null;
+        boolean hasCropping = hasCropping(request.getCropRect(), mPipelineIn.getSize());
+        boolean isMaxQuality =
+                request.getCaptureMode() == ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY;
+        if (isOnDisk && hasCropping && isMaxQuality) {
+            // For saving to disk, the image is decoded to Bitmap, cropped and encoded to JPEG
+            // again. In that case, use 100 to avoid compression quality loss. The trade-off of
+            // using a high quality is poorer performance. So we only do that if the capture mode
+            // is CAPTURE_MODE_MAXIMIZE_QUALITY.
+            return 100;
+        }
+        return request.getJpegQuality();
+    }
+
     @NonNull
     @VisibleForTesting
     CaptureNode getCaptureNode() {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2CroppedBitmap.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2CroppedBitmap.java
index 3760c0c..5101fe4 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2CroppedBitmap.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2CroppedBitmap.java
@@ -16,15 +16,15 @@
 package androidx.camera.core.imagecapture;
 
 import static androidx.camera.core.ImageCapture.ERROR_FILE_IO;
+import static androidx.camera.core.impl.utils.TransformUtils.updateSensorToBufferTransform;
+
+import static java.util.Objects.requireNonNull;
 
 import android.graphics.Bitmap;
 import android.graphics.BitmapFactory;
 import android.graphics.BitmapRegionDecoder;
-import android.graphics.ImageFormat;
-import android.graphics.Matrix;
 import android.graphics.Rect;
 import android.os.Build;
-import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
@@ -47,12 +47,10 @@
         Bitmap bitmap = createCroppedBitmap(packet.getData(), cropRect);
         return Packet.of(
                 bitmap,
-                packet.getExif(),
-                ImageFormat.FLEX_RGBA_8888,
-                new Size(bitmap.getWidth(), bitmap.getHeight()),
+                requireNonNull(packet.getExif()),
                 new Rect(0, 0, bitmap.getWidth(), bitmap.getHeight()),
                 packet.getRotationDegrees(),
-                createSensorToBufferTransform(packet.getSensorToBufferTransform(), cropRect));
+                updateSensorToBufferTransform(packet.getSensorToBufferTransform(), cropRect));
     }
 
     @NonNull
@@ -68,13 +66,4 @@
         }
         return decoder.decodeRegion(cropRect, new BitmapFactory.Options());
     }
-
-    @NonNull
-    private Matrix createSensorToBufferTransform(
-            @NonNull Matrix original,
-            @NonNull Rect cropRect) {
-        Matrix matrix = new Matrix(original);
-        matrix.postTranslate(-cropRect.left, -cropRect.top);
-        return matrix;
-    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
index 894d114..d5d02ea 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegBytes2Disk.java
@@ -114,8 +114,14 @@
             // Create new exif based on the original exif.
             Exif exif = Exif.createFromFile(tempFile);
             originalExif.copyToCroppedImage(exif);
-            // TODO: handle the exif orientation quirk.
-            exif.rotate(rotationDegrees);
+
+            if (exif.getRotation() == 0 && rotationDegrees != 0) {
+                // When the HAL does not handle rotation, exif rotation is 0. In which case we
+                // apply the packet rotation.
+                // See: EXIF_ROTATION_AVAILABILITY
+                exif.rotate(rotationDegrees);
+            }
+
             // Overwrite exif based on metadata.
             ImageCapture.Metadata metadata = options.getMetadata();
             if (metadata.isReversedHorizontal()) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegImage2Result.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegImage2Result.java
new file mode 100644
index 0000000..4dd868b
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/JpegImage2Result.java
@@ -0,0 +1,59 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture;
+
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.ImmutableImageInfo;
+import androidx.camera.core.SettableImageProxy;
+import androidx.camera.core.processing.Packet;
+import androidx.camera.core.processing.Processor;
+
+/**
+ * Produces a {@link ImageProxy} as in-memory capture result.
+ *
+ * <p>Updates the input {@link ImageProxy}'s metadata based on the info in the {@link Packet}.
+ * The metadata in the {@link Packet} should be correct at this stage. The quirks should be handled
+ * in the {@link ProcessingInput2Packet} processor, and the transformation info should be updated
+ * by upstream processors.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+public class JpegImage2Result implements Processor<Packet<ImageProxy>, ImageProxy> {
+
+    @NonNull
+    @Override
+    public ImageProxy process(@NonNull Packet<ImageProxy> input)
+            throws ImageCaptureException {
+        ImageProxy image = input.getData();
+
+        ImageInfo imageInfo = ImmutableImageInfo.create(
+                image.getImageInfo().getTagBundle(),
+                image.getImageInfo().getTimestamp(),
+                input.getRotationDegrees(),
+                input.getSensorToBufferTransform());
+
+        final ImageProxy imageWithUpdatedInfo = new SettableImageProxy(image,
+                input.getSize(), imageInfo);
+        imageWithUpdatedInfo.setCropRect(input.getCropRect());
+        return imageWithUpdatedInfo;
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java
new file mode 100644
index 0000000..12a00ce
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingInput2Packet.java
@@ -0,0 +1,142 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture;
+
+import static android.graphics.ImageFormat.JPEG;
+
+import static androidx.camera.core.ImageCapture.ERROR_FILE_IO;
+import static androidx.camera.core.imagecapture.ImagePipeline.EXIF_ROTATION_AVAILABILITY;
+import static androidx.camera.core.impl.utils.TransformUtils.getRectToRect;
+import static androidx.camera.core.impl.utils.TransformUtils.is90or270;
+import static androidx.camera.core.impl.utils.TransformUtils.within360;
+import static androidx.core.util.Preconditions.checkState;
+
+import android.graphics.Matrix;
+import android.graphics.Rect;
+import android.graphics.RectF;
+import android.os.Build;
+import android.util.Size;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageCaptureException;
+import androidx.camera.core.ImageProxy;
+import androidx.camera.core.impl.utils.Exif;
+import androidx.camera.core.processing.Packet;
+import androidx.camera.core.processing.Processor;
+
+import java.io.IOException;
+
+/**
+ * Converts {@link ProcessingNode} input to a {@link Packet}.
+ *
+ * <p>This is we fix the metadata of the image, such as rotation and crop rect.
+ */
+@RequiresApi(api = Build.VERSION_CODES.LOLLIPOP)
+final class ProcessingInput2Packet implements
+        Processor<ProcessingNode.InputPacket, Packet<ImageProxy>> {
+
+    @NonNull
+    @Override
+    public Packet<ImageProxy> process(@NonNull ProcessingNode.InputPacket inputPacket)
+            throws ImageCaptureException {
+        ImageProxy image = inputPacket.getImageProxy();
+        ProcessingRequest request = inputPacket.getProcessingRequest();
+
+        Exif exif;
+        if (image.getFormat() == JPEG) {
+            // Extracts Exif data from JPEG.
+            try {
+                exif = Exif.createFromImageProxy(image);
+                // Rewind the buffer after reading.
+                image.getPlanes()[0].getBuffer().rewind();
+            } catch (IOException e) {
+                throw new ImageCaptureException(ERROR_FILE_IO, "Failed to extract EXIF data.", e);
+            }
+        } else {
+            // TODO: handle YUV image.
+            throw new UnsupportedOperationException();
+        }
+
+        // Default metadata based on UseCase config.
+        Rect cropRect = request.getCropRect();
+        Matrix sensorToBuffer = request.getSensorToBufferTransform();
+        int rotationDegrees = request.getRotationDegrees();
+
+        // Update metadata if the rotation is sent to the HAL.
+        if (EXIF_ROTATION_AVAILABILITY.shouldUseExifOrientation(image)) {
+            // If the image's size does not match the Exif size, it might be a vendor bug.
+            // Consider adding it to ImageCaptureRotationOptionQuirk.
+            checkState(isSizeMatch(exif, image), "Exif size does not match image size.");
+
+            Matrix halTransform = getHalTransform(request.getRotationDegrees(),
+                    new Size(exif.getWidth(), exif.getHeight()), exif.getRotation());
+            cropRect = getUpdatedCropRect(request.getCropRect(), halTransform);
+            sensorToBuffer = getUpdatedTransform(
+                    request.getSensorToBufferTransform(), halTransform);
+            rotationDegrees = exif.getRotation();
+        }
+
+        return Packet.of(image, exif, cropRect, rotationDegrees, sensorToBuffer);
+    }
+
+    private static boolean isSizeMatch(@NonNull Exif exif, @NonNull ImageProxy image) {
+        return exif.getWidth() == image.getWidth() && exif.getHeight() == image.getHeight();
+    }
+
+    /**
+     * Updates sensorToSurface transformation.
+     */
+    @NonNull
+    private static Matrix getUpdatedTransform(@NonNull Matrix sensorToSurface,
+            @NonNull Matrix halTransform) {
+        Matrix sensorToBuffer = new Matrix(sensorToSurface);
+        sensorToBuffer.postConcat(halTransform);
+        return sensorToBuffer;
+    }
+
+    /**
+     * Transforms crop rect with the HAL transformation.
+     */
+    @NonNull
+    private static Rect getUpdatedCropRect(@NonNull Rect cropRect, @NonNull Matrix halTransform) {
+        RectF rectF = new RectF(cropRect);
+        halTransform.mapRect(rectF);
+        Rect rect = new Rect();
+        rectF.round(rect);
+        return rect;
+    }
+
+    /**
+     * Calculates the transformation applied by the HAL.
+     */
+    @NonNull
+    private static Matrix getHalTransform(
+            @IntRange(from = 0, to = 359) int requestRotationDegrees,
+            @NonNull Size imageSize,
+            @IntRange(from = 0, to = 359) int exifRotationDegrees) {
+        int halRotationDegrees = requestRotationDegrees - exifRotationDegrees;
+        Size surfaceSize = is90or270(within360(halRotationDegrees))
+                ? new Size(/*width=*/imageSize.getHeight(), /*height=*/imageSize.getWidth()) :
+                imageSize;
+        return getRectToRect(
+                new RectF(0, 0, surfaceSize.getWidth(), surfaceSize.getHeight()),
+                new RectF(0, 0, imageSize.getWidth(), imageSize.getHeight()),
+                halRotationDegrees);
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
index 3284841..6b8e889 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/ProcessingNode.java
@@ -16,17 +16,26 @@
 
 package androidx.camera.core.imagecapture;
 
+import static androidx.camera.core.ImageCapture.ERROR_UNKNOWN;
 import static androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor;
 
+import static java.util.Objects.requireNonNull;
+
+import android.graphics.Bitmap;
 import android.os.Build;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.annotation.VisibleForTesting;
 import androidx.annotation.WorkerThread;
+import androidx.camera.core.ImageCapture;
+import androidx.camera.core.ImageCaptureException;
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.impl.utils.executor.CameraXExecutors;
 import androidx.camera.core.processing.Edge;
 import androidx.camera.core.processing.Node;
+import androidx.camera.core.processing.Packet;
+import androidx.camera.core.processing.Processor;
 
 import com.google.auto.value.AutoValue;
 
@@ -44,6 +53,13 @@
     @NonNull
     private final Executor mBlockingExecutor;
 
+    private Processor<InputPacket, Packet<ImageProxy>> mInput2Packet;
+    private Processor<Packet<ImageProxy>, Packet<byte[]>> mImage2JpegBytes;
+    private Processor<Bitmap2JpegBytes.In, Packet<byte[]>> mBitmap2JpegBytes;
+    private Processor<JpegBytes2Disk.In, ImageCapture.OutputFileResults> mJpegBytes2Disk;
+    private Processor<Packet<byte[]>, Packet<Bitmap>> mJpegBytes2CroppedBitmap;
+    private Processor<Packet<ImageProxy>, ImageProxy> mJpegImage2Result;
+
     /**
      * @param blockingExecutor a executor that can be blocked by long running tasks. e.g.
      *                         {@link CameraXExecutors#ioExecutor()}
@@ -58,6 +74,13 @@
         // Listen to the input edge.
         inputEdge.getEdge().setListener(
                 inputPacket -> mBlockingExecutor.execute(() -> processInputPacket(inputPacket)));
+
+        mInput2Packet = new ProcessingInput2Packet();
+        mImage2JpegBytes = new Image2JpegBytes();
+        mJpegBytes2CroppedBitmap = new JpegBytes2CroppedBitmap();
+        mBitmap2JpegBytes = new Bitmap2JpegBytes();
+        mJpegBytes2Disk = new JpegBytes2Disk();
+        mJpegImage2Result = new JpegImage2Result();
         // No output. The request callback will be invoked to deliver the final result.
         return null;
     }
@@ -68,22 +91,58 @@
 
     /**
      * Processes an {@link InputPacket} and delivers the result to {@link TakePictureManager}.
-     *
-     * TODO: implement this method.
      */
     @WorkerThread
     void processInputPacket(@NonNull InputPacket inputPacket) {
         ProcessingRequest request = inputPacket.getProcessingRequest();
-        ImageProxy image = inputPacket.getImageProxy();
-        if (inputPacket.getProcessingRequest().isInMemoryCapture()) {
-            // TODO(b/240998057): update the transform info of the output image based on request
-            //  and/or Exif info.
-            mainThreadExecutor().execute(() -> request.onFinalResult(image));
-        } else {
-            throw new UnsupportedOperationException();
+        try {
+            if (inputPacket.getProcessingRequest().isInMemoryCapture()) {
+                ImageProxy result = processInMemoryCapture(inputPacket);
+                mainThreadExecutor().execute(() -> request.onFinalResult(result));
+            } else {
+                ImageCapture.OutputFileResults result = processOnDiskCapture(inputPacket);
+                mainThreadExecutor().execute(() -> request.onFinalResult(result));
+            }
+        } catch (ImageCaptureException e) {
+            sendError(request, e);
+        } catch (RuntimeException e) {
+            // For unexpected exceptions, throw an ERROR_UNKNOWN ImageCaptureException.
+            sendError(request, new ImageCaptureException(ERROR_UNKNOWN, "Processing failed.", e));
         }
     }
 
+    @NonNull
+    @WorkerThread
+    ImageCapture.OutputFileResults processOnDiskCapture(@NonNull InputPacket inputPacket)
+            throws ImageCaptureException {
+        ProcessingRequest request = inputPacket.getProcessingRequest();
+        Packet<ImageProxy> originalImage = mInput2Packet.process(inputPacket);
+        Packet<byte[]> jpegBytes = mImage2JpegBytes.process(originalImage);
+        if (jpegBytes.hasCropping()) {
+            Packet<Bitmap> croppedBitmap = mJpegBytes2CroppedBitmap.process(jpegBytes);
+            jpegBytes = mBitmap2JpegBytes.process(
+                    Bitmap2JpegBytes.In.of(croppedBitmap, request.getJpegQuality()));
+        }
+        return mJpegBytes2Disk.process(
+                JpegBytes2Disk.In.of(jpegBytes, requireNonNull(request.getOutputFileOptions())));
+    }
+
+    @NonNull
+    @WorkerThread
+    ImageProxy processInMemoryCapture(@NonNull InputPacket inputPacket)
+            throws ImageCaptureException {
+        Packet<ImageProxy> originalImage = mInput2Packet.process(inputPacket);
+        return mJpegImage2Result.process(originalImage);
+    }
+
+    /**
+     * Sends {@link ImageCaptureException} to {@link TakePictureManager}.
+     */
+    private static void sendError(@NonNull ProcessingRequest request,
+            @NonNull ImageCaptureException e) {
+        mainThreadExecutor().execute(() -> request.onProcessFailure(e));
+    }
+
     /**
      * Input packet which is a combination of camera frame and processing request.
      */
@@ -122,4 +181,10 @@
             return new AutoValue_ProcessingNode_In(new Edge<>(), format);
         }
     }
+
+    @VisibleForTesting
+    void injectJpegBytes2CroppedBitmapForTesting(
+            @NonNull Processor<Packet<byte[]>, Packet<Bitmap>> processor) {
+        mJpegBytes2CroppedBitmap = processor;
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
index 89c2c0e..f77c182 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/imagecapture/TakePictureRequest.java
@@ -105,6 +105,16 @@
     abstract int getJpegQuality();
 
     /**
+     * Gets the capture mode of the request.
+     *
+     * <p>When there are software JPEG encoding/decoding, the value of {@link #getJpegQuality()}
+     * is used for the software encoding. The capture mode value is for calculating the JPEG
+     * quality for camera hardware encoding.
+     */
+    @ImageCapture.CaptureMode
+    abstract int getCaptureMode();
+
+    /**
      * Gets the {@link CameraCaptureCallback}s set on the {@link SessionConfig}.
      *
      * <p>This is for calling back to Camera2InterOp. See: aosp/947197.
@@ -157,6 +167,7 @@
             @NonNull Matrix sensorToBufferTransform,
             int rotationDegrees,
             int jpegQuality,
+            @ImageCapture.CaptureMode int captureMode,
             @NonNull List<CameraCaptureCallback> sessionConfigCameraCaptureCallbacks) {
         checkArgument(( null) == (outputFileOptions == null),
                 "onDiskCallback and outputFileOptions should be both null or both non-null.");
@@ -164,6 +175,6 @@
                 "One and only one on-disk or in-memory callback should be present.");
         return new AutoValue_TakePictureRequest(appExecutor, inMemoryCallback,
                 onDiskCallback, outputFileOptions, cropRect, sensorToBufferTransform,
-                rotationDegrees, jpegQuality, sessionConfigCameraCaptureCallbacks);
+                rotationDegrees, jpegQuality, captureMode, sessionConfigCameraCaptureCallbacks);
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceConfig.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceConfig.java
index 1428065..9175b5e 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceConfig.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/SurfaceConfig.java
@@ -16,11 +16,14 @@
 
 package androidx.camera.core.impl;
 
+import android.graphics.ImageFormat;
 import android.hardware.camera2.CameraCaptureSession.StateCallback;
 import android.os.Handler;
+import android.util.Size;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.internal.utils.SizeUtil;
 
 import com.google.auto.value.AutoValue;
 
@@ -77,6 +80,59 @@
     }
 
     /**
+     * Gets {@link ConfigType} from image format.
+     *
+     * <p> PRIV refers to any target whose available sizes are found using
+     * StreamConfigurationMap.getOutputSizes(Class) with no direct application-visible format,
+     * YUV refers to a target Surface using the ImageFormat.YUV_420_888 format, JPEG refers to
+     * the ImageFormat.JPEG format, and RAW refers to the ImageFormat.RAW_SENSOR format.
+     */
+    @NonNull
+    public static SurfaceConfig.ConfigType getConfigType(int imageFormat) {
+        if (imageFormat == ImageFormat.YUV_420_888) {
+            return SurfaceConfig.ConfigType.YUV;
+        } else if (imageFormat == ImageFormat.JPEG) {
+            return SurfaceConfig.ConfigType.JPEG;
+        } else if (imageFormat == ImageFormat.RAW_SENSOR) {
+            return SurfaceConfig.ConfigType.RAW;
+        } else {
+            return SurfaceConfig.ConfigType.PRIV;
+        }
+    }
+
+    /**
+     * Transform to a SurfaceConfig object with image format and size info
+     *
+     * @param imageFormat           the image format info for the surface configuration object
+     * @param size                  the size info for the surface configuration object
+     * @param surfaceSizeDefinition the surface definition for the surface configuration object
+     * @return new {@link SurfaceConfig} object
+     */
+    @NonNull
+    public static SurfaceConfig transformSurfaceConfig(int imageFormat, @NonNull Size size,
+            @NonNull SurfaceSizeDefinition surfaceSizeDefinition) {
+        ConfigType configType =
+                SurfaceConfig.getConfigType(imageFormat);
+        ConfigSize configSize = ConfigSize.NOT_SUPPORT;
+
+        // Compare with surface size definition to determine the surface configuration size
+        int sizeArea = SizeUtil.getArea(size);
+        if (sizeArea <= SizeUtil.getArea(surfaceSizeDefinition.getAnalysisSize())) {
+            configSize = ConfigSize.VGA;
+        } else if (sizeArea
+                <= SizeUtil.getArea(surfaceSizeDefinition.getPreviewSize())) {
+            configSize = ConfigSize.PREVIEW;
+        } else if (sizeArea
+                <= SizeUtil.getArea(surfaceSizeDefinition.getRecordSize())) {
+            configSize = ConfigSize.RECORD;
+        } else {
+            configSize = ConfigSize.MAXIMUM;
+        }
+
+        return SurfaceConfig.create(configType, configSize);
+    }
+
+    /**
      * The Camera2 configuration type for the surface.
      *
      * <p>These are the enumerations defined in {@link
@@ -96,8 +152,8 @@
      * android.hardware.camera2.CameraDevice#createCaptureSession(List, StateCallback, Handler)}.
      */
     public enum ConfigSize {
-        /** Default ANALYSIS size is 640x480. */
-        ANALYSIS(0),
+        /** Default VGA size is 640x480, which is the default size of Image Analysis. */
+        VGA(0),
         /**
          * PREVIEW refers to the best size match to the device's screen resolution, or to 1080p
          * (1920x1080), whichever is smaller.
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ExifData.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ExifData.java
index e347233..f8ebadf 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ExifData.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/ExifData.java
@@ -116,8 +116,11 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageInfo;
+import androidx.camera.core.ImageProxy;
 import androidx.camera.core.Logger;
 import androidx.camera.core.impl.CameraCaptureMetaData;
+import androidx.camera.core.impl.ImageOutputConfig;
 import androidx.core.util.Preconditions;
 import androidx.exifinterface.media.ExifInterface;
 
@@ -297,6 +300,27 @@
     }
 
     /**
+     * Creates a {@link ExifData} from {@link ImageProxy} and rotation degrees.
+     *
+     * @param rotationDegrees overwrites the rotation degrees in the {@link ImageInfo}.
+     */
+    @NonNull
+    public static ExifData create(@NonNull ImageProxy imageProxy,
+            @ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
+        ExifData.Builder builder = ExifData.builderForDevice();
+        imageProxy.getImageInfo().populateExifData(builder);
+
+        // Overwrites the orientation degrees value of the output image because the capture
+        // results might not have correct value when capturing image in YUV_420_888 format. See
+        // b/204375890.
+        builder.setOrientationDegrees(rotationDegrees);
+
+        return builder.setImageWidth(imageProxy.getWidth())
+                .setImageHeight(imageProxy.getHeight())
+                .build();
+    }
+
+    /**
      * Gets the byte order.
      */
     @NonNull
@@ -547,6 +571,7 @@
 
         /**
          * Sets the amount of time the sensor was exposed for, in nanoseconds.
+         *
          * @param exposureTimeNs The exposure time in nanoseconds.
          */
         @NonNull
@@ -559,6 +584,7 @@
          * Sets the lens f-number.
          *
          * <p>The lens f-number has precision 1.xx, for example, 1.80.
+         *
          * @param fNumber The f-number.
          */
         @NonNull
@@ -594,7 +620,7 @@
          * Sets the white balance mode.
          *
          * @param whiteBalanceMode The white balance mode. One of {@link WhiteBalanceMode#AUTO}
-         *                        or {@link WhiteBalanceMode#MANUAL}.
+         *                         or {@link WhiteBalanceMode#MANUAL}.
          */
         @NonNull
         public Builder setWhiteBalanceMode(@NonNull WhiteBalanceMode whiteBalanceMode) {
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/TransformUtils.java b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/TransformUtils.java
index d6643c5..f649315 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/TransformUtils.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/impl/utils/TransformUtils.java
@@ -71,6 +71,14 @@
     }
 
     /**
+     * Returns true if the crop rect does not match the size.
+     */
+    public static boolean hasCropping(@NonNull Rect cropRect, @NonNull Size size) {
+        return cropRect.left != 0 || cropRect.top != 0 || cropRect.width() != size.getWidth()
+                || cropRect.height() != size.getHeight();
+    }
+
+    /**
      * Transforms size to a {@link RectF} with zero left and top.
      */
     @NonNull
@@ -100,7 +108,7 @@
     /**
      * Rotates a {@link Size} according to the rotation degrees.
      *
-     * @param size the size to rotate
+     * @param size            the size to rotate
      * @param rotationDegrees the rotation degrees
      * @return rotated size
      * @throws IllegalArgumentException if the rotation degrees is not a multiple of 90
@@ -281,6 +289,18 @@
     }
 
     /**
+     * Updates sensor to buffer transform based on crop rect.
+     */
+    @NonNull
+    public static Matrix updateSensorToBufferTransform(
+            @NonNull Matrix original,
+            @NonNull Rect cropRect) {
+        Matrix matrix = new Matrix(original);
+        matrix.postTranslate(-cropRect.left, -cropRect.top);
+        return matrix;
+    }
+
+    /**
      * Gets the transform from a normalized space (-1, -1) - (1, 1) to the given rect.
      */
     @NonNull
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/ByteBufferOutputStream.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/ByteBufferOutputStream.java
new file mode 100644
index 0000000..f124b48
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/ByteBufferOutputStream.java
@@ -0,0 +1,63 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.io.EOFException;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.nio.ByteBuffer;
+
+/**
+ * A {@link OutputStream} that wraps around a {@link ByteBuffer}.
+ */
+@RequiresApi(21)
+public final class ByteBufferOutputStream extends OutputStream {
+
+    private final ByteBuffer mByteBuffer;
+
+    public ByteBufferOutputStream(@NonNull ByteBuffer buf) {
+        mByteBuffer = buf;
+    }
+
+    @Override
+    public void write(int b) throws IOException {
+        if (!mByteBuffer.hasRemaining()) {
+            throw new EOFException("Output ByteBuffer has no bytes remaining.");
+        }
+
+        mByteBuffer.put((byte) b);
+    }
+
+    @Override
+    public void write(byte[] b, int off, int len) throws IOException {
+        if (b == null) {
+            throw new NullPointerException();
+        } else if ((off < 0) || (off > b.length) || (len < 0)
+                || ((off + len) > b.length) || ((off + len) < 0)) {
+            throw new IndexOutOfBoundsException();
+        } else if (len == 0) {
+            return;
+        } else if (mByteBuffer.remaining() < len) {
+            throw new EOFException("Output ByteBuffer has insufficient bytes remaining.");
+        }
+
+        mByteBuffer.put(b, off, len);
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
index 02ea3bc0..12c9144 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/YuvToJpegProcessor.java
@@ -43,8 +43,6 @@
 
 import com.google.common.util.concurrent.ListenableFuture;
 
-import java.io.EOFException;
-import java.io.IOException;
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.util.List;
@@ -173,7 +171,7 @@
             ByteBuffer jpegBuf = jpegImage.getPlanes()[0].getBuffer();
             int initialPos = jpegBuf.position();
             OutputStream os = new ExifOutputStream(new ByteBufferOutputStream(jpegBuf),
-                    getExifData(imageProxy, rotationDegrees));
+                    ExifData.create(imageProxy, rotationDegrees));
             yuvImage.compressToJpeg(imageRect, quality, os);
 
             // Input can now be closed.
@@ -304,54 +302,4 @@
             mImageRect = new Rect(0, 0, size.getWidth(), size.getHeight());
         }
     }
-
-    @NonNull
-    private static ExifData getExifData(@NonNull ImageProxy imageProxy,
-            @ImageOutputConfig.RotationDegreesValue int rotationDegrees) {
-        ExifData.Builder builder = ExifData.builderForDevice();
-        imageProxy.getImageInfo().populateExifData(builder);
-
-        // Overwrites the orientation degrees value of the output image because the capture
-        // results might not have correct value when capturing image in YUV_420_888 format. See
-        // b/204375890.
-        builder.setOrientationDegrees(rotationDegrees);
-
-        return builder.setImageWidth(imageProxy.getWidth())
-                .setImageHeight(imageProxy.getHeight())
-                .build();
-    }
-
-    private static final class ByteBufferOutputStream extends OutputStream {
-
-        private final ByteBuffer mByteBuffer;
-
-        ByteBufferOutputStream(@NonNull ByteBuffer buf) {
-            mByteBuffer = buf;
-        }
-
-        @Override
-        public void write(int b) throws IOException {
-            if (!mByteBuffer.hasRemaining()) {
-                throw new EOFException("Output ByteBuffer has no bytes remaining.");
-            }
-
-            mByteBuffer.put((byte) b);
-        }
-
-        @Override
-        public void write(byte[] b, int off, int len) throws IOException {
-            if (b == null) {
-                throw new NullPointerException();
-            } else if ((off < 0) || (off > b.length) || (len < 0)
-                    || ((off + len) > b.length) || ((off + len) < 0)) {
-                throw new IndexOutOfBoundsException();
-            } else if (len == 0) {
-                return;
-            } else if (mByteBuffer.remaining() < len) {
-                throw new EOFException("Output ByteBuffer has insufficient bytes remaining.");
-            }
-
-            mByteBuffer.put(b, off, len);
-        }
-    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
index dc2bebb..0d64495 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/workaround/ExifRotationAvailability.java
@@ -40,8 +40,7 @@
     public boolean isRotationOptionSupported() {
         ImageCaptureRotationOptionQuirk quirk =
                 DeviceQuirks.get(ImageCaptureRotationOptionQuirk.class);
-
-        return quirk != null ? quirk.isSupported(CaptureConfig.OPTION_ROTATION) : true;
+        return quirk == null || quirk.isSupported(CaptureConfig.OPTION_ROTATION);
     }
 
     /**
@@ -54,10 +53,6 @@
      * @param image The captured image object.
      */
     public boolean shouldUseExifOrientation(@NonNull ImageProxy image) {
-        ImageCaptureRotationOptionQuirk quirk =
-                DeviceQuirks.get(ImageCaptureRotationOptionQuirk.class);
-
-        return (quirk != null ? quirk.isSupported(CaptureConfig.OPTION_ROTATION) : true)
-                && image.getFormat() == ImageFormat.JPEG;
+        return isRotationOptionSupported() && image.getFormat() == ImageFormat.JPEG;
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/AspectRatioUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/AspectRatioUtil.java
new file mode 100644
index 0000000..1d78446
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/AspectRatioUtil.java
@@ -0,0 +1,124 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal.utils;
+
+import static androidx.camera.core.internal.utils.SizeUtil.getArea;
+
+import android.util.Rational;
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.core.util.Preconditions;
+
+import java.util.Comparator;
+
+/**
+ * Utility class for aspect ratio related operations.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class AspectRatioUtil {
+    private static final int ALIGN16 = 16;
+
+    private AspectRatioUtil() {}
+
+    /**
+     * Returns true if the supplied resolution is a mod16 matching with the supplied aspect ratio.
+     */
+    public static boolean hasMatchingAspectRatio(@NonNull Size resolution,
+            @Nullable Rational aspectRatio) {
+        boolean isMatch = false;
+        if (aspectRatio == null) {
+            isMatch = false;
+        } else if (aspectRatio.equals(
+                new Rational(resolution.getWidth(), resolution.getHeight()))) {
+            isMatch = true;
+        } else if (getArea(resolution) >= getArea(SizeUtil.VGA_SIZE)) {
+            // Only do mod 16 calculation if the size is equal to or larger than 640x480. It is
+            // because the aspect ratio will be affected critically by mod 16 calculation if the
+            // size is small. It may result in unexpected outcome such like 256x144 will be
+            // considered as 18.5:9.
+            isMatch = isPossibleMod16FromAspectRatio(resolution,
+                    aspectRatio);
+        }
+        return isMatch;
+    }
+
+    /**
+     * For codec performance improvement, OEMs may make the supported sizes to be mod16 alignment
+     * . It means that the width or height of the supported size will be multiple of 16. The
+     * result number after applying mod16 alignment can be the larger or smaller number that is
+     * multiple of 16 and is closest to the original number. For example, a standard 16:9
+     * supported size is 1920x1080. It may become 1920x1088 on some devices because 1088 is
+     * multiple of 16. This function uses the target aspect ratio to calculate the possible
+     * original width or height inversely. And then, checks whether the possibly original width or
+     * height is in the range that the mod16 aligned height or width can support.
+     */
+    private static boolean isPossibleMod16FromAspectRatio(@NonNull Size resolution,
+            @NonNull Rational aspectRatio) {
+        int width = resolution.getWidth();
+        int height = resolution.getHeight();
+
+        Rational invAspectRatio = new Rational(/* numerator= */aspectRatio.getDenominator(),
+                /* denominator= */aspectRatio.getNumerator());
+        if (width % 16 == 0 && height % 16 == 0) {
+            return ratioIntersectsMod16Segment(Math.max(0, height - ALIGN16), width, aspectRatio)
+                    || ratioIntersectsMod16Segment(Math.max(0, width - ALIGN16), height,
+                    invAspectRatio);
+        } else if (width % 16 == 0) {
+            return ratioIntersectsMod16Segment(height, width, aspectRatio);
+        } else if (height % 16 == 0) {
+            return ratioIntersectsMod16Segment(width, height, invAspectRatio);
+        }
+        return false;
+    }
+
+
+    private static boolean ratioIntersectsMod16Segment(int height, int mod16Width,
+            Rational aspectRatio) {
+        Preconditions.checkArgument(mod16Width % 16 == 0);
+        double aspectRatioWidth =
+                height * aspectRatio.getNumerator() / (double) aspectRatio.getDenominator();
+        return aspectRatioWidth > Math.max(0, mod16Width - ALIGN16) && aspectRatioWidth < (
+                mod16Width + ALIGN16);
+    }
+
+    /** Comparator based on how close they are to the target aspect ratio. */
+    @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+    public static final class CompareAspectRatiosByDistanceToTargetRatio implements
+            Comparator<Rational> {
+        private Rational mTargetRatio;
+
+        public CompareAspectRatiosByDistanceToTargetRatio(@NonNull Rational targetRatio) {
+            mTargetRatio = targetRatio;
+        }
+
+        @Override
+        public int compare(Rational lhs, Rational rhs) {
+            if (lhs.equals(rhs)) {
+                return 0;
+            }
+
+            final Float lhsRatioDelta = Math.abs(lhs.floatValue() - mTargetRatio.floatValue());
+            final Float rhsRatioDelta = Math.abs(rhs.floatValue() - mTargetRatio.floatValue());
+
+            int result = (int) Math.signum(lhsRatioDelta - rhsRatioDelta);
+            return result;
+        }
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
new file mode 100644
index 0000000..85e4c80
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/utils/SizeUtil.java
@@ -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.camera.core.internal.utils;
+
+import android.util.Size;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Utility class for size related operations.
+ */
+@RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
+public final class SizeUtil {
+    public static final Size VGA_SIZE = new Size(640, 480);
+
+    private SizeUtil() {}
+
+    /**
+     * Returns the area of the supplied size.
+     */
+    public static int getArea(@NonNull Size size) {
+        return size.getWidth() * size.getHeight();
+    }
+}
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/OpenGlRenderer.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/OpenGlRenderer.java
index e98ed3c..6c84e13 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/OpenGlRenderer.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/OpenGlRenderer.java
@@ -16,15 +16,36 @@
 
 package androidx.camera.core.processing;
 
+import static java.util.Objects.requireNonNull;
+
+import android.opengl.EGL14;
+import android.opengl.EGLConfig;
+import android.opengl.EGLContext;
+import android.opengl.EGLDisplay;
+import android.opengl.EGLExt;
+import android.opengl.EGLSurface;
+import android.opengl.GLES11Ext;
+import android.opengl.GLES20;
+import android.util.Log;
+import android.util.Size;
 import android.view.Surface;
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.WorkerThread;
+import androidx.camera.core.Logger;
 import androidx.core.util.Preconditions;
 
-import java.util.List;
+import com.google.auto.value.AutoValue;
+
+import java.nio.ByteBuffer;
+import java.nio.ByteOrder;
+import java.nio.FloatBuffer;
+import java.util.HashMap;
+import java.util.Locale;
+import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.atomic.AtomicBoolean;
 
 /**
@@ -37,12 +58,68 @@
 @WorkerThread
 @RequiresApi(21) // TODO(b/200306659): Remove and replace with annotation on package-info.java
 public final class OpenGlRenderer {
-    static {
-        System.loadLibrary("camerax_core_opengl_renderer_jni");
-    }
+    private static final String TAG = "OpenGlRenderer";
+
+    private static final String VAR_TEXTURE_COORD = "vTextureCoord";
+    private static final String VAR_TEXTURE = "sTexture";
+
+    private static final String DEFAULT_VERTEX_SHADER = String.format(Locale.US,
+            "uniform mat4 uTexMatrix;\n"
+                    + "attribute vec4 aPosition;\n"
+                    + "attribute vec4 aTextureCoord;\n"
+                    + "varying vec2 %s;\n"
+                    + "void main() {\n"
+                    + "    gl_Position = aPosition;\n"
+                    + "    %s = (uTexMatrix * aTextureCoord).xy;\n"
+                    + "}\n", VAR_TEXTURE_COORD, VAR_TEXTURE_COORD);
+
+    private static final String DEFAULT_FRAGMENT_SHADER = String.format(Locale.US,
+            "#extension GL_OES_EGL_image_external : require\n"
+                    + "precision mediump float;\n"
+                    + "varying vec2 %s;\n"
+                    + "uniform samplerExternalOES %s;\n"
+                    + "void main() {\n"
+                    + "    gl_FragColor = texture2D(%s, %s);\n"
+                    + "}\n", VAR_TEXTURE_COORD, VAR_TEXTURE, VAR_TEXTURE, VAR_TEXTURE_COORD);
+
+    private static final float[] VERTEX_COORDS = {
+            -1.0f, -1.0f,   // 0 bottom left
+            1.0f, -1.0f,    // 1 bottom right
+            -1.0f,  1.0f,   // 2 top left
+            1.0f,  1.0f,    // 3 top right
+    };
+    private static final FloatBuffer VERTEX_BUF = createFloatBuffer(VERTEX_COORDS);
+
+    private static final float[] TEX_COORDS = {
+            0.0f, 0.0f,     // 0 bottom left
+            1.0f, 0.0f,     // 1 bottom right
+            0.0f, 1.0f,     // 2 top left
+            1.0f, 1.0f      // 3 top right
+    };
+    private static final FloatBuffer TEX_BUF = createFloatBuffer(TEX_COORDS);
+
+    private static final int SIZEOF_FLOAT = 4;
+    private static final int TEX_TARGET = GLES11Ext.GL_TEXTURE_EXTERNAL_OES;
 
     private final AtomicBoolean mInitialized = new AtomicBoolean(false);
-    private final ThreadLocal<Long> mNativeContext = new ThreadLocal<>();
+    private final Map<Surface, OutputSurface> mOutputSurfaceMap = new HashMap<>();
+    @Nullable
+    private Thread mGlThread;
+    @NonNull
+    private EGLDisplay mEglDisplay = EGL14.EGL_NO_DISPLAY;
+    @NonNull
+    private EGLContext mEglContext = EGL14.EGL_NO_CONTEXT;
+    @Nullable
+    private EGLConfig mEglConfig;
+    @NonNull
+    private EGLSurface mTempSurface = EGL14.EGL_NO_SURFACE;
+    @Nullable
+    private OutputSurface mCurrentOutputSurface;
+    private int mTexId = -1;
+    private int mProgramHandle = -1;
+    private int mTexMatrixLoc = -1;
+    private int mPositionLoc = -1;
+    private int mTexCoordLoc = -1;
 
     /**
      * Initializes the OpenGLRenderer
@@ -52,30 +129,25 @@
      * thread as this method, so called GL thread, otherwise an {@link IllegalStateException}
      * will be thrown.
      *
-     * @throws IllegalStateException if the renderer is already initialized.
+     * @throws IllegalStateException if the renderer is already initialized or failed to be
+     * initialized.
      * @throws IllegalArgumentException if the ShaderProvider fails to create shader or provides
      * invalid shader string.
      */
     public void init(@NonNull ShaderProvider shaderProvider) {
         checkInitializedOrThrow(false);
-
-        long nativeContext;
-        if (shaderProvider == ShaderProvider.DEFAULT) {
-            nativeContext = initContext(null);
-        } else {
-            List<String> varNames = getShaderVariableNames();
-            Preconditions.checkState(varNames.size() == 2);
-            String fragmentCoords = varNames.get(0);
-            String sampler = varNames.get(1);
-            String fragmentShader;
-            try {
-                fragmentShader = shaderProvider.createFragmentShader(sampler, fragmentCoords);
-            } catch (Throwable t) {
-                throw new IllegalArgumentException("Unable to create custom fragment shader", t);
-            }
-            nativeContext = initContext(fragmentShader);
+        try {
+            createEglContext();
+            createTempSurface();
+            makeCurrent(mTempSurface);
+            createProgram(shaderProvider);
+            loadLocations();
+            createTexture();
+        } catch (IllegalStateException | IllegalArgumentException e) {
+            releaseInternal();
+            throw e;
         }
-        mNativeContext.set(nativeContext);
+        mGlThread = Thread.currentThread();
         mInitialized.set(true);
     }
 
@@ -88,9 +160,8 @@
         if (!mInitialized.getAndSet(false)) {
             return;
         }
-        long nativeContext = getNativeContextOrThrow();
-        closeContext(nativeContext);
-        mNativeContext.remove();
+        checkGlThreadOrThrow();
+        releaseInternal();
     }
 
     /**
@@ -101,9 +172,21 @@
      */
     public void setOutputSurface(@NonNull Surface surface) {
         checkInitializedOrThrow(true);
-        long nativeContext = getNativeContextOrThrow();
+        checkGlThreadOrThrow();
 
-        setWindowSurface(nativeContext, surface);
+        if (!mOutputSurfaceMap.containsKey(surface)) {
+            EGLSurface eglSurface = createWindowSurface(mEglDisplay, requireNonNull(mEglConfig),
+                    surface);
+            Size size = getSurfaceSize(eglSurface);
+            mOutputSurfaceMap.put(surface, OutputSurface.of(eglSurface, size.getWidth(),
+                    size.getHeight()));
+        }
+        mCurrentOutputSurface = requireNonNull(mOutputSurfaceMap.get(surface));
+        makeCurrent(mCurrentOutputSurface.getEglSurface());
+
+        GLES20.glViewport(0, 0, mCurrentOutputSurface.getWidth(),
+                mCurrentOutputSurface.getHeight());
+        GLES20.glScissor(0, 0, mCurrentOutputSurface.getWidth(), mCurrentOutputSurface.getHeight());
     }
 
     /**
@@ -115,9 +198,9 @@
      */
     public int getTextureName() {
         checkInitializedOrThrow(true);
-        long nativeContext = getNativeContextOrThrow();
+        checkGlThreadOrThrow();
 
-        return getTexName(nativeContext);
+        return mTexId;
     }
 
     /**
@@ -128,9 +211,263 @@
      */
     public void render(long timestampNs, @NonNull float[] textureTransform) {
         checkInitializedOrThrow(true);
-        long nativeContext = getNativeContextOrThrow();
+        checkGlThreadOrThrow();
 
-        renderTexture(nativeContext, timestampNs, textureTransform);
+        if (mCurrentOutputSurface == null) {
+            return;
+        }
+
+        // Select the program.
+        GLES20.glUseProgram(mProgramHandle);
+        checkGlErrorOrThrow("glUseProgram");
+
+        // Set the texture.
+        GLES20.glActiveTexture(GLES20.GL_TEXTURE0);
+        GLES20.glBindTexture(TEX_TARGET, mTexId);
+
+        // TODO(b/245855601): Upload the matrix to GPU when textureTransform is changed.
+        // Copy the texture transformation matrix over.
+        GLES20.glUniformMatrix4fv(mTexMatrixLoc, /*count=*/1, /*transpose=*/false, textureTransform,
+                /*offset=*/0);
+        checkGlErrorOrThrow("glUniformMatrix4fv");
+
+        // Enable the "aPosition" vertex attribute.
+        GLES20.glEnableVertexAttribArray(mPositionLoc);
+        checkGlErrorOrThrow("glEnableVertexAttribArray");
+
+        // Connect vertexBuffer to "aPosition".
+        int coordsPerVertex = 2;
+        int vertexStride = 0;
+        GLES20.glVertexAttribPointer(mPositionLoc, coordsPerVertex, GLES20.GL_FLOAT,
+                /*normalized=*/false, vertexStride, VERTEX_BUF);
+        checkGlErrorOrThrow("glVertexAttribPointer");
+
+        // Enable the "aTextureCoord" vertex attribute.
+        GLES20.glEnableVertexAttribArray(mTexCoordLoc);
+        checkGlErrorOrThrow("glEnableVertexAttribArray");
+
+        // Connect texBuffer to "aTextureCoord".
+        int coordsPerTex = 2;
+        int texStride = 0;
+        GLES20.glVertexAttribPointer(mTexCoordLoc, coordsPerTex, GLES20.GL_FLOAT,
+                /*normalized=*/false, texStride, TEX_BUF);
+        checkGlErrorOrThrow("glVertexAttribPointer");
+
+        // Draw the rect.
+        GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /*firstVertex=*/0, /*vertexCount=*/4);
+        checkGlErrorOrThrow("glDrawArrays");
+
+        // TODO(b/245855601): Figure out if these calls are necessary.
+        // Done -- disable vertex array, texture, and program.
+        GLES20.glDisableVertexAttribArray(mPositionLoc);
+        GLES20.glDisableVertexAttribArray(mTexCoordLoc);
+        GLES20.glUseProgram(0);
+        GLES20.glBindTexture(TEX_TARGET, 0);
+
+        // Set timestamp
+        EGLExt.eglPresentationTimeANDROID(mEglDisplay, mCurrentOutputSurface.getEglSurface(),
+                timestampNs);
+
+        // Swap buffer
+        if (!EGL14.eglSwapBuffers(mEglDisplay, mCurrentOutputSurface.getEglSurface())) {
+            Logger.w(TAG, "Failed to swap buffers with EGL error: 0x" + Integer.toHexString(
+                    EGL14.eglGetError()));
+        }
+    }
+
+    private void createEglContext() {
+        mEglDisplay = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
+        if (Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY)) {
+            throw new IllegalStateException("Unable to get EGL14 display");
+        }
+        int[] version = new int[2];
+        if (!EGL14.eglInitialize(mEglDisplay, version, 0, version, 1)) {
+            mEglDisplay = EGL14.EGL_NO_DISPLAY;
+            throw new IllegalStateException("Unable to initialize EGL14");
+        }
+        int[] attribToChooseConfig = {
+                EGL14.EGL_RED_SIZE, 8,
+                EGL14.EGL_GREEN_SIZE, 8,
+                EGL14.EGL_BLUE_SIZE, 8,
+                EGL14.EGL_ALPHA_SIZE, 8,
+                EGL14.EGL_RENDERABLE_TYPE, EGL14.EGL_OPENGL_ES2_BIT,
+                EGLExt.EGL_RECORDABLE_ANDROID, EGL14.EGL_TRUE,
+                EGL14.EGL_SURFACE_TYPE, EGL14.EGL_WINDOW_BIT | EGL14.EGL_PBUFFER_BIT,
+                EGL14.EGL_NONE
+        };
+        EGLConfig[] configs = new EGLConfig[1];
+        int[] numConfigs = new int[1];
+        if (!EGL14.eglChooseConfig(mEglDisplay, attribToChooseConfig, 0, configs, 0, configs.length,
+                numConfigs, 0)) {
+            throw new IllegalStateException("Unable to find a suitable EGLConfig");
+        }
+        EGLConfig config = configs[0];
+        int[] attribToCreateContext = {
+                EGL14.EGL_CONTEXT_CLIENT_VERSION, 2,
+                EGL14.EGL_NONE
+        };
+        EGLContext context = EGL14.eglCreateContext(mEglDisplay, config, EGL14.EGL_NO_CONTEXT,
+                attribToCreateContext, 0);
+        checkEglErrorOrThrow("eglCreateContext");
+        mEglConfig = config;
+        mEglContext = context;
+
+        // Confirm with query.
+        int[] values = new int[1];
+        EGL14.eglQueryContext(mEglDisplay, mEglContext, EGL14.EGL_CONTEXT_CLIENT_VERSION, values,
+                0);
+        Log.d(TAG, "EGLContext created, client version " + values[0]);
+    }
+
+    private void createTempSurface() {
+        mTempSurface = createPBufferSurface(mEglDisplay, requireNonNull(mEglConfig), /*width=*/1,
+                /*height=*/1);
+    }
+
+    private void makeCurrent(@NonNull EGLSurface eglSurface) {
+        Preconditions.checkNotNull(mEglDisplay);
+        Preconditions.checkNotNull(mEglContext);
+        if (!EGL14.eglMakeCurrent(mEglDisplay, eglSurface, eglSurface, mEglContext)) {
+            throw new IllegalStateException("eglMakeCurrent failed");
+        }
+    }
+
+    private void createProgram(@NonNull ShaderProvider shaderProvider) {
+        int vertexShader = -1;
+        int fragmentShader = -1;
+        int program = -1;
+        try {
+            vertexShader = loadShader(GLES20.GL_VERTEX_SHADER, DEFAULT_VERTEX_SHADER);
+            fragmentShader = loadFragmentShader(shaderProvider);
+            program = GLES20.glCreateProgram();
+            checkGlErrorOrThrow("glCreateProgram");
+            GLES20.glAttachShader(program, vertexShader);
+            checkGlErrorOrThrow("glAttachShader");
+            GLES20.glAttachShader(program, fragmentShader);
+            checkGlErrorOrThrow("glAttachShader");
+            GLES20.glLinkProgram(program);
+            int[] linkStatus = new int[1];
+            GLES20.glGetProgramiv(program, GLES20.GL_LINK_STATUS, linkStatus, /*offset=*/0);
+            if (linkStatus[0] != GLES20.GL_TRUE) {
+                throw new IllegalStateException(
+                        "Could not link program: " + GLES20.glGetProgramInfoLog(program));
+            }
+            mProgramHandle = program;
+        } catch (IllegalStateException | IllegalArgumentException e) {
+            if (vertexShader != -1) {
+                GLES20.glDeleteShader(vertexShader);
+            }
+            if (fragmentShader != -1) {
+                GLES20.glDeleteShader(fragmentShader);
+            }
+            if (program != -1) {
+                GLES20.glDeleteProgram(program);
+            }
+            throw e;
+        }
+    }
+
+    private void loadLocations() {
+        mPositionLoc = GLES20.glGetAttribLocation(mProgramHandle, "aPosition");
+        checkLocationOrThrow(mPositionLoc, "aPosition");
+        mTexCoordLoc = GLES20.glGetAttribLocation(mProgramHandle, "aTextureCoord");
+        checkLocationOrThrow(mTexCoordLoc, "aTextureCoord");
+        mTexMatrixLoc = GLES20.glGetUniformLocation(mProgramHandle, "uTexMatrix");
+        checkLocationOrThrow(mTexMatrixLoc, "uTexMatrix");
+    }
+
+    private void createTexture() {
+        int[] textures = new int[1];
+        GLES20.glGenTextures(1, textures, 0);
+        checkGlErrorOrThrow("glGenTextures");
+
+        int texId = textures[0];
+        GLES20.glBindTexture(TEX_TARGET, texId);
+        checkGlErrorOrThrow("glBindTexture " + texId);
+
+        GLES20.glTexParameterf(TEX_TARGET, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_NEAREST);
+        GLES20.glTexParameterf(TEX_TARGET, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
+        GLES20.glTexParameteri(TEX_TARGET, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
+        GLES20.glTexParameteri(TEX_TARGET, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
+        checkGlErrorOrThrow("glTexParameter");
+
+        mTexId = texId;
+    }
+
+    private int loadFragmentShader(@NonNull ShaderProvider shaderProvider) {
+        if (shaderProvider == ShaderProvider.DEFAULT) {
+            return loadShader(GLES20.GL_FRAGMENT_SHADER, DEFAULT_FRAGMENT_SHADER);
+        } else {
+            // Throw IllegalArgumentException if the shader provider can not provide a valid
+            // fragment shader.
+            String source;
+            try {
+                source = shaderProvider.createFragmentShader(VAR_TEXTURE, VAR_TEXTURE_COORD);
+                // A simple check to workaround custom shader doesn't contain required variable.
+                // See b/241193761.
+                if (source == null || !source.contains(VAR_TEXTURE_COORD) || !source.contains(
+                        VAR_TEXTURE)) {
+                    throw new IllegalArgumentException("Invalid fragment shader");
+                }
+                return loadShader(GLES20.GL_FRAGMENT_SHADER, source);
+            } catch (Throwable t) {
+                if (t instanceof IllegalArgumentException) {
+                    throw t;
+                }
+                throw new IllegalArgumentException("Unable to compile fragment shader", t);
+            }
+        }
+    }
+
+    @NonNull
+    private Size getSurfaceSize(@NonNull EGLSurface eglSurface) {
+        int width = querySurface(mEglDisplay, eglSurface, EGL14.EGL_WIDTH);
+        int height = querySurface(mEglDisplay, eglSurface, EGL14.EGL_HEIGHT);
+        return new Size(width, height);
+    }
+
+    private void releaseInternal() {
+        // Delete program
+        if (mProgramHandle != -1) {
+            GLES20.glDeleteProgram(mProgramHandle);
+            mProgramHandle = -1;
+        }
+
+        // Destroy EGLSurfaces
+        for (OutputSurface outputSurface : mOutputSurfaceMap.values()) {
+            EGL14.eglDestroySurface(mEglDisplay, outputSurface.getEglSurface());
+        }
+        mOutputSurfaceMap.clear();
+
+        // Destroy temp surface
+        if (!Objects.equals(mTempSurface, EGL14.EGL_NO_SURFACE)) {
+            EGL14.eglDestroySurface(mEglDisplay, mTempSurface);
+            mTempSurface = EGL14.EGL_NO_SURFACE;
+        }
+
+        // Destroy EGLContext and terminate display
+        if (!Objects.equals(mEglDisplay, EGL14.EGL_NO_DISPLAY)) {
+            if (!Objects.equals(mEglContext, EGL14.EGL_NO_CONTEXT)) {
+                // Ignore the result of eglMakeCurrent with EGL_NO_SURFACE because it returns false
+                // on some devices.
+                EGL14.eglMakeCurrent(mEglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE,
+                        mEglContext);
+                EGL14.eglDestroyContext(mEglDisplay, mEglContext);
+                mEglContext = EGL14.EGL_NO_CONTEXT;
+            }
+            EGL14.eglTerminate(mEglDisplay);
+            mEglDisplay = EGL14.EGL_NO_DISPLAY;
+        }
+
+        // Reset other members
+        mEglConfig = null;
+        mProgramHandle = -1;
+        mTexMatrixLoc = -1;
+        mPositionLoc = -1;
+        mTexCoordLoc = -1;
+        mTexId = -1;
+        mCurrentOutputSurface = null;
+        mGlThread = null;
     }
 
     private void checkInitializedOrThrow(boolean shouldInitialized) {
@@ -140,26 +477,112 @@
         Preconditions.checkState(result, message);
     }
 
-    private long getNativeContextOrThrow() {
-        Long nativeContext = mNativeContext.get();
-        Preconditions.checkState(nativeContext != null,
+    private void checkGlThreadOrThrow() {
+        Preconditions.checkState(mGlThread == Thread.currentThread(),
                 "Method call must be called on the GL thread.");
-        return nativeContext;
+    }
+
+    @SuppressWarnings("SameParameterValue") // currently hard code width/height with 1/1
+    @NonNull
+    private static EGLSurface createPBufferSurface(@NonNull EGLDisplay eglDisplay,
+            @NonNull EGLConfig eglConfig, int width, int height) {
+        int[] surfaceAttrib = {
+                EGL14.EGL_WIDTH, width,
+                EGL14.EGL_HEIGHT, height,
+                EGL14.EGL_NONE
+        };
+        EGLSurface eglSurface = EGL14.eglCreatePbufferSurface(eglDisplay, eglConfig, surfaceAttrib,
+                /*offset=*/0);
+        checkEglErrorOrThrow("eglCreatePbufferSurface");
+        if (eglSurface == null) {
+            throw new IllegalStateException("surface was null");
+        }
+        return eglSurface;
     }
 
     @NonNull
-    private static native List<String> getShaderVariableNames();
+    private static EGLSurface createWindowSurface(@NonNull EGLDisplay eglDisplay,
+            @NonNull EGLConfig eglConfig, @NonNull Surface surface) {
+        // Create a window surface, and attach it to the Surface we received.
+        int[] surfaceAttrib = {
+                EGL14.EGL_NONE
+        };
+        EGLSurface eglSurface = EGL14.eglCreateWindowSurface(eglDisplay, eglConfig, surface,
+                surfaceAttrib, /*offset=*/0);
+        checkEglErrorOrThrow("eglCreateWindowSurface");
+        if (eglSurface == null) {
+            throw new IllegalStateException("surface was null");
+        }
+        return eglSurface;
+    }
 
-    private static native long initContext(@Nullable String fragmentShader);
+    private static int loadShader(int shaderType, @NonNull String source) {
+        int shader = GLES20.glCreateShader(shaderType);
+        checkGlErrorOrThrow("glCreateShader type=" + shaderType);
+        GLES20.glShaderSource(shader, source);
+        GLES20.glCompileShader(shader);
+        int[] compiled = new int[1];
+        GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, compiled, /*offset=*/0);
+        if (compiled[0] == 0) {
+            Logger.w(TAG, "Could not compile shader: " + source);
+            GLES20.glDeleteShader(shader);
+            throw new IllegalStateException(
+                    "Could not compile shader type " + shaderType + ":" + GLES20.glGetShaderInfoLog(
+                            shader));
+        }
+        return shader;
+    }
 
-    private static native boolean setWindowSurface(long nativeContext, @Nullable Surface surface);
+    private static int querySurface(@NonNull EGLDisplay eglDisplay, @NonNull EGLSurface eglSurface,
+             int what) {
+        int[] value = new int[1];
+        EGL14.eglQuerySurface(eglDisplay, eglSurface, what, value, /*offset=*/0);
+        return value[0];
+    }
 
-    private static native int getTexName(long nativeContext);
+    @NonNull
+    public static FloatBuffer createFloatBuffer(@NonNull float[] coords) {
+        ByteBuffer bb = ByteBuffer.allocateDirect(coords.length * SIZEOF_FLOAT);
+        bb.order(ByteOrder.nativeOrder());
+        FloatBuffer fb = bb.asFloatBuffer();
+        fb.put(coords);
+        fb.position(0);
+        return fb;
+    }
 
-    private static native boolean renderTexture(
-            long nativeContext,
-            long timestampNs,
-            @NonNull float[] textureTransform);
+    private static void checkLocationOrThrow(int location, @NonNull String label) {
+        if (location < 0) {
+            throw new IllegalStateException("Unable to locate '" + label + "' in program");
+        }
+    }
 
-    private static native void closeContext(long nativeContext);
+    private static void checkEglErrorOrThrow(@NonNull String op) {
+        int error = EGL14.eglGetError();
+        if (error != EGL14.EGL_SUCCESS) {
+            throw new IllegalStateException(op + ": EGL error: 0x" + Integer.toHexString(error));
+        }
+    }
+
+    private static void checkGlErrorOrThrow(@NonNull String op) {
+        int error = GLES20.glGetError();
+        if (error != GLES20.GL_NO_ERROR) {
+            throw new IllegalStateException(op + ": GL error 0x" + Integer.toHexString(error));
+        }
+    }
+
+    @AutoValue
+    abstract static class OutputSurface {
+
+        @NonNull
+        static OutputSurface of(@NonNull EGLSurface eglSurface, int width, int height) {
+            return new AutoValue_OpenGlRenderer_OutputSurface(eglSurface, width, height);
+        }
+
+        @NonNull
+        abstract EGLSurface getEglSurface();
+
+        abstract int getWidth();
+
+        abstract int getHeight();
+    }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java b/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
index 497bb84..092a441 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/processing/Packet.java
@@ -26,8 +26,10 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
+import androidx.camera.core.ImageInfo;
 import androidx.camera.core.ImageProxy;
 import androidx.camera.core.impl.utils.Exif;
+import androidx.camera.core.impl.utils.TransformUtils;
 
 import com.google.auto.value.AutoValue;
 
@@ -68,8 +70,12 @@
 
     /**
      * The Exif info extracted from JPEG bytes.
+     *
+     * @return null if {@link #getData()} is a non-JPEG {@link ImageProxy}. {@link Exif} does not
+     * work with non-JPEG format. In that case, the exif data can be obtained via
+     * {@link ImageInfo#populateExifData}.
      */
-    @NonNull
+    @Nullable
     public abstract Exif getExif();
 
     /**
@@ -114,10 +120,17 @@
     public abstract Matrix getSensorToBufferTransform();
 
     /**
-     * Creates {@link Bitmap} {@link Packet}.
+     * Returns true if the {@link Packet} needs cropping.
+     */
+    public boolean hasCropping() {
+        return TransformUtils.hasCropping(getCropRect(), getSize());
+    }
+
+    /**
+     * Creates {@link Bitmap} based {@link Packet}.
      */
     @NonNull
-    public static Packet<Bitmap> of(@NonNull Bitmap data, @Nullable Exif exif,
+    public static Packet<Bitmap> of(@NonNull Bitmap data, @NonNull Exif exif,
             @NonNull Rect cropRect, int rotationDegrees, @NonNull Matrix sensorToBufferTransform) {
         return new AutoValue_Packet<>(data, exif, ImageFormat.FLEX_RGBA_8888,
                 new Size(data.getWidth(), data.getHeight()),
@@ -125,10 +138,21 @@
     }
 
     /**
-     * Creates {@link Packet}.
+     * Creates {@link ImageProxy} based {@link Packet}.
      */
     @NonNull
-    public static <T> Packet<T> of(@NonNull T data, @NonNull Exif exif,
+    public static Packet<ImageProxy> of(@NonNull ImageProxy data, @Nullable Exif exif,
+            @NonNull Rect cropRect, int rotationDegrees, @NonNull Matrix sensorToBufferTransform) {
+        return new AutoValue_Packet<>(data, exif, data.getFormat(),
+                new Size(data.getWidth(), data.getHeight()), cropRect, rotationDegrees,
+                sensorToBufferTransform);
+    }
+
+    /**
+     * Creates byte array based {@link Packet}.
+     */
+    @NonNull
+    public static Packet<byte[]> of(@NonNull byte[] data, @NonNull Exif exif,
             int format, @NonNull Size size, @NonNull Rect cropRect,
             int rotationDegrees, @NonNull Matrix sensorToBufferTransform) {
         return new AutoValue_Packet<>(data, exif, format, size, cropRect,
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
index 8f9ce2f..283218b 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ImageCaptureTest.kt
@@ -264,6 +264,9 @@
         // Act.
         requestProcessor.sendRequest(request)
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         verify(request).dispatchImage(any())
     }
@@ -278,6 +281,9 @@
             // Act.
             requestProcessor.sendRequest(request)
 
+            // Ensure tasks are posted to the processing executor
+            shadowOf(getMainLooper()).idle()
+
             // Assert.
             verify(request).dispatchImage(any())
         }
@@ -297,6 +303,9 @@
         requestProcessor.sendRequest(request0)
         requestProcessor.sendRequest(request1)
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         // Has processing request but not complete.
         assertThat(captorFutureRef.get()).isNotNull()
@@ -307,6 +316,9 @@
         // Complete request0.
         captorFutureRef.getAndSet(null)!!.set(mock(ImageProxy::class.java))
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         // request0 is complete and request1 is in processing.
         verify(request0).dispatchImage(any())
@@ -317,6 +329,9 @@
         // Complete request1.
         captorFutureRef.getAndSet(null)!!.set(mock(ImageProxy::class.java))
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         verify(request1).dispatchImage(any())
     }
@@ -332,6 +347,9 @@
             val request = createImageCaptureRequest()
             requestProcessor.sendRequest(request)
 
+            // Ensure tasks are posted to the processing executor
+            shadowOf(getMainLooper()).idle()
+
             // Save the dispatched images.
             val captor = ArgumentCaptor.forClass(ImageProxy::class.java)
             verify(request).dispatchImage(captor.capture())
@@ -344,6 +362,9 @@
         val request = createImageCaptureRequest()
         requestProcessor.sendRequest(request)
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         verify(request, never()).dispatchImage(any())
 
@@ -351,6 +372,9 @@
         // Close one image to trigger next processing.
         images.poll()!!.close()
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         // It should trigger next processing.
         verify(request).dispatchImage(any())
@@ -370,6 +394,9 @@
             val request = createImageCaptureRequest()
             requestList.add(request)
             requestProcessor.sendRequest(request)
+
+            // Ensure tasks are posted to the processing executor
+            shadowOf(getMainLooper()).idle()
         }
 
         // Act.
@@ -377,6 +404,9 @@
         val throwable = RuntimeException(errorMsg)
         requestProcessor.cancelRequests(throwable)
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Assert.
         for (request in requestList) {
             verify(request).notifyCallbackError(anyInt(), eq(errorMsg), eq(throwable))
@@ -397,6 +427,9 @@
         // Act.
         requestProcessor.sendRequest(request)
 
+        // Ensure tasks are posted to the processing executor
+        shadowOf(getMainLooper()).idle()
+
         // Verify.
         verify(request).notifyCallbackError(anyInt(), eq(errorMsg), eq(throwable))
     }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
index 31040ff..06db2e8 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/PreviewTest.kt
@@ -336,8 +336,8 @@
         }
         shadowOf(getMainLooper()).idle()
 
-        // Assert: received SurfaceRequest is the pending SurfaceRequest.
-        assertThat(receivedSurfaceRequest).isSameInstanceAs(pendingSurfaceRequest)
+        // Assert: received SurfaceRequest is not the pending SurfaceRequest.
+        assertThat(receivedSurfaceRequest).isNotSameInstanceAs(pendingSurfaceRequest)
         assertThat(receivedTransformationInfo).isNotNull()
 
         // Act: set a different SurfaceProvider.
@@ -439,6 +439,7 @@
             .setTargetRotation(Surface.ROTATION_0)
             .build()
         preview.effect = surfaceEffect
+        preview.setSurfaceProvider(CameraXExecutors.directExecutor()) {}
         val previewConfig = PreviewConfig(
             cameraXConfig.getUseCaseConfigFactoryProvider(null)!!.newInstance(context).getConfig(
                 UseCaseConfigFactory.CaptureType.PREVIEW,
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
index a327e10..b690027 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeProcessingRequest.kt
@@ -19,6 +19,7 @@
 import android.graphics.Matrix
 import android.graphics.Rect
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.imagecapture.Utils.CROP_RECT
 import androidx.camera.core.impl.CaptureBundle
 
 /**
@@ -44,7 +45,7 @@
     constructor(captureBundle: CaptureBundle, callback: TakePictureCallback) : this(
         null,
         captureBundle,
-        Rect(0, 0, 0, 0),
+        CROP_RECT,
         0,
         100,
         Matrix(), callback
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
index 5ce1ea6..9f525f0 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureCallback.kt
@@ -16,7 +16,7 @@
 
 package androidx.camera.core.imagecapture
 
-import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCapture.OutputFileResults
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
 
@@ -29,12 +29,14 @@
     var inMemoryResult: ImageProxy? = null
     var captureFailure: ImageCaptureException? = null
     var processFailure: ImageCaptureException? = null
+    var onDiskResult: OutputFileResults? = null
 
     override fun onImageCaptured() {
         >
     }
 
-    override fun onFinalResult(outputFileResults: ImageCapture.OutputFileResults) {
+    override fun onFinalResult(outputFileResults: OutputFileResults) {
+        >
     }
 
     override fun onFinalResult(imageProxy: ImageProxy) {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
index 2c7e709..2ae941f 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/FakeTakePictureRequest.kt
@@ -22,6 +22,8 @@
 import androidx.camera.core.ImageCapture.OnImageCapturedCallback
 import androidx.camera.core.ImageCaptureException
 import androidx.camera.core.ImageProxy
+import androidx.camera.core.imagecapture.Utils.JPEG_QUALITY
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
 import androidx.camera.core.impl.CameraCaptureCallback
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import java.util.concurrent.Executor
@@ -90,11 +92,15 @@
     }
 
     internal override fun getRotationDegrees(): Int {
-        return 0
+        return ROTATION_DEGREES
     }
 
     internal override fun getJpegQuality(): Int {
-        return 100
+        return JPEG_QUALITY
+    }
+
+    internal override fun getCaptureMode(): Int {
+        return ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
     }
 
     override fun getSessionConfigCameraCaptureCallbacks(): MutableList<CameraCaptureCallback> {
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt
new file mode 100644
index 0000000..7e04271
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Image2JpegBytesTest.kt
@@ -0,0 +1,78 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture
+
+import android.graphics.ImageFormat.JPEG
+import android.os.Build
+import android.util.Size
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.HEIGHT
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
+import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.processing.Packet
+import androidx.camera.testing.ExifUtil.createExif
+import androidx.camera.testing.TestImageUtil.createJpegBytes
+import androidx.camera.testing.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.TestImageUtil.getAverageDiff
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [Image2JpegBytes]
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class Image2JpegBytesTest {
+
+    private val processor = Image2JpegBytes()
+
+    @Test
+    fun processJpegImage_assertOutput() {
+        // Arrange: create input
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val exif = createExif(jpegBytes)
+        val image = createJpegFakeImageProxy(jpegBytes) as ImageProxy
+        val input = Packet.of(
+            image,
+            exif,
+            CROP_RECT,
+            ROTATION_DEGREES,
+            SENSOR_TO_BUFFER
+        )
+
+        // Act.
+        val output = processor.process(input)
+
+        // Assert: the image is the same.
+        assertThat(getAverageDiff(jpegBytes, output.data)).isEqualTo(0)
+        // Assert: the Exif is extracted correctly.
+        assertThat(output.exif).isEqualTo(exif)
+        // Assert: metadata is correct.
+        assertThat(output.cropRect).isEqualTo(CROP_RECT)
+        assertThat(output.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+        assertThat(output.format).isEqualTo(JPEG)
+        assertThat(output.size).isEqualTo(Size(WIDTH, HEIGHT))
+        assertThat(output.sensorToBufferTransform).isEqualTo(SENSOR_TO_BUFFER)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
index 4222fb2..60230fe 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ImagePipelineTest.kt
@@ -17,19 +17,33 @@
 package androidx.camera.core.imagecapture
 
 import android.graphics.ImageFormat
+import android.graphics.Rect
 import android.hardware.camera2.CameraDevice
 import android.os.Build
 import android.os.Looper.getMainLooper
-import android.util.Size
 import androidx.camera.core.ImageCapture
+import androidx.camera.core.ImageCapture.CAPTURE_MODE_MAXIMIZE_QUALITY
+import androidx.camera.core.ImageCapture.CAPTURE_MODE_MINIMIZE_LATENCY
+import androidx.camera.core.ImageCapture.CaptureMode
 import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.FULL_RECT
+import androidx.camera.core.imagecapture.Utils.HEIGHT
+import androidx.camera.core.imagecapture.Utils.JPEG_QUALITY
+import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
+import androidx.camera.core.imagecapture.Utils.SIZE
+import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.imagecapture.Utils.injectRotationOptionQuirk
+import androidx.camera.core.impl.CaptureConfig
 import androidx.camera.core.impl.CaptureConfig.OPTION_ROTATION
 import androidx.camera.core.impl.ImageInputConfig
-import androidx.camera.core.impl.MutableOptionsBundle
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
 import androidx.camera.core.internal.IoConfig.OPTION_IO_EXECUTOR
+import androidx.camera.testing.TestImageUtil.createJpegBytes
+import androidx.camera.testing.TestImageUtil.createJpegFakeImageProxy
 import androidx.camera.testing.fakes.FakeImageInfo
-import androidx.camera.testing.fakes.FakeImageProxy
 import com.google.common.truth.Truth.assertThat
 import org.junit.After
 import org.junit.Before
@@ -49,9 +63,7 @@
 class ImagePipelineTest {
 
     companion object {
-        private val SIZE = Size(640, 480)
         private const val TEMPLATE_TYPE = CameraDevice.TEMPLATE_STILL_CAPTURE
-        private const val ROTATION = 99
         private val IN_MEMORY_REQUEST =
             FakeTakePictureRequest(FakeTakePictureRequest.Type.IN_MEMORY)
         private val CALLBACK = FakeTakePictureCallback()
@@ -65,9 +77,6 @@
         // Create ImageCaptureConfig.
         val builder = ImageCapture.Builder().setCaptureOptionUnpacker { _, builder ->
             builder.templateType = TEMPLATE_TYPE
-            builder.implementationOptions = MutableOptionsBundle.create().apply {
-                this.insertOption(OPTION_ROTATION, ROTATION)
-            }
         }
         builder.mutableConfig.insertOption(OPTION_IO_EXECUTOR, mainThreadExecutor())
         builder.mutableConfig.insertOption(ImageInputConfig.OPTION_INPUT_FORMAT, ImageFormat.JPEG)
@@ -81,7 +90,7 @@
 
     @Test
     fun createRequests_verifyCameraRequest() {
-        // Assert.
+        // Arrange.
         val captureInput = imagePipeline.captureNode.inputEdge
 
         // Act: create requests
@@ -93,8 +102,11 @@
             .containsExactly(captureInput.cameraCaptureCallback)
         assertThat(captureConfig.surfaces).containsExactly(captureInput.surface)
         assertThat(captureConfig.templateType).isEqualTo(TEMPLATE_TYPE)
+        val jpegQuality = captureConfig.implementationOptions
+            .retrieveOption(CaptureConfig.OPTION_JPEG_QUALITY)
+        assertThat(jpegQuality).isEqualTo(JPEG_QUALITY)
         assertThat(captureConfig.implementationOptions.retrieveOption(OPTION_ROTATION))
-            .isEqualTo(ROTATION)
+            .isEqualTo(ROTATION_DEGREES)
 
         // Act: fail the camera request.
         cameraRequest.onCaptureFailure(FAILURE)
@@ -103,6 +115,20 @@
     }
 
     @Test
+    fun createCameraRequestWithRotationQuirk_rotationNotInCaptureConfig() {
+        // Arrange.
+        injectRotationOptionQuirk()
+
+        // Act: create requests
+        val result = imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK)
+        // Assert: CameraRequest is constructed correctly.
+        val cameraRequest = result.first!!
+        val captureConfig = cameraRequest.captureConfigs.single()
+        assertThat(captureConfig.implementationOptions.retrieveOption(OPTION_ROTATION, null))
+            .isNull()
+    }
+
+    @Test
     fun createRequests_verifyProcessingRequest() {
         // Act: create requests
         val result = imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK)
@@ -121,6 +147,76 @@
     }
 
     @Test
+    fun createRequestWithCroppingAndMaxQuality_cameraRequestJpegQualityIs100() {
+        assertThat(
+            getCameraRequestJpegQuality(
+                CROP_RECT,
+                CAPTURE_MODE_MAXIMIZE_QUALITY
+            )
+        ).isEqualTo(
+            100
+        )
+    }
+
+    @Test
+    fun createRequestWithCroppingAndMinLatency_cameraRequestJpegQualityIsOriginal() {
+        assertThat(
+            getCameraRequestJpegQuality(
+                CROP_RECT,
+                CAPTURE_MODE_MINIMIZE_LATENCY
+            )
+        ).isEqualTo(
+            JPEG_QUALITY
+        )
+    }
+
+    @Test
+    fun createRequestWithoutCroppingAndMaxQuality_cameraRequestJpegQualityIsOriginal() {
+        assertThat(
+            getCameraRequestJpegQuality(
+                FULL_RECT,
+                CAPTURE_MODE_MAXIMIZE_QUALITY
+            )
+        ).isEqualTo(
+            JPEG_QUALITY
+        )
+    }
+
+    private fun getCameraRequestJpegQuality(
+        cropRect: Rect,
+        @CaptureMode captureMode: Int
+    ): Int {
+        // Arrange: TakePictureRequest with cropping
+        val request = TakePictureRequest.of(
+            mainThreadExecutor(), null,
+            object : ImageCapture.OnImageSavedCallback {
+                override fun onImageSaved(outputFileResults: ImageCapture.OutputFileResults) {
+                }
+
+                override fun onError(exception: ImageCaptureException) {
+                }
+            },
+            OUTPUT_FILE_OPTIONS,
+            cropRect,
+            SENSOR_TO_BUFFER,
+            ROTATION_DEGREES,
+            JPEG_QUALITY,
+            captureMode,
+            listOf()
+        )
+
+        // Act: create camera request.
+        val result = imagePipeline.createRequests(request, CALLBACK)
+
+        // Get JPEG quality and return.
+        val cameraRequest = result.first!!
+        val captureConfig = cameraRequest.captureConfigs.single()
+        return captureConfig.implementationOptions.retrieveOption(
+            CaptureConfig.OPTION_JPEG_QUALITY
+        )!!
+    }
+
+    @Test
     fun createRequests_captureTagMatches() {
         // Act: create requests
         val result = imagePipeline.createRequests(IN_MEMORY_REQUEST, CALLBACK)
@@ -139,9 +235,11 @@
         val processingRequest = imagePipeline.createRequests(
             IN_MEMORY_REQUEST, CALLBACK
         ).second!!
-        val image = FakeImageProxy(FakeImageInfo().apply {
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val imageInfo = FakeImageInfo().apply {
             this.setTag(processingRequest.tagBundleKey, processingRequest.stageIds.single())
-        })
+        }
+        val image = createJpegFakeImageProxy(imageInfo, jpegBytes)
 
         // Act: send processing request and the image.
         imagePipeline.postProcess(processingRequest)
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegBytes2DiskTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegBytes2DiskTest.kt
index c738cfe..c45d78d 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegBytes2DiskTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegBytes2DiskTest.kt
@@ -85,10 +85,12 @@
     fun saveToFile_verifySavedImageIsIdentical() {
         // Act.
         val path = saveFileAndGetPath()
-        // Assert.
+        // Assert: image is identical.
         val restoredBitmap = BitmapFactory.decodeFile(path)
         assertThat(getAverageDiff(restoredBitmap, createBitmap(WIDTH, HEIGHT))).isEqualTo(0)
-        assertThat(createFromFileString(path).rotation).isEqualTo(ROTATION_DEGREES)
+        // Assert: exif rotation matches the packet rotation.
+        val restoredExif = createFromFileString(path)
+        assertThat(restoredExif.rotation).isEqualTo(ROTATION_DEGREES)
     }
 
     @Test
@@ -101,7 +103,6 @@
         // Assert.
         val restoredExif = createFromFileString(path)
         assertThat(restoredExif.description).isEqualTo(EXIF_DESCRIPTION)
-        assertThat(restoredExif.rotation).isEqualTo(ROTATION_DEGREES)
     }
 
     @Test
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegImage2ResultTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegImage2ResultTest.kt
new file mode 100644
index 0000000..2b95704
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/JpegImage2ResultTest.kt
@@ -0,0 +1,77 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture
+
+import android.graphics.ImageFormat.JPEG
+import android.os.Build
+import androidx.camera.core.ImageProxy
+import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.HEIGHT
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
+import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.internal.utils.ImageUtil.jpegImageToJpegByteArray
+import androidx.camera.core.processing.Packet
+import androidx.camera.testing.ExifUtil.createExif
+import androidx.camera.testing.TestImageUtil.createJpegBytes
+import androidx.camera.testing.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.TestImageUtil.getAverageDiff
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [JpegImage2Result].
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class JpegImage2ResultTest {
+
+    private val processor = JpegImage2Result()
+
+    @Test
+    fun process_verifyOutput() {
+        // Arrange.
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val exif = createExif(jpegBytes)
+        val image = createJpegFakeImageProxy(jpegBytes) as ImageProxy
+        val input = Packet.of(
+            image,
+            exif,
+            CROP_RECT,
+            ROTATION_DEGREES,
+            SENSOR_TO_BUFFER
+        )
+        // Act.
+        val output = processor.process(input)
+
+        // Assert: image is the same.
+        val restoredJpeg = jpegImageToJpegByteArray(output)
+        assertThat(getAverageDiff(jpegBytes, restoredJpeg)).isEqualTo(0)
+        // Assert: metadata is updated based on the Packet.
+        assertThat(output.format).isEqualTo(JPEG)
+        assertThat(output.width).isEqualTo(WIDTH)
+        assertThat(output.height).isEqualTo(HEIGHT)
+        assertThat(output.cropRect).isEqualTo(CROP_RECT)
+        assertThat(output.imageInfo.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+        assertThat(output.imageInfo.sensorToBufferTransformMatrix).isEqualTo(SENSOR_TO_BUFFER)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
new file mode 100644
index 0000000..71a2a5d
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingInput2PacketTest.kt
@@ -0,0 +1,133 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.imagecapture
+
+import android.graphics.ImageFormat
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Build
+import android.util.Size
+import androidx.camera.core.imagecapture.Utils.CROP_RECT
+import androidx.camera.core.imagecapture.Utils.EXIF_DESCRIPTION
+import androidx.camera.core.imagecapture.Utils.HEIGHT
+import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
+import androidx.camera.core.imagecapture.Utils.WIDTH
+import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.imagecapture.Utils.injectRotationOptionQuirk
+import androidx.camera.core.internal.utils.ImageUtil.jpegImageToJpegByteArray
+import androidx.camera.testing.ExifUtil.updateExif
+import androidx.camera.testing.TestImageUtil.createJpegBytes
+import androidx.camera.testing.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.TestImageUtil.getAverageDiff
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+import org.robolectric.annotation.internal.DoNotInstrument
+
+/**
+ * Unit tests for [ProcessingInput2Packet]
+ */
+@RunWith(RobolectricTestRunner::class)
+@DoNotInstrument
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class ProcessingInput2PacketTest {
+
+    private val processor = ProcessingInput2Packet()
+
+    @Test
+    fun processInput_assertImageAndNonTransformationExif() {
+        // Arrange: create input
+        val jpegBytes = updateExif(createJpegBytes(640, 480)) {
+            it.description = EXIF_DESCRIPTION
+        }
+        val image = createJpegFakeImageProxy(jpegBytes)
+        val processingRequest = createProcessingRequest()
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act.
+        val output = processor.process(input)
+
+        // Assert.
+        assertThat(output.format).isEqualTo(ImageFormat.JPEG)
+        // Assert: buffer is rewound after reading Exif data.
+        val buffer = output.data.planes[0].buffer
+        assertThat(buffer.position()).isEqualTo(0)
+        // Assert: image is the same.
+        val restoredJpeg = jpegImageToJpegByteArray(output.data)
+        assertThat(getAverageDiff(jpegBytes, restoredJpeg)).isEqualTo(0)
+        // Assert: the Exif is extracted correctly.
+        assertThat(output.exif!!.description).isEqualTo(EXIF_DESCRIPTION)
+    }
+
+    @Test
+    fun withoutQuirk_outputMetadataIsBasedOnJpegExif() {
+        // Arrange: assume the rotation is 90 and it's applied by the HAL.
+        // Exif has 0 rotation because HAL applied the rotation.
+        val image = createJpegFakeImageProxy(createJpegBytes(WIDTH, HEIGHT))
+        val processingRequest = ProcessingRequest(
+            { listOf() },
+            OUTPUT_FILE_OPTIONS,
+            Rect(240, 0, HEIGHT, WIDTH),
+            90,
+            /*jpegQuality=*/100,
+            Matrix().also { it.setScale(-1F, 1F, 240F, 320F) },
+            FakeTakePictureCallback()
+        )
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act.
+        val output = processor.process(input)
+
+        // Assert: the metadata are based on exif and Packet.
+        // Rotation is 0 because exif rotation is 0
+        assertThat(output.rotationDegrees).isEqualTo(0)
+        // The crop rect is rotated 90 degrees.
+        assertThat(output.cropRect).isEqualTo(CROP_RECT)
+        assertThat(output.size).isEqualTo(Size(WIDTH, HEIGHT))
+        // Assert: the new transform will be SENSOR_TO_BUFFER (mirroring) + the 90 HAL rotation.
+        // The top-left corner is mapped to bottom-left, and the top-right corner is mapped to
+        // bottom-right.
+        val topCorners = floatArrayOf(0F, 0F, HEIGHT.toFloat(), 0F)
+        output.sensorToBufferTransform.mapPoints(topCorners)
+        assertThat(topCorners).usingTolerance(1E-4).containsExactly(
+            floatArrayOf(WIDTH.toFloat(), HEIGHT.toFloat(), WIDTH.toFloat(), 0F)
+        )
+    }
+
+    @Test
+    fun injectHalRotationQuirk_outputIgnoresExifRotation() {
+        // Arrange: create input
+        injectRotationOptionQuirk()
+        val image = createJpegFakeImageProxy(createJpegBytes(WIDTH, HEIGHT))
+        val processingRequest = createProcessingRequest()
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act.
+        val output = processor.process(input)
+
+        // Assert: the metadata are based on Packet only.
+        assertThat(output.cropRect).isEqualTo(CROP_RECT)
+        assertThat(output.rotationDegrees).isEqualTo(ROTATION_DEGREES)
+        assertThat(output.format).isEqualTo(ImageFormat.JPEG)
+        assertThat(output.size).isEqualTo(Size(WIDTH, HEIGHT))
+        assertThat(output.sensorToBufferTransform).isEqualTo(SENSOR_TO_BUFFER)
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
index b8a7591..edae2a0 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/ProcessingNodeTest.kt
@@ -16,11 +16,30 @@
 
 package androidx.camera.core.imagecapture
 
+import android.graphics.BitmapFactory
+import android.graphics.Color.BLUE
+import android.graphics.Color.YELLOW
 import android.graphics.ImageFormat
+import android.graphics.Rect
 import android.os.Build
 import android.os.Looper.getMainLooper
+import androidx.camera.core.ImageCaptureException
+import androidx.camera.core.imagecapture.Utils.EXIF_DESCRIPTION
+import androidx.camera.core.imagecapture.Utils.HEIGHT
+import androidx.camera.core.imagecapture.Utils.OUTPUT_FILE_OPTIONS
+import androidx.camera.core.imagecapture.Utils.ROTATION_DEGREES
+import androidx.camera.core.imagecapture.Utils.SENSOR_TO_BUFFER
+import androidx.camera.core.imagecapture.Utils.WIDTH
 import androidx.camera.core.imagecapture.Utils.createCaptureBundle
+import androidx.camera.core.imagecapture.Utils.createProcessingRequest
+import androidx.camera.core.impl.utils.Exif.createFromFileString
 import androidx.camera.core.impl.utils.executor.CameraXExecutors.mainThreadExecutor
+import androidx.camera.core.internal.utils.ImageUtil
+import androidx.camera.testing.ExifUtil.updateExif
+import androidx.camera.testing.TestImageUtil.createBitmap
+import androidx.camera.testing.TestImageUtil.createJpegBytes
+import androidx.camera.testing.TestImageUtil.createJpegFakeImageProxy
+import androidx.camera.testing.TestImageUtil.getAverageDiff
 import androidx.camera.testing.fakes.FakeImageInfo
 import androidx.camera.testing.fakes.FakeImageProxy
 import com.google.common.truth.Truth.assertThat
@@ -51,15 +70,93 @@
     }
 
     @Test
+    fun cropRectEqualsImageRect_croppingNotInvoked() {
+        // Arrange: create a request with no cropping
+        val callback = FakeTakePictureCallback()
+        val request = ProcessingRequest(
+            { listOf() },
+            OUTPUT_FILE_OPTIONS,
+            Rect(0, 0, WIDTH, HEIGHT),
+            ROTATION_DEGREES,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            callback
+        )
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val image = createJpegFakeImageProxy(jpegBytes)
+        // Track if cropping is invoked.
+        var croppingInvoked = false
+        node.injectJpegBytes2CroppedBitmapForTesting {
+            croppingInvoked = true
+            JpegBytes2CroppedBitmap().process(it)
+        }
+
+        // Act.
+        processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, image))
+        shadowOf(getMainLooper()).idle()
+        val filePath = callback.onDiskResult!!.savedUri!!.path!!
+
+        // Assert: restored image is not cropped.
+        val restoredBitmap = BitmapFactory.decodeFile(filePath)
+        assertThat(getAverageDiff(createBitmap(WIDTH, HEIGHT), restoredBitmap)).isEqualTo(0)
+        // Assert: cropping was not invoked.
+        assertThat(croppingInvoked).isFalse()
+    }
+
+    @Test
     fun inMemoryInputPacket_callbackInvoked() {
         // Arrange.
         val callback = FakeTakePictureCallback()
         val request = FakeProcessingRequest(createCaptureBundle(intArrayOf()), callback)
-        val image = FakeImageProxy(FakeImageInfo())
+        val jpegBytes = createJpegBytes(WIDTH, HEIGHT)
+        val image = createJpegFakeImageProxy(jpegBytes)
         // Act.
         processingNodeIn.edge.accept(ProcessingNode.InputPacket.of(request, image))
         shadowOf(getMainLooper()).idle()
-        // Assert.
-        assertThat(callback.inMemoryResult).isEqualTo(image)
+        // Assert: the output image is identical to the input.
+        val restoredJpeg = ImageUtil.jpegImageToJpegByteArray(callback.inMemoryResult!!)
+        assertThat(getAverageDiff(jpegBytes, restoredJpeg)).isEqualTo(0)
+    }
+
+    @Test
+    fun saveIncorrectImage_getsErrorCallback() {
+        // Arrange: create an invalid ImageProxy.
+        val takePictureCallback = FakeTakePictureCallback()
+        val image = FakeImageProxy(FakeImageInfo())
+        val processingRequest = createProcessingRequest(takePictureCallback)
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act: send input to the edge and wait for callback
+        processingNodeIn.edge.accept(input)
+        shadowOf(getMainLooper()).idle()
+
+        // Assert: receives a process failure.
+        assertThat(takePictureCallback.processFailure)
+            .isInstanceOf(ImageCaptureException::class.java)
+    }
+
+    @Test
+    fun saveJpegOnDisk_verifyOutput() {
+        // Arrange: create a on-disk processing request.
+        val takePictureCallback = FakeTakePictureCallback()
+        val jpegBytes = updateExif(createJpegBytes(640, 480)) {
+            it.description = EXIF_DESCRIPTION
+        }
+        val image = createJpegFakeImageProxy(jpegBytes)
+        val processingRequest = createProcessingRequest(takePictureCallback)
+        val input = ProcessingNode.InputPacket.of(processingRequest, image)
+
+        // Act: send input to the edge and wait for the saved URI
+        processingNodeIn.edge.accept(input)
+        shadowOf(getMainLooper()).idle()
+        val filePath = takePictureCallback.onDiskResult!!.savedUri!!.path!!
+
+        // Assert: image content is cropped correctly
+        val bitmap = BitmapFactory.decodeFile(filePath)
+        assertThat(getAverageDiff(bitmap, Rect(0, 0, 320, 240), BLUE)).isEqualTo(0)
+        assertThat(getAverageDiff(bitmap, Rect(321, 0, WIDTH, 240), YELLOW)).isEqualTo(0)
+        // Assert: Exif info is saved correctly.
+        val exif = createFromFileString(filePath)
+        assertThat(exif.description).isEqualTo(EXIF_DESCRIPTION)
     }
 }
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
index 64f754d..8c059cfd 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
+++ b/camera/camera-core/src/test/java/androidx/camera/core/imagecapture/Utils.kt
@@ -17,6 +17,10 @@
 package androidx.camera.core.imagecapture
 
 import android.graphics.ImageFormat
+import android.graphics.Matrix
+import android.graphics.Rect
+import android.os.Build
+import android.util.Size
 import androidx.camera.core.ImageCapture
 import androidx.camera.core.ImageProxy
 import androidx.camera.core.impl.CaptureBundle
@@ -27,6 +31,7 @@
 import androidx.camera.testing.fakes.FakeImageProxy
 import java.io.File
 import java.util.UUID
+import org.robolectric.util.ReflectionHelpers.setStaticField
 
 /**
  * Utility methods for testing image capture.
@@ -38,9 +43,39 @@
     internal const val EXIF_DESCRIPTION = "description"
     internal const val ROTATION_DEGREES = 180
     internal const val ALTITUDE = 0.1
+    internal const val JPEG_QUALITY = 90
+    internal val SENSOR_TO_BUFFER = Matrix().also { it.setScale(-1F, 1F, 320F, 240F) }
+    internal val SIZE = Size(WIDTH, HEIGHT)
+    internal val CROP_RECT = Rect(0, 240, WIDTH, HEIGHT)
+    internal val FULL_RECT = Rect(0, 0, WIDTH, HEIGHT)
     internal val TEMP_FILE = File.createTempFile(
         "unit_test_" + UUID.randomUUID().toString(), ".temp"
     ).also { it.deleteOnExit() }
+    internal val OUTPUT_FILE_OPTIONS = ImageCapture.OutputFileOptions.Builder(
+        TEMP_FILE
+    ).build()
+
+    internal fun createProcessingRequest(
+        takePictureCallback: TakePictureCallback = FakeTakePictureCallback()
+    ): ProcessingRequest {
+        return ProcessingRequest(
+            { listOf() },
+            OUTPUT_FILE_OPTIONS,
+            CROP_RECT,
+            ROTATION_DEGREES,
+            /*jpegQuality=*/100,
+            SENSOR_TO_BUFFER,
+            takePictureCallback
+        )
+    }
+
+    /**
+     * Inject a ImageCaptureRotationOptionQuirk.
+     */
+    fun injectRotationOptionQuirk() {
+        setStaticField(Build::class.java, "BRAND", "HUAWEI")
+        setStaticField(Build::class.java, "MODEL", "SNE-LX1")
+    }
 
     /**
      * Creates an empty [ImageCaptureConfig] so [ImagePipeline] constructor won't crash.
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/AspectRatioUtilTest.kt b/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/AspectRatioUtilTest.kt
new file mode 100644
index 0000000..70f5d86
--- /dev/null
+++ b/camera/camera-core/src/test/java/androidx/camera/core/internal/utils/AspectRatioUtilTest.kt
@@ -0,0 +1,121 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.camera.core.internal.utils
+
+import android.os.Build
+import android.util.Rational
+import android.util.Size
+import com.google.common.truth.Truth
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+@Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
+class AspectRatioUtilTest {
+
+    @Test
+    fun testHasMatchingAspectRatio_withNullAspectRatio() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(16, 9),
+                null
+            )
+        ).isFalse()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withSameAspectRatio() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(16, 9),
+                Rational(16, 9)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_720p() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(1280, 720),
+                Rational(16, 9)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_1080p() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(1920, 1088),
+                Rational(16, 9)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_1440p() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(2560, 1440),
+                Rational(16, 9)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_2160p() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(3840, 2160),
+                Rational(16, 9)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_1x1() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(1088, 1088),
+                Rational(1, 1)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withMod16AspectRatio_4x3() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(1024, 768),
+                Rational(4, 3)
+            )
+        ).isTrue()
+    }
+
+    @Test
+    fun testHasMatchingAspectRatio_withNonMod16AspectRatio() {
+        Truth.assertThat(
+            AspectRatioUtil.hasMatchingAspectRatio(
+                Size(1281, 721),
+                Rational(16, 9)
+            )
+        ).isFalse()
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageCaptureConfigProvider.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageCaptureConfigProvider.java
index 7e2c2b5..d12a1ac3 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageCaptureConfigProvider.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/ImageCaptureConfigProvider.java
@@ -141,11 +141,9 @@
         @NonNull
         private final Context mContext;
         @Nullable
-        VendorProcessor mVendorCaptureProcessor;
-
+        private VendorProcessor mVendorCaptureProcessor;
         @Nullable
         private volatile CameraInfo mCameraInfo;
-
         ImageCaptureEventAdapter(@NonNull ImageCaptureExtenderImpl impl,
                 @NonNull Context context,
                 @Nullable VendorProcessor vendorCaptureProcessor) {
@@ -175,6 +173,7 @@
             String cameraId = Camera2CameraInfo.from(mCameraInfo).getCameraId();
             CameraCharacteristics cameraCharacteristics =
                     Camera2CameraInfo.extractCameraCharacteristics(mCameraInfo);
+            Logger.d(TAG, "ImageCapture onInit");
             mImpl.onInit(cameraId, cameraCharacteristics, mContext);
 
             if (mVendorCaptureProcessor != null) {
@@ -200,6 +199,7 @@
         @Override
         @Nullable
         public CaptureConfig onEnableSession() {
+            Logger.d(TAG, "ImageCapture onEnableSession");
             CaptureStageImpl captureStageImpl = mImpl.onEnableSession();
             if (captureStageImpl != null) {
                 return new AdaptingCaptureStage(captureStageImpl).getCaptureConfig();
@@ -212,6 +212,7 @@
         @Override
         @Nullable
         public CaptureConfig onDisableSession() {
+            Logger.d(TAG, "ImageCapture onDisableSession");
             CaptureStageImpl captureStageImpl = mImpl.onDisableSession();
             if (captureStageImpl != null) {
                 return new AdaptingCaptureStage(captureStageImpl).getCaptureConfig();
diff --git a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/PreviewConfigProvider.java b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/PreviewConfigProvider.java
index f5a5a33..b97d8d0 100644
--- a/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/PreviewConfigProvider.java
+++ b/camera/camera-extensions/src/main/java/androidx/camera/extensions/internal/PreviewConfigProvider.java
@@ -143,10 +143,9 @@
         @NonNull
         private final Context mContext;
         @Nullable
-        final VendorProcessor mPreviewProcessor;
+        private final VendorProcessor mPreviewProcessor;
         @Nullable
         CameraInfo mCameraInfo;
-
         @GuardedBy("mLock")
         final Object mLock = new Object();
 
@@ -183,10 +182,10 @@
             synchronized (mLock) {
                 Preconditions.checkNotNull(mCameraInfo,
                         "PreviewConfigProvider was not attached.");
-
                 String cameraId = Camera2CameraInfo.from(mCameraInfo).getCameraId();
                 CameraCharacteristics cameraCharacteristics =
                         Camera2CameraInfo.extractCameraCharacteristics(mCameraInfo);
+                Logger.d(TAG, "Preview onInit");
                 mImpl.onInit(cameraId, cameraCharacteristics, mContext);
                 if (mPreviewProcessor != null) {
                     mPreviewProcessor.onInit();
@@ -213,6 +212,7 @@
         @Nullable
         public CaptureConfig onEnableSession() {
             synchronized (mLock) {
+                Logger.d(TAG, "Preview onEnableSession");
                 CaptureStageImpl captureStageImpl = mImpl.onEnableSession();
                 if (captureStageImpl != null) {
                     return new AdaptingCaptureStage(captureStageImpl).getCaptureConfig();
@@ -227,6 +227,7 @@
         @Nullable
         public CaptureConfig onDisableSession() {
             synchronized (mLock) {
+                Logger.d(TAG, "Preview onDisableSession");
                 CaptureStageImpl captureStageImpl = mImpl.onDisableSession();
                 if (captureStageImpl != null) {
                     return new AdaptingCaptureStage(captureStageImpl).getCaptureConfig();
@@ -254,6 +255,7 @@
         @Override
         public void onDeInitSession() {
             synchronized (mLock) {
+                Logger.d(TAG, "Preview onDeInit");
                 if (mPreviewProcessor != null) {
                     mPreviewProcessor.onDeInit();
                 }
diff --git a/camera/camera-testing/src/main/java/androidx/camera/testing/TestImageUtil.java b/camera/camera-testing/src/main/java/androidx/camera/testing/TestImageUtil.java
index 40086f8..0cdc3d0 100644
--- a/camera/camera-testing/src/main/java/androidx/camera/testing/TestImageUtil.java
+++ b/camera/camera-testing/src/main/java/androidx/camera/testing/TestImageUtil.java
@@ -60,9 +60,10 @@
      * Creates a {@link FakeImageProxy} from JPEG bytes.
      */
     @NonNull
-    public static FakeImageProxy createJpegFakeImageProxy(@NonNull byte[] jpegBytes) {
+    public static FakeImageProxy createJpegFakeImageProxy(@NonNull FakeImageInfo fakeImageInfo,
+            @NonNull byte[] jpegBytes) {
         Bitmap bitmap = decodeByteArray(jpegBytes, 0, jpegBytes.length);
-        FakeImageProxy image = new FakeImageProxy(new FakeImageInfo());
+        FakeImageProxy image = new FakeImageProxy(fakeImageInfo);
         image.setFormat(JPEG);
         image.setPlanes(new FakeJpegPlaneProxy[]{new FakeJpegPlaneProxy(jpegBytes)});
         image.setWidth(bitmap.getWidth());
@@ -71,6 +72,14 @@
     }
 
     /**
+     * Creates a {@link FakeImageProxy} from JPEG bytes.
+     */
+    @NonNull
+    public static FakeImageProxy createJpegFakeImageProxy(@NonNull byte[] jpegBytes) {
+        return createJpegFakeImageProxy(new FakeImageInfo(), jpegBytes);
+    }
+
+    /**
      * Generates a JPEG image.
      */
     @NonNull
@@ -98,6 +107,15 @@
     }
 
     /**
+     * Calculates the average color difference between the 2 JPEG images.
+     */
+    public static int getAverageDiff(@NonNull byte[] jpeg1, @NonNull byte[] jpeg2) {
+        return getAverageDiff(
+                decodeByteArray(jpeg1, 0, jpeg1.length),
+                decodeByteArray(jpeg2, 0, jpeg2.length));
+    }
+
+    /**
      * Calculates the average color difference between the 2 bitmaps.
      */
     public static int getAverageDiff(@NonNull Bitmap bitmap1, @NonNull Bitmap bitmap2) {
diff --git a/camera/camera-view/src/androidTest/java/androidx/camera/view/transform/FileTransformFactoryDeviceTest.kt b/camera/camera-view/src/androidTest/java/androidx/camera/view/transform/FileTransformFactoryDeviceTest.kt
index 5e77dcf..62e859f 100644
--- a/camera/camera-view/src/androidTest/java/androidx/camera/view/transform/FileTransformFactoryDeviceTest.kt
+++ b/camera/camera-view/src/androidTest/java/androidx/camera/view/transform/FileTransformFactoryDeviceTest.kt
@@ -47,48 +47,48 @@
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 @SdkSuppress(minSdkVersion = 21)
-public class FileTransformFactoryDeviceTest {
+class FileTransformFactoryDeviceTest {
 
     private lateinit var factory: FileTransformFactory
     private val contentResolver = getApplicationContext<Context>().contentResolver
 
     @get:Rule
-    public val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
+    val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
         Manifest.permission.WRITE_EXTERNAL_STORAGE
     )
 
     @Before
-    public fun setUp() {
+    fun setUp() {
         factory = FileTransformFactory()
     }
 
     @Test
-    public fun setUseRotationDegrees_getterReturnsTrue() {
+    fun setUseRotationDegrees_getterReturnsTrue() {
         factory.isUsingExifOrientation = true
         assertThat(factory.isUsingExifOrientation).isTrue()
     }
 
     @Test
-    public fun extractFromFile() {
+    fun extractFromFile() {
         factory.getOutputTransform(createImageFile()).assertMapping(1f, 1f, WIDTH, HEIGHT)
     }
 
     @Test
-    public fun extractFromFileWithExifInfo() {
+    fun extractFromFileWithExifInfo() {
         factory.isUsingExifOrientation = true
         factory.getOutputTransform(createImageFile(ExifInterface.ORIENTATION_ROTATE_90))
             .assertMapping(1f, 1f, 0, WIDTH)
     }
 
     @Test
-    public fun extractFromInputStream() {
+    fun extractFromInputStream() {
         FileInputStream(createImageFile()).use {
             factory.getOutputTransform(it).assertMapping(1f, 1f, WIDTH, HEIGHT)
         }
     }
 
     @Test
-    public fun extractFromMediaStoreUri() {
+    fun extractFromMediaStoreUri() {
         val uri = createMediaStoreImage()
         factory.getOutputTransform(contentResolver, uri).assertMapping(1f, 1f, WIDTH, HEIGHT)
         contentResolver.delete(uri, null, null)
@@ -135,7 +135,7 @@
             contentValues
         )
         contentResolver.openOutputStream(uri!!).use {
-            createBitmap().compress(Bitmap.CompressFormat.JPEG, 100, it)
+            createBitmap().compress(Bitmap.CompressFormat.JPEG, 100, it!!)
         }
         return uri
     }
diff --git a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
index 5a6d5de1..c4a00a7 100644
--- a/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
+++ b/camera/integration-tests/coretestapp/src/androidTest/java/androidx/camera/integration/core/UseCaseCombinationTest.kt
@@ -136,6 +136,34 @@
         previewMonitor.waitForStream()
     }
 
+    /** Test Combination: Preview (no surface provider) + ImageCapture */
+    @Test
+    fun previewCombinesImageCapture_withNoSurfaceProvider(): Unit = runBlocking {
+        skipTestOnCameraPipeConfig()
+
+        // Arrange.
+        val previewMonitor = PreviewMonitor()
+        val preview = initPreview(previewMonitor)
+        val imageCapture = initImageCapture()
+
+        assertThat(camera.isUseCasesCombinationSupported(preview, imageCapture)).isTrue()
+
+        // TODO(b/160249108) move off of main thread once UseCases can be attached on any thread
+        // Act.
+        withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                DEFAULT_SELECTOR,
+                preview,
+                imageCapture
+            )
+        }
+
+        // Assert.
+        imageCapture.waitForCapturing()
+        previewMonitor.waitForStreamIdle()
+    }
+
     /** Test Combination: Preview + ImageAnalysis */
     @Test
     fun previewCombinesImageAnalysis(): Unit = runBlocking {
@@ -166,6 +194,35 @@
         imageAnalysisMonitor.waitForImageAnalysis()
     }
 
+    /** Test Combination: Preview (no surface provider) + ImageAnalysis */
+    @Test
+    fun previewCombinesImageAnalysis_withNoSurfaceProvider(): Unit = runBlocking {
+        skipTestOnCameraPipeConfig()
+
+        // Arrange.
+        val previewMonitor = PreviewMonitor()
+        val preview = initPreview(previewMonitor)
+        val imageAnalysisMonitor = AnalysisMonitor()
+        val imageAnalysis = initImageAnalysis(imageAnalysisMonitor)
+
+        assertThat(camera.isUseCasesCombinationSupported(preview, imageAnalysis)).isTrue()
+
+        // TODO(b/160249108) move off of main thread once UseCases can be attached on any thread
+        // Act.
+        withContext(Dispatchers.Main) {
+            cameraProvider.bindToLifecycle(
+                fakeLifecycleOwner,
+                DEFAULT_SELECTOR,
+                preview,
+                imageAnalysis
+            )
+        }
+
+        // Assert.
+        previewMonitor.waitForStreamIdle()
+        imageAnalysisMonitor.waitForImageAnalysis()
+    }
+
     /** Test Combination: Preview + ImageAnalysis + ImageCapture  */
     @Test
     fun previewCombinesImageAnalysisAndImageCapture(): Unit = runBlocking {
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
index d5e18e7..e5bb529 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/LifecycleStatusChangeStressTest.kt
@@ -26,20 +26,22 @@
 import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.VERIFICATION_TARGET_PREVIEW
 import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.launchCameraExtensionsActivity
 import androidx.camera.integration.extensions.util.HOME_TIMEOUT_MS
-import androidx.camera.integration.extensions.util.pauseAndResumeActivity
 import androidx.camera.integration.extensions.util.takePictureAndWaitForImageSavedIdle
+import androidx.camera.integration.extensions.util.waitForPreviewViewIdle
 import androidx.camera.integration.extensions.util.waitForPreviewViewStreaming
 import androidx.camera.lifecycle.ProcessCameraProvider
 import androidx.camera.testing.CameraUtil
 import androidx.camera.testing.CameraUtil.PreTestCameraIdList
 import androidx.camera.testing.CoreAppTestUtil
 import androidx.camera.testing.LabTestRule
+import androidx.lifecycle.Lifecycle
 import androidx.test.core.app.ApplicationProvider
 import androidx.test.filters.LargeTest
 import androidx.test.platform.app.InstrumentationRegistry
 import androidx.test.rule.GrantPermissionRule
 import androidx.test.uiautomator.UiDevice
 import androidx.testutils.RepeatRule
+import androidx.testutils.withActivity
 import java.util.concurrent.TimeUnit
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
@@ -155,24 +157,30 @@
         verificationTarget: Int,
         repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
     ) {
-        var activityScenario = launchCameraExtensionsActivity(cameraId, extensionMode)
+        val activityScenario = launchCameraExtensionsActivity(cameraId, extensionMode)
 
-        try {
-            activityScenario.waitForPreviewViewStreaming()
+        with(activityScenario) {
+            use {
+                waitForPreviewViewStreaming()
 
-            repeat(repeatCount) {
-                activityScenario = activityScenario.pauseAndResumeActivity(cameraId, extensionMode)
-                if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
-                    activityScenario.waitForPreviewViewStreaming()
-                }
+                repeat(repeatCount) {
+                    withActivity {
+                        resetPreviewViewIdleStateIdlingResource()
+                        resetPreviewViewStreamingStateIdlingResource()
+                    }
+                    moveToState(Lifecycle.State.CREATED)
+                    waitForPreviewViewIdle()
+                    moveToState(Lifecycle.State.RESUMED)
 
-                if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
-                    activityScenario.takePictureAndWaitForImageSavedIdle()
+                    if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
+                        waitForPreviewViewStreaming()
+                    }
+
+                    if (verificationTarget.and(VERIFICATION_TARGET_IMAGE_CAPTURE) != 0) {
+                        takePictureAndWaitForImageSavedIdle()
+                    }
                 }
             }
-        } finally {
-            // Finish the activity
-            activityScenario.close()
         }
     }
 
@@ -194,17 +202,23 @@
         verificationTarget: Int,
         repeatCount: Int = STRESS_TEST_OPERATION_REPEAT_COUNT
     ) {
-        var activityScenario = launchCameraExtensionsActivity(cameraId, extensionMode)
-
-        activityScenario.waitForPreviewViewStreaming()
-
-        repeat(repeatCount) {
-            activityScenario = activityScenario.pauseAndResumeActivity(cameraId, extensionMode)
-            activityScenario.waitForPreviewViewStreaming()
-        }
+        val activityScenario = launchCameraExtensionsActivity(cameraId, extensionMode)
 
         with(activityScenario) {
             use {
+                waitForPreviewViewStreaming()
+
+                repeat(repeatCount) {
+                    withActivity {
+                        resetPreviewViewIdleStateIdlingResource()
+                        resetPreviewViewStreamingStateIdlingResource()
+                    }
+                    moveToState(Lifecycle.State.CREATED)
+                    waitForPreviewViewIdle()
+                    moveToState(Lifecycle.State.RESUMED)
+                    waitForPreviewViewStreaming()
+                }
+
                 if (verificationTarget.and(VERIFICATION_TARGET_PREVIEW) != 0) {
                     waitForPreviewViewStreaming()
                 }
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsActivityTestExtensions.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsActivityTestExtensions.kt
index 18a810d..082aa68 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsActivityTestExtensions.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsActivityTestExtensions.kt
@@ -19,9 +19,7 @@
 import android.util.Log
 import androidx.camera.integration.extensions.CameraExtensionsActivity
 import androidx.camera.integration.extensions.R
-import androidx.camera.integration.extensions.util.CameraXExtensionsTestUtil.relaunchCameraExtensionsActivity
 import androidx.camera.view.PreviewView
-import androidx.lifecycle.Lifecycle
 import androidx.test.core.app.ActivityScenario
 import androidx.test.espresso.Espresso
 import androidx.test.espresso.IdlingRegistry
@@ -114,22 +112,4 @@
             }
         }
     }
-}
-
-/**
- * Pauses and resumes the activity. Returns the new activityScenario because the original activity
- * might be destroyed and new one is created.
- */
-internal fun ActivityScenario<CameraExtensionsActivity>.pauseAndResumeActivity(
-    cameraId: String,
-    extensionMode: Int
-): ActivityScenario<CameraExtensionsActivity> {
-    withActivity { resetPreviewViewIdleStateIdlingResource() }
-    moveToState(Lifecycle.State.CREATED)
-    waitForPreviewViewIdle()
-    withActivity { resetPreviewViewStreamingStateIdlingResource() }
-    // The original activity might be destroyed when re-launch the activity. Re-retrieve the
-    // returned activityScenario from relaunchCameraExtensionsActivity() to run the following test
-    // steps.
-    return relaunchCameraExtensionsActivity(cameraId, extensionMode)
 }
\ No newline at end of file
diff --git a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
index 70c0512..088da77 100644
--- a/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
+++ b/camera/integration-tests/extensionstestapp/src/androidTest/java/androidx/camera/integration/extensions/util/CameraXExtensionsTestUtil.kt
@@ -222,29 +222,6 @@
         return activityScenario
     }
 
-    @JvmStatic
-    fun relaunchCameraExtensionsActivity(
-        cameraId: String,
-        extensionMode: Int,
-        deleteCapturedImages: Boolean = true,
-    ): ActivityScenario<CameraExtensionsActivity> {
-        val intent = ApplicationProvider.getApplicationContext<Context>().packageManager
-            .getLaunchIntentForPackage(BASIC_SAMPLE_PACKAGE)?.apply {
-                putExtra(IntentExtraKey.INTENT_EXTRA_KEY_CAMERA_ID, cameraId)
-                putExtra(IntentExtraKey.INTENT_EXTRA_KEY_EXTENSION_MODE, extensionMode)
-                putExtra(
-                    IntentExtraKey.INTENT_EXTRA_KEY_DELETE_CAPTURED_IMAGE,
-                    deleteCapturedImages
-                )
-                flags = Intent.FLAG_ACTIVITY_SINGLE_TOP
-            }
-
-        val activityScenario: ActivityScenario<CameraExtensionsActivity> =
-            ActivityScenario.launch(intent)
-
-        return activityScenario
-    }
-
     /**
      * Large stress test repeat count to run the test
      */
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 3e15e08..faa2325 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
@@ -506,8 +506,8 @@
             val uri = resolver.insert(contentUri, values)
             try {
                 val fos = resolver.openOutputStream(uri!!)
-                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos)
-                fos!!.close()
+                bitmap.compress(Bitmap.CompressFormat.PNG, 100, fos!!)
+                fos.close()
                 showToast("Saved: $displayName")
             } catch (e: IOException) {
                 Log.e(
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
index 374103f..141ec63 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyManager.java
@@ -118,26 +118,28 @@
 
         // register properties
         executor.execute(() -> {
-            for (PropertyIdAreaId propertyIdAndAreadId : propertyIdWithAreaIds) {
+            for (PropertyIdAreaId propertyIdAreaId : propertyIdWithAreaIds) {
                 try {
-                    mPropertyRequestProcessor.registerProperty(propertyIdAndAreadId.getPropertyId(),
+                    mPropertyRequestProcessor.registerProperty(propertyIdAreaId.getPropertyId(),
                             sampleRate);
+                    Log.i(LogTags.TAG_CAR_HARDWARE, "Registered property: "
+                            + propertyIdAreaId.getPropertyId());
                 } catch (IllegalArgumentException e) {
                     // the property is not implemented
                     Log.e(LogTags.TAG_CAR_HARDWARE,
                             "Failed to register for property: "
-                                    + propertyIdAndAreadId.getPropertyId(), e);
+                                    + propertyIdAreaId.getPropertyId(), e);
                     mPropertyProcessorCallback.onErrorEvent(
-                            CarInternalError.create(propertyIdAndAreadId.getPropertyId(),
-                                    propertyIdAndAreadId.getAreaId(),
+                            CarInternalError.create(propertyIdAreaId.getPropertyId(),
+                                    propertyIdAreaId.getAreaId(),
                                     CarValue.STATUS_UNIMPLEMENTED));
                 } catch (Exception e) {
                     Log.e(LogTags.TAG_CAR_HARDWARE,
                             "Failed to register for property: "
-                                    + propertyIdAndAreadId.getPropertyId(), e);
+                                    + propertyIdAreaId.getPropertyId(), e);
                     mPropertyProcessorCallback.onErrorEvent(
-                            CarInternalError.create(propertyIdAndAreadId.getPropertyId(),
-                                    propertyIdAndAreadId.getAreaId(), CarValue.STATUS_UNAVAILABLE));
+                            CarInternalError.create(propertyIdAreaId.getPropertyId(),
+                                    propertyIdAreaId.getAreaId(), CarValue.STATUS_UNAVAILABLE));
                 }
             }
         });
@@ -286,9 +288,13 @@
         @Override
         public void onChangeEvent(CarPropertyValue carPropertyValue) {
             synchronized (mLock) {
+                int propertyId = carPropertyValue.getPropertyId();
+                Log.i(LogTags.TAG_CAR_HARDWARE, "A change occurred in the value of property: "
+                        + carPropertyValue.toString());
                 // check timestamp
                 if (mListenerAndResponseCache.updateResponseIfNeeded(carPropertyValue)) {
-                    int propertyId = carPropertyValue.getPropertyId();
+                    Log.i(LogTags.TAG_CAR_HARDWARE, "Update needed for property: "
+                            + propertyId);
                     if (PropertyUtils.isOnChangeProperty(propertyId)) {
                         mScheduledExecutorService.execute(() -> dispatchResponsesWithoutDelay(
                                 PropertyIdAreaId.builder().setPropertyId(propertyId)
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyRequestProcessor.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyRequestProcessor.java
index aa1cbd7..b05e764 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyRequestProcessor.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyRequestProcessor.java
@@ -282,11 +282,20 @@
      * @throws IllegalArgumentException if a property is not implemented in the car
      */
     public void registerProperty(int propertyId, float sampleRate) {
+        Log.i(LogTags.TAG_CAR_HARDWARE,
+                "Attempting registration for the property: " + propertyId + " at sample rate: "
+                        + sampleRate);
         if (getPropertyConfig(propertyId) == null) {
             throw new IllegalArgumentException("Property is not implemented in the car: "
                     + propertyId);
         }
-        mCarPropertyManager.registerCallback(mPropertyEventCallback, propertyId, sampleRate);
+        boolean registerCallback =
+                mCarPropertyManager.registerCallback(mPropertyEventCallback,
+                        propertyId,
+                        sampleRate);
+        Log.i(LogTags.TAG_CAR_HARDWARE,
+                "Registration completed in CarPropertyManager with success status: "
+                        + registerCallback);
     }
 
     /**
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
index 16e0cd1..891a4d9 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/common/PropertyUtils.java
@@ -483,10 +483,10 @@
                 carZoneSetsToFloatValues = new HashMap<>();
         for (Map.Entry<Set<CarZone>, ? extends Pair<?, ?>> entry : requireNonNull(minMaxRange
                 .entrySet())) {
-            int min = (Integer) entry.getValue().first;
-            int max = (Integer) entry.getValue().second;
+            float min = (Float) entry.getValue().first;
+            float max = (Float) entry.getValue().second;
             carZoneSetsToFloatValues.put(entry.getKey(),
-                    new Pair<>((float) min, (float) max));
+                    new Pair<>(min, max));
         }
         return carZoneSetsToFloatValues;
     }
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java
index eb913ae..d5ae1f9 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/climate/AutomotiveCarClimateTest.java
@@ -334,8 +334,8 @@
         carZones.add(Collections.singleton(FRONT_RIGHT_ZONE));
         List<Integer> propertyIds = Collections.singletonList(HVAC_TEMPERATURE_SET);
         Map<Set<CarZone>, Pair<Object, Object>> requestMinMaxValueMap = new HashMap<>();
-        requestMinMaxValueMap.put(Collections.singleton(FRONT_LEFT_ZONE), new Pair<>(16, 32));
-        requestMinMaxValueMap.put(Collections.singleton(FRONT_RIGHT_ZONE), new Pair<>(16, 32));
+        requestMinMaxValueMap.put(Collections.singleton(FRONT_LEFT_ZONE), new Pair<>(16f, 32f));
+        requestMinMaxValueMap.put(Collections.singleton(FRONT_RIGHT_ZONE), new Pair<>(16f, 32f));
         mCarPropertyProfiles.add(CarPropertyProfile.builder().setPropertyId(HVAC_TEMPERATURE_SET)
                 .setCelsiusRange(null)
                 .setFahrenheitRange(null)
diff --git a/car/app/app-samples/github_build.gradle b/car/app/app-samples/github_build.gradle
index 646c902..6a2b974 100644
--- a/car/app/app-samples/github_build.gradle
+++ b/car/app/app-samples/github_build.gradle
@@ -22,7 +22,7 @@
         mavenCentral()
     }
     dependencies {
-        classpath 'com.android.tools.build:gradle:4.0.1'
+        classpath 'com.android.tools.build:gradle:7.0.4'
 
         // NOTE: Do not place your application dependencies here; they belong
         // in the individual module build.gradle files
diff --git a/car/app/app-samples/navigation/automotive/github_build.gradle b/car/app/app-samples/navigation/automotive/github_build.gradle
index acecc0a..070f8b6 100644
--- a/car/app/app-samples/navigation/automotive/github_build.gradle
+++ b/car/app/app-samples/navigation/automotive/github_build.gradle
@@ -17,16 +17,16 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         applicationId "androidx.car.app.sample.navigation"
         minSdkVersion 29
-        targetSdkVersion 32
+        targetSdkVersion 33
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the navigation-mobile version
-        versionCode 111
-        versionName "111"
+        versionCode 113
+        versionName "113"
     }
 
     buildTypes {
diff --git a/car/app/app-samples/navigation/common/github_build.gradle b/car/app/app-samples/navigation/common/github_build.gradle
index 878aa78..71f7bd9 100644
--- a/car/app/app-samples/navigation/common/github_build.gradle
+++ b/car/app/app-samples/navigation/common/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 32
+        targetSdkVersion 33
         versionCode 1
         versionName "1.0"
     }
diff --git a/car/app/app-samples/navigation/mobile/github_build.gradle b/car/app/app-samples/navigation/mobile/github_build.gradle
index 27e8cdc..fa8e3d4 100644
--- a/car/app/app-samples/navigation/mobile/github_build.gradle
+++ b/car/app/app-samples/navigation/mobile/github_build.gradle
@@ -17,16 +17,16 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         applicationId "androidx.car.app.sample.navigation"
         minSdkVersion 23
-        targetSdkVersion 32
+        targetSdkVersion 33
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the navigation-automotive version
-        versionCode 112
-        versionName "112"
+        versionCode 114
+        versionName "114"
     }
 
     buildTypes {
diff --git a/car/app/app-samples/showcase/automotive/github_build.gradle b/car/app/app-samples/showcase/automotive/github_build.gradle
index a97639c..72130ff 100644
--- a/car/app/app-samples/showcase/automotive/github_build.gradle
+++ b/car/app/app-samples/showcase/automotive/github_build.gradle
@@ -17,16 +17,16 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         applicationId "androidx.car.app.sample.showcase"
         minSdkVersion 29
-        targetSdkVersion 32
+        targetSdkVersion 33
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the showcase-mobile version
-        versionCode 111
-        versionName "111"
+        versionCode 113
+        versionName "113"
     }
 
     buildTypes {
diff --git a/car/app/app-samples/showcase/common/github_build.gradle b/car/app/app-samples/showcase/common/github_build.gradle
index acb2903..d95602d 100644
--- a/car/app/app-samples/showcase/common/github_build.gradle
+++ b/car/app/app-samples/showcase/common/github_build.gradle
@@ -17,11 +17,11 @@
 apply plugin: 'com.android.library'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         minSdkVersion 23
-        targetSdkVersion 32
+        targetSdkVersion 33
         versionCode 1
         versionName "1.0"
     }
diff --git a/car/app/app-samples/showcase/mobile/github_build.gradle b/car/app/app-samples/showcase/mobile/github_build.gradle
index 6385aea..5ad3f0b 100644
--- a/car/app/app-samples/showcase/mobile/github_build.gradle
+++ b/car/app/app-samples/showcase/mobile/github_build.gradle
@@ -17,16 +17,16 @@
 apply plugin: 'com.android.application'
 
 android {
-    compileSdk 32
+    compileSdk 33
 
     defaultConfig {
         applicationId "androidx.car.app.sample.showcase"
         minSdkVersion 23
-        targetSdkVersion 32
+        targetSdkVersion 33
         // Increment this to generate signed builds for uploading to Playstore
         // Make sure this is different from the showcase-automotive version
-        versionCode 112
-        versionName "112"
+        versionCode 114
+        versionName "114"
     }
 
     buildTypes {
diff --git a/collection/collection-benchmark-kmp/build.gradle b/collection/collection-benchmark-kmp/build.gradle
index fdcc30b..22aa497 100644
--- a/collection/collection-benchmark-kmp/build.gradle
+++ b/collection/collection-benchmark-kmp/build.gradle
@@ -25,7 +25,6 @@
 }
 
 androidXMultiplatform {
-    jvm() // No-op target needed for CI to succeed, see b/243154573
     ios()
     mac()
     linux()
diff --git a/compose/animation/animation-core/api/current.ignore b/compose/animation/animation-core/api/current.ignore
deleted file mode 100644
index af0a929..0000000
--- a/compose/animation/animation-core/api/current.ignore
+++ /dev/null
@@ -1,29 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateDpAsState(float, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateDpAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.Dp>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateFloatAsState(float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, float, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState has changed return type from androidx.compose.runtime.State<java.lang.Float> to androidx.compose.runtime.State<? extends java.lang.Float>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntAsState(int, androidx.compose.animation.core.AnimationSpec<java.lang.Integer>, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntAsState has changed return type from androidx.compose.runtime.State<java.lang.Integer> to androidx.compose.runtime.State<? extends java.lang.Integer>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntOffsetAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntOffsetAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.IntOffset>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntSizeAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntSizeAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.IntSize>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateOffsetAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateOffsetAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Offset>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateRectAsState(androidx.compose.ui.geometry.Rect, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateRectAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Rect>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateSizeAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateSizeAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Size>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateValueAsState(T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.AnimationSpec<T>, T, kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateValueAsState has changed return type from androidx.compose.runtime.State<T> to androidx.compose.runtime.State<? extends T>
-
-
-RemovedDeprecatedMethod: androidx.compose.animation.core.InfiniteRepeatableSpec#InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.InfiniteRepeatableSpec.InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.RepeatableSpec#RepeatableSpec(int, androidx.compose.animation.core.DurationBasedAnimationSpec<T>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.RepeatableSpec.RepeatableSpec(int,androidx.compose.animation.core.DurationBasedAnimationSpec<T>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.VectorizedInfiniteRepeatableSpec#VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.VectorizedInfiniteRepeatableSpec.VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.VectorizedRepeatableSpec#VectorizedRepeatableSpec(int, androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.VectorizedRepeatableSpec.VectorizedRepeatableSpec(int,androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>,androidx.compose.animation.core.RepeatMode)
diff --git a/compose/animation/animation-core/api/restricted_current.ignore b/compose/animation/animation-core/api/restricted_current.ignore
deleted file mode 100644
index af0a929..0000000
--- a/compose/animation/animation-core/api/restricted_current.ignore
+++ /dev/null
@@ -1,29 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateDpAsState(float, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.Dp>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.Dp,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateDpAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.Dp> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.Dp>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateFloatAsState(float, androidx.compose.animation.core.AnimationSpec<java.lang.Float>, float, kotlin.jvm.functions.Function1<? super java.lang.Float,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateFloatAsState has changed return type from androidx.compose.runtime.State<java.lang.Float> to androidx.compose.runtime.State<? extends java.lang.Float>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntAsState(int, androidx.compose.animation.core.AnimationSpec<java.lang.Integer>, kotlin.jvm.functions.Function1<? super java.lang.Integer,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntAsState has changed return type from androidx.compose.runtime.State<java.lang.Integer> to androidx.compose.runtime.State<? extends java.lang.Integer>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntOffsetAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntOffset>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntOffset,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntOffsetAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.IntOffset> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.IntOffset>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateIntSizeAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.unit.IntSize>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.unit.IntSize,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateIntSizeAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.unit.IntSize> to androidx.compose.runtime.State<? extends androidx.compose.ui.unit.IntSize>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateOffsetAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Offset>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Offset,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateOffsetAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Offset> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Offset>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateRectAsState(androidx.compose.ui.geometry.Rect, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Rect>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Rect,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateRectAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Rect> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Rect>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateSizeAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.geometry.Size>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.geometry.Size,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateSizeAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.geometry.Size> to androidx.compose.runtime.State<? extends androidx.compose.ui.geometry.Size>
-ChangedType: androidx.compose.animation.core.AnimateAsStateKt#animateValueAsState(T, androidx.compose.animation.core.TwoWayConverter<T,V>, androidx.compose.animation.core.AnimationSpec<T>, T, kotlin.jvm.functions.Function1<? super T,? extends kotlin.Unit>):
-    Method androidx.compose.animation.core.AnimateAsStateKt.animateValueAsState has changed return type from androidx.compose.runtime.State<T> to androidx.compose.runtime.State<? extends T>
-
-
-RemovedDeprecatedMethod: androidx.compose.animation.core.InfiniteRepeatableSpec#InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.InfiniteRepeatableSpec.InfiniteRepeatableSpec(androidx.compose.animation.core.DurationBasedAnimationSpec<T>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.RepeatableSpec#RepeatableSpec(int, androidx.compose.animation.core.DurationBasedAnimationSpec<T>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.RepeatableSpec.RepeatableSpec(int,androidx.compose.animation.core.DurationBasedAnimationSpec<T>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.VectorizedInfiniteRepeatableSpec#VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.VectorizedInfiniteRepeatableSpec.VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>,androidx.compose.animation.core.RepeatMode)
-RemovedDeprecatedMethod: androidx.compose.animation.core.VectorizedRepeatableSpec#VectorizedRepeatableSpec(int, androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>, androidx.compose.animation.core.RepeatMode):
-    Removed deprecated method androidx.compose.animation.core.VectorizedRepeatableSpec.VectorizedRepeatableSpec(int,androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V>,androidx.compose.animation.core.RepeatMode)
diff --git a/compose/animation/animation/api/current.ignore b/compose/animation/animation/api/current.ignore
deleted file mode 100644
index 4593b53..0000000
--- a/compose/animation/animation/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.compose.animation.SingleValueAnimationKt#animateColorAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,? extends kotlin.Unit>):
-    Method androidx.compose.animation.SingleValueAnimationKt.animateColorAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> to androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color>
diff --git a/compose/animation/animation/api/restricted_current.ignore b/compose/animation/animation/api/restricted_current.ignore
deleted file mode 100644
index 4593b53..0000000
--- a/compose/animation/animation/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.compose.animation.SingleValueAnimationKt#animateColorAsState(long, androidx.compose.animation.core.AnimationSpec<androidx.compose.ui.graphics.Color>, kotlin.jvm.functions.Function1<? super androidx.compose.ui.graphics.Color,? extends kotlin.Unit>):
-    Method androidx.compose.animation.SingleValueAnimationKt.animateColorAsState has changed return type from androidx.compose.runtime.State<androidx.compose.ui.graphics.Color> to androidx.compose.runtime.State<? extends androidx.compose.ui.graphics.Color>
diff --git a/compose/benchmark-utils/build.gradle b/compose/benchmark-utils/build.gradle
index df64ee9..1ee2afbf5 100644
--- a/compose/benchmark-utils/build.gradle
+++ b/compose/benchmark-utils/build.gradle
@@ -23,17 +23,17 @@
 
 dependencies {
     api("androidx.activity:activity:1.2.0")
-    api(project(":compose:test-utils"))
-    api(project(":benchmark:benchmark-junit4"))
+    api(projectOrArtifact(":compose:test-utils"))
+    api(projectOrArtifact(":benchmark:benchmark-junit4"))
 
     implementation(libs.kotlinStdlibCommon)
-    implementation(project(":compose:runtime:runtime"))
-    implementation(project(":compose:ui:ui"))
+    implementation(projectOrArtifact(":compose:runtime:runtime"))
+    implementation(projectOrArtifact(":compose:ui:ui"))
     implementation(libs.testRules)
 
     // This has stub APIs for access to legacy Android APIs, so we don't want
     // any dependency on this module.
-    compileOnly(project(":compose:ui:ui-android-stubs"))
+    compileOnly(projectOrArtifact(":compose:ui:ui-android-stubs"))
 }
 
 android {
diff --git a/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/ResizeComposeViewBenchmark.kt b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/ResizeComposeViewBenchmark.kt
new file mode 100644
index 0000000..7f1d93a
--- /dev/null
+++ b/compose/foundation/foundation-layout/benchmark/src/androidTest/java/androidx/compose/foundation/layout/benchmark/ResizeComposeViewBenchmark.kt
@@ -0,0 +1,148 @@
+/*
+ * Copyright 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.layout.benchmark
+
+import android.widget.LinearLayout
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.fillMaxHeight
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.layout.width
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.benchmarkFirstLayout
+import androidx.compose.testutils.benchmark.benchmarkFirstMeasure
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkLayout
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkMeasure
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Constraints
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.constrainWidth
+import androidx.compose.ui.viewinterop.AndroidView
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+/**
+ * Benchmark that runs [RectsInColumnTestCase].
+ */
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ResizeComposeViewBenchmark {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val rectsInColumnCaseFactory = { ComposeViewTestCase() }
+
+    @Test
+    fun first_measure() {
+        benchmarkRule.benchmarkFirstMeasure(rectsInColumnCaseFactory)
+    }
+
+    @Test
+    fun first_layout() {
+        benchmarkRule.benchmarkFirstLayout(rectsInColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleSize_measure() {
+        benchmarkRule.toggleStateBenchmarkMeasure(rectsInColumnCaseFactory)
+    }
+
+    @Test
+    fun toggleSize_layout() {
+        benchmarkRule.toggleStateBenchmarkLayout(rectsInColumnCaseFactory)
+    }
+}
+
+class ComposeViewTestCase : LayeredComposeTestCase(), ToggleableTestCase {
+
+    private var childSize by mutableStateOf(IntSize(300, 400))
+
+    @Composable
+    override fun MeasuredContent() {
+        with(LocalDensity.current) {
+            Box(Modifier.size(childSize.width.toDp(), childSize.height.toDp())) {
+                AndroidView(
+                    modifier = Modifier.fillMaxSize(),
+                    factory = { context ->
+                        val column = LinearLayout(context).apply {
+                            orientation = LinearLayout.VERTICAL
+                        }
+                        repeat(10) {
+                            val row = ComposeView(context).apply {
+                                setContent {
+                                    Layout(content = {
+                                        with(LocalDensity.current) {
+                                            repeat(20) {
+                                                Row(Modifier.size(10.toDp())) {
+                                                    repeat(10) {
+                                                        Box(
+                                                            Modifier
+                                                                .width(1.toDp())
+                                                                .fillMaxHeight()
+                                                        )
+                                                    }
+                                                }
+                                            }
+                                        }
+                                    }) { measurables, constraints ->
+                                        val width = constraints.constrainWidth(400)
+                                        val height = constraints.constrainWidth(400)
+                                        layout(width, height) {
+                                            measurables.forEachIndexed { i, m ->
+                                                val p = m.measure(Constraints.fixed(10, 10))
+                                                p.place(i * 10, 0)
+                                            }
+                                        }
+                                    }
+                                }
+                            }
+                            val layoutParams = LinearLayout.LayoutParams(
+                                LinearLayout.LayoutParams.MATCH_PARENT,
+                                0,
+                                1f
+                            )
+                            column.addView(row, layoutParams)
+                        }
+                        column
+                    }
+                )
+            }
+        }
+    }
+
+    override fun toggleState() {
+        if (childSize.width == 300) {
+            childSize = IntSize(400, 300)
+        } else {
+            childSize = IntSize(300, 400)
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/api/current.ignore b/compose/foundation/foundation/api/current.ignore
deleted file mode 100644
index 1799285..0000000
--- a/compose/foundation/foundation/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.foundation.lazy.LazyListScrollingKt:
-    Removed class androidx.compose.foundation.lazy.LazyListScrollingKt
-RemovedClass: androidx.compose.foundation.lazy.grid.LazyGridScrollingKt:
-    Removed class androidx.compose.foundation.lazy.grid.LazyGridScrollingKt
diff --git a/compose/foundation/foundation/api/public_plus_experimental_1.3.0-beta03.txt b/compose/foundation/foundation/api/public_plus_experimental_1.3.0-beta03.txt
index 66f106d..36c0cee 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_1.3.0-beta03.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_1.3.0-beta03.txt
@@ -866,8 +866,8 @@
   }
 
   public final class LazyStaggeredGridDslKt {
-    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
-    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/public_plus_experimental_current.txt b/compose/foundation/foundation/api/public_plus_experimental_current.txt
index 66f106d..36c0cee 100644
--- a/compose/foundation/foundation/api/public_plus_experimental_current.txt
+++ b/compose/foundation/foundation/api/public_plus_experimental_current.txt
@@ -866,8 +866,8 @@
   }
 
   public final class LazyStaggeredGridDslKt {
-    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
-    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyHorizontalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells rows, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
+    method @androidx.compose.foundation.ExperimentalFoundationApi @androidx.compose.runtime.Composable public static void LazyVerticalStaggeredGrid(androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells columns, optional androidx.compose.ui.Modifier modifier, optional androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState state, optional androidx.compose.foundation.layout.PaddingValues contentPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical verticalArrangement, optional androidx.compose.foundation.layout.Arrangement.Horizontal horizontalArrangement, optional androidx.compose.foundation.gestures.FlingBehavior flingBehavior, optional boolean userScrollEnabled, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope,kotlin.Unit> content);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void items(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, T![] items, optional kotlin.jvm.functions.Function1<? super T,?>? key, optional kotlin.jvm.functions.Function1<? super T,?> contentType, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super T,kotlin.Unit> itemContent);
     method @androidx.compose.foundation.ExperimentalFoundationApi public static <T> void itemsIndexed(androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridScope, java.util.List<? extends T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?> contentType, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
diff --git a/compose/foundation/foundation/api/restricted_current.ignore b/compose/foundation/foundation/api/restricted_current.ignore
deleted file mode 100644
index 1799285..0000000
--- a/compose/foundation/foundation/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.foundation.lazy.LazyListScrollingKt:
-    Removed class androidx.compose.foundation.lazy.LazyListScrollingKt
-RemovedClass: androidx.compose.foundation.lazy.grid.LazyGridScrollingKt:
-    Removed class androidx.compose.foundation.lazy.grid.LazyGridScrollingKt
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyBenchmarkCommon.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyBenchmarkCommon.kt
new file mode 100644
index 0000000..617ba28
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyBenchmarkCommon.kt
@@ -0,0 +1,153 @@
+/*
+ * 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.compose.foundation.benchmark.lazy
+
+import android.view.MotionEvent
+import android.view.View
+import androidx.compose.foundation.gestures.FlingBehavior
+import androidx.compose.foundation.gestures.ScrollScope
+import androidx.compose.testutils.ComposeTestCase
+import androidx.compose.testutils.assertNoPendingChanges
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.doFramesUntilNoChangesPending
+import androidx.compose.ui.geometry.Offset
+
+internal object NoFlingBehavior : FlingBehavior {
+    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
+        return 0f
+    }
+}
+
+data class LazyItem(val index: Int)
+
+/**
+ * Helper for dispatching simple [MotionEvent]s to a [view] for use in scrolling benchmarks.
+ */
+class MotionEventHelper(private val view: View) {
+    private var time = 0L
+    private var lastCoord: Offset? = null
+
+    fun sendEvent(
+        action: Int,
+        delta: Offset
+    ) {
+        time += 10L
+
+        val coord = delta + (lastCoord ?: Offset.Zero)
+
+        lastCoord = if (action == MotionEvent.ACTION_UP) {
+            null
+        } else {
+            coord
+        }
+
+        val locationOnScreen = IntArray(2) { 0 }
+        view.getLocationOnScreen(locationOnScreen)
+
+        val motionEvent = MotionEvent.obtain(
+            0,
+            time,
+            action,
+            1,
+            arrayOf(MotionEvent.PointerProperties()),
+            arrayOf(
+                MotionEvent.PointerCoords().apply {
+                    x = locationOnScreen[0] + coord.x.coerceAtLeast(1f)
+                    y = locationOnScreen[1] + coord.y.coerceAtLeast(1f)
+                }
+            ),
+            0,
+            0,
+            0f,
+            0f,
+            0,
+            0,
+            0,
+            0
+        ).apply {
+            offsetLocation(-locationOnScreen[0].toFloat(), -locationOnScreen[1].toFloat())
+        }
+
+        view.dispatchTouchEvent(motionEvent)
+    }
+}
+
+// TODO(b/169852102 use existing public constructs instead)
+internal fun ComposeBenchmarkRule.toggleStateBenchmark(
+    caseFactory: () -> LazyBenchmarkTestCase
+) {
+    runBenchmarkFor(caseFactory) {
+        doFramesUntilNoChangesPending()
+
+        measureRepeated {
+            runWithTimingDisabled {
+                assertNoPendingChanges()
+                getTestCase().beforeToggle()
+                if (hasPendingChanges()) {
+                    doFrame()
+                }
+                assertNoPendingChanges()
+            }
+            getTestCase().toggle()
+            if (hasPendingChanges()) {
+                doFrame()
+            }
+            runWithTimingDisabled {
+                assertNoPendingChanges()
+                getTestCase().afterToggle()
+                assertNoPendingChanges()
+            }
+        }
+    }
+}
+
+// TODO(b/169852102 use existing public constructs instead)
+internal fun ComposeBenchmarkRule.toggleStateBenchmarkDraw(
+    caseFactory: () -> LazyBenchmarkTestCase
+) {
+    runBenchmarkFor(caseFactory) {
+        doFrame()
+
+        measureRepeated {
+            runWithTimingDisabled {
+                // reset the state and draw
+                getTestCase().beforeToggle()
+                measure()
+                layout()
+                drawPrepare()
+                draw()
+                drawFinish()
+                // toggle and prepare measuring draw
+                getTestCase().toggle()
+                measure()
+                layout()
+                drawPrepare()
+            }
+            draw()
+            runWithTimingDisabled {
+                getTestCase().afterToggle()
+                drawFinish()
+            }
+        }
+    }
+}
+
+interface LazyBenchmarkTestCase : ComposeTestCase {
+    fun beforeToggle()
+    fun toggle()
+    fun afterToggle()
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt
new file mode 100644
index 0000000..d21da5c
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyGridScrollingBenchmark.kt
@@ -0,0 +1,272 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.benchmark.lazy
+
+import android.os.Build
+import android.view.MotionEvent
+import android.view.View
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.lazy.grid.LazyHorizontalGrid
+import androidx.compose.foundation.lazy.grid.LazyGridState
+import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
+import androidx.compose.foundation.lazy.grid.GridCells
+import androidx.compose.foundation.lazy.grid.items
+import androidx.compose.foundation.lazy.grid.rememberLazyGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class LazyGridScrollingBenchmark(
+    private val testCase: LazyGridScrollingTestCase
+) {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun scrollProgrammatically_noNewItems() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun scrollProgrammatically_newItemComposed() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun scrollViaPointerInput_noNewItems() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                usePointerInput = true
+            )
+        }
+    }
+
+    @Test
+    fun scrollViaPointerInput_newItemComposed() {
+        benchmarkRule.toggleStateBenchmark {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                usePointerInput = true
+            )
+        }
+    }
+
+    @Test
+    fun drawAfterScroll_noNewItems() {
+        // this test makes sense only when run on the Android version which supports RenderNodes
+        // as this tests how efficiently we move RenderNodes.
+        Assume.assumeTrue(supportsRenderNode || supportsMRenderNode)
+        benchmarkRule.toggleStateBenchmarkDraw {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun drawAfterScroll_newItemComposed() {
+        // this test makes sense only when run on the Android version which supports RenderNodes
+        // as this tests how efficiently we move RenderNodes.
+        Assume.assumeTrue(supportsRenderNode || supportsMRenderNode)
+        benchmarkRule.toggleStateBenchmarkDraw {
+            GridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters(): Array<LazyGridScrollingTestCase> =
+            arrayOf(
+                Vertical,
+                Horizontal
+            )
+
+        // Copied from AndroidComposeTestCaseRunner
+        private val supportsRenderNode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+        private val supportsMRenderNode = Build.VERSION.SDK_INT < Build.VERSION_CODES.P &&
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+    }
+}
+
+class LazyGridScrollingTestCase(
+    private val name: String,
+    val isVertical: Boolean,
+    val content: @Composable GridRemeasureTestCase.(LazyGridState) -> Unit
+) {
+    override fun toString(): String {
+        return name
+    }
+}
+
+private val Vertical = LazyGridScrollingTestCase(
+    "Vertical",
+    isVertical = true
+) { state ->
+    LazyVerticalGrid(
+        columns = GridCells.Fixed(2),
+        state = state,
+        modifier = Modifier.requiredHeight(400.dp).fillMaxWidth(),
+        flingBehavior = NoFlingBehavior
+    ) {
+        items(2) {
+            FirstLargeItem()
+        }
+        items(items) {
+            RegularItem()
+        }
+    }
+}
+
+private val Horizontal = LazyGridScrollingTestCase(
+    "Horizontal",
+    isVertical = false
+) { state ->
+    LazyHorizontalGrid(
+        rows = GridCells.Fixed(2),
+        state = state,
+        modifier = Modifier.requiredHeight(400.dp).fillMaxWidth(),
+        flingBehavior = NoFlingBehavior
+    ) {
+        items(2) {
+            FirstLargeItem()
+        }
+        items(items) {
+            RegularItem()
+        }
+    }
+}
+
+class GridRemeasureTestCase(
+    val addNewItemOnToggle: Boolean,
+    val content: @Composable GridRemeasureTestCase.(LazyGridState) -> Unit,
+    val isVertical: Boolean,
+    val usePointerInput: Boolean = false
+) : LazyBenchmarkTestCase {
+
+    val items = List(300) { LazyItem(it) }
+
+    private lateinit var state: LazyGridState
+    private lateinit var view: View
+    private lateinit var motionEventHelper: MotionEventHelper
+    private var touchSlop: Float = 0f
+    private var scrollBy: Int = 0
+
+    @Composable
+    fun FirstLargeItem() {
+        Box(Modifier.requiredSize(30.dp))
+    }
+
+    @Composable
+    override fun Content() {
+        scrollBy = if (addNewItemOnToggle) {
+            with(LocalDensity.current) { 15.dp.roundToPx() }
+        } else {
+            5
+        }
+        view = LocalView.current
+        if (!::motionEventHelper.isInitialized) motionEventHelper = MotionEventHelper(view)
+        touchSlop = LocalViewConfiguration.current.touchSlop
+        state = rememberLazyGridState()
+        content(state)
+    }
+
+    @Composable
+    fun RegularItem() {
+        Box(Modifier.requiredSize(20.dp).background(Color.Red, RoundedCornerShape(8.dp)))
+    }
+
+    override fun beforeToggle() {
+        runBlocking {
+            state.scrollToItem(0, 0)
+        }
+        if (usePointerInput) {
+            val size = if (isVertical) view.measuredHeight else view.measuredWidth
+            motionEventHelper.sendEvent(MotionEvent.ACTION_DOWN, (size / 2f).toSingleAxisOffset())
+            motionEventHelper.sendEvent(MotionEvent.ACTION_MOVE, touchSlop.toSingleAxisOffset())
+        }
+        assertEquals(0, state.firstVisibleItemIndex)
+        assertEquals(0, state.firstVisibleItemScrollOffset)
+    }
+
+    override fun toggle() {
+        if (usePointerInput) {
+            motionEventHelper
+                .sendEvent(MotionEvent.ACTION_MOVE, -scrollBy.toFloat().toSingleAxisOffset())
+        } else {
+            runBlocking {
+                state.scrollBy(scrollBy.toFloat())
+            }
+        }
+    }
+
+    override fun afterToggle() {
+        assertEquals(0, state.firstVisibleItemIndex)
+        assertEquals(scrollBy, state.firstVisibleItemScrollOffset)
+        if (usePointerInput) {
+            motionEventHelper.sendEvent(MotionEvent.ACTION_UP, Offset.Zero)
+        }
+    }
+
+    private fun Float.toSingleAxisOffset(): Offset =
+        Offset(x = if (isVertical) 0f else this, y = if (isVertical) this else 0f)
+}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
index d3f66c9..329cccc 100644
--- a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyListScrollingBenchmark.kt
@@ -20,8 +20,6 @@
 import android.view.MotionEvent
 import android.view.View
 import androidx.compose.foundation.background
-import androidx.compose.foundation.gestures.FlingBehavior
-import androidx.compose.foundation.gestures.ScrollScope
 import androidx.compose.foundation.gestures.scrollBy
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxHeight
@@ -36,10 +34,7 @@
 import androidx.compose.foundation.lazy.rememberLazyListState
 import androidx.compose.foundation.shape.RoundedCornerShape
 import androidx.compose.runtime.Composable
-import androidx.compose.testutils.ComposeTestCase
-import androidx.compose.testutils.assertNoPendingChanges
 import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
-import androidx.compose.testutils.doFramesUntilNoChangesPending
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.graphics.Color
@@ -200,80 +195,14 @@
     }
 }
 
-private object NoFlingBehavior : FlingBehavior {
-    override suspend fun ScrollScope.performFling(initialVelocity: Float): Float {
-        return 0f
-    }
-}
-
-// TODO(b/169852102 use existing public constructs instead)
-private fun ComposeBenchmarkRule.toggleStateBenchmark(
-    caseFactory: () -> ListRemeasureTestCase
-) {
-    runBenchmarkFor(caseFactory) {
-        doFramesUntilNoChangesPending()
-
-        measureRepeated {
-            runWithTimingDisabled {
-                assertNoPendingChanges()
-                getTestCase().beforeToggle()
-                if (hasPendingChanges()) {
-                    doFrame()
-                }
-                assertNoPendingChanges()
-            }
-            getTestCase().toggle()
-            if (hasPendingChanges()) {
-                doFrame()
-            }
-            runWithTimingDisabled {
-                assertNoPendingChanges()
-                getTestCase().afterToggle()
-                assertNoPendingChanges()
-            }
-        }
-    }
-}
-
-// TODO(b/169852102 use existing public constructs instead)
-private fun ComposeBenchmarkRule.toggleStateBenchmarkDraw(
-    caseFactory: () -> ListRemeasureTestCase
-) {
-    runBenchmarkFor(caseFactory) {
-        doFrame()
-
-        measureRepeated {
-            runWithTimingDisabled {
-                // reset the state and draw
-                getTestCase().beforeToggle()
-                measure()
-                layout()
-                drawPrepare()
-                draw()
-                drawFinish()
-                // toggle and prepare measuring draw
-                getTestCase().toggle()
-                measure()
-                layout()
-                drawPrepare()
-            }
-            draw()
-            runWithTimingDisabled {
-                getTestCase().afterToggle()
-                drawFinish()
-            }
-        }
-    }
-}
-
 class ListRemeasureTestCase(
     val addNewItemOnToggle: Boolean,
     val content: @Composable ListRemeasureTestCase.(LazyListState) -> Unit,
     val isVertical: Boolean,
     val usePointerInput: Boolean = false
-) : ComposeTestCase {
+) : LazyBenchmarkTestCase {
 
-    val items = List(100) { ListItem(it) }
+    val items = List(100) { LazyItem(it) }
 
     private lateinit var listState: LazyListState
     private lateinit var view: View
@@ -305,7 +234,7 @@
         Box(Modifier.requiredSize(20.dp).background(Color.Red, RoundedCornerShape(8.dp)))
     }
 
-    fun beforeToggle() {
+    override fun beforeToggle() {
         runBlocking {
             listState.scrollToItem(0, 0)
         }
@@ -318,7 +247,7 @@
         assertEquals(0, listState.firstVisibleItemScrollOffset)
     }
 
-    fun toggle() {
+    override fun toggle() {
         if (usePointerInput) {
             motionEventHelper
                 .sendEvent(MotionEvent.ACTION_MOVE, -scrollBy.toFloat().toSingleAxisOffset())
@@ -329,7 +258,7 @@
         }
     }
 
-    fun afterToggle() {
+    override fun afterToggle() {
         assertEquals(0, listState.firstVisibleItemIndex)
         assertEquals(scrollBy, listState.firstVisibleItemScrollOffset)
         if (usePointerInput) {
@@ -340,57 +269,3 @@
     private fun Float.toSingleAxisOffset(): Offset =
         Offset(x = if (isVertical) 0f else this, y = if (isVertical) this else 0f)
 }
-
-data class ListItem(val index: Int)
-
-/**
- * Helper for dispatching simple [MotionEvent]s to a [view] for use in scrolling benchmarks.
- */
-class MotionEventHelper(private val view: View) {
-    private var time = 0L
-    private var lastCoord: Offset? = null
-
-    fun sendEvent(
-        action: Int,
-        delta: Offset
-    ) {
-        time += 10L
-
-        val coord = delta + (lastCoord ?: Offset.Zero)
-
-        lastCoord = if (action == MotionEvent.ACTION_UP) {
-            null
-        } else {
-            coord
-        }
-
-        val locationOnScreen = IntArray(2) { 0 }
-        view.getLocationOnScreen(locationOnScreen)
-
-        val motionEvent = MotionEvent.obtain(
-            0,
-            time,
-            action,
-            1,
-            arrayOf(MotionEvent.PointerProperties()),
-            arrayOf(
-                MotionEvent.PointerCoords().apply {
-                    x = locationOnScreen[0] + coord.x.coerceAtLeast(1f)
-                    y = locationOnScreen[1] + coord.y.coerceAtLeast(1f)
-                }
-            ),
-            0,
-            0,
-            0f,
-            0f,
-            0,
-            0,
-            0,
-            0
-        ).apply {
-            offsetLocation(-locationOnScreen[0].toFloat(), -locationOnScreen[1].toFloat())
-        }
-
-        view.dispatchTouchEvent(motionEvent)
-    }
-}
diff --git a/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyStaggeredGridScrollingBenchmark.kt b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyStaggeredGridScrollingBenchmark.kt
new file mode 100644
index 0000000..41f3813
--- /dev/null
+++ b/compose/foundation/foundation/benchmark/src/androidTest/java/androidx/compose/foundation/benchmark/lazy/LazyStaggeredGridScrollingBenchmark.kt
@@ -0,0 +1,278 @@
+/*
+ * Copyright 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.compose.foundation.benchmark.lazy
+
+import android.os.Build
+import android.view.MotionEvent
+import android.view.View
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.background
+import androidx.compose.foundation.gestures.scrollBy
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.requiredHeight
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.lazy.staggeredgrid.LazyHorizontalStaggeredGrid
+import androidx.compose.foundation.lazy.staggeredgrid.LazyStaggeredGridState
+import androidx.compose.foundation.lazy.staggeredgrid.LazyVerticalStaggeredGrid
+import androidx.compose.foundation.lazy.staggeredgrid.StaggeredGridCells
+import androidx.compose.foundation.lazy.staggeredgrid.items
+import androidx.compose.foundation.lazy.staggeredgrid.rememberLazyStaggeredGridState
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.runtime.Composable
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.LargeTest
+import kotlinx.coroutines.runBlocking
+import org.junit.Assert.assertEquals
+import org.junit.Assume
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalFoundationApi::class)
+@LargeTest
+@RunWith(Parameterized::class)
+class LazyStaggeredGridScrollingBenchmark(
+    private val testCase: LazyStaggeredGridScrollingTestCase
+) {
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    @Test
+    fun scrollProgrammatically_noNewItems() {
+        benchmarkRule.toggleStateBenchmark {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun scrollProgrammatically_newItemComposed() {
+        benchmarkRule.toggleStateBenchmark {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun scrollViaPointerInput_noNewItems() {
+        benchmarkRule.toggleStateBenchmark {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                usePointerInput = true
+            )
+        }
+    }
+
+    @Test
+    fun scrollViaPointerInput_newItemComposed() {
+        benchmarkRule.toggleStateBenchmark {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical,
+                usePointerInput = true
+            )
+        }
+    }
+
+    @Test
+    fun drawAfterScroll_noNewItems() {
+        // this test makes sense only when run on the Android version which supports RenderNodes
+        // as this tests how efficiently we move RenderNodes.
+        Assume.assumeTrue(supportsRenderNode || supportsMRenderNode)
+        benchmarkRule.toggleStateBenchmarkDraw {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = false,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    @Test
+    fun drawAfterScroll_newItemComposed() {
+        // this test makes sense only when run on the Android version which supports RenderNodes
+        // as this tests how efficiently we move RenderNodes.
+        Assume.assumeTrue(supportsRenderNode || supportsMRenderNode)
+        benchmarkRule.toggleStateBenchmarkDraw {
+            StaggeredGridRemeasureTestCase(
+                addNewItemOnToggle = true,
+                content = testCase.content,
+                isVertical = testCase.isVertical
+            )
+        }
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters(): Array<LazyStaggeredGridScrollingTestCase> =
+            arrayOf(
+                Vertical,
+                Horizontal
+            )
+
+        // Copied from AndroidComposeTestCaseRunner
+        private val supportsRenderNode = Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q
+        private val supportsMRenderNode = Build.VERSION.SDK_INT < Build.VERSION_CODES.P &&
+            Build.VERSION.SDK_INT >= Build.VERSION_CODES.M
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+class LazyStaggeredGridScrollingTestCase(
+    private val name: String,
+    val isVertical: Boolean,
+    val content: @Composable StaggeredGridRemeasureTestCase.(LazyStaggeredGridState) -> Unit
+) {
+    override fun toString(): String {
+        return name
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private val Vertical = LazyStaggeredGridScrollingTestCase(
+    "Vertical",
+    isVertical = true
+) { state ->
+    LazyVerticalStaggeredGrid(
+        columns = StaggeredGridCells.Fixed(2),
+        state = state,
+        modifier = Modifier.requiredHeight(400.dp).fillMaxWidth(),
+        flingBehavior = NoFlingBehavior
+    ) {
+        items(2) {
+            FirstLargeItem()
+        }
+        items(items) {
+            RegularItem()
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+private val Horizontal = LazyStaggeredGridScrollingTestCase(
+    "Horizontal",
+    isVertical = false
+) { state ->
+    LazyHorizontalStaggeredGrid(
+        rows = StaggeredGridCells.Fixed(2),
+        state = state,
+        modifier = Modifier.requiredHeight(400.dp).fillMaxWidth(),
+        flingBehavior = NoFlingBehavior
+    ) {
+        items(2) {
+            FirstLargeItem()
+        }
+        items(items) {
+            RegularItem()
+        }
+    }
+}
+
+@OptIn(ExperimentalFoundationApi::class)
+class StaggeredGridRemeasureTestCase(
+    val addNewItemOnToggle: Boolean,
+    val content: @Composable StaggeredGridRemeasureTestCase.(LazyStaggeredGridState) -> Unit,
+    val isVertical: Boolean,
+    val usePointerInput: Boolean = false
+) : LazyBenchmarkTestCase {
+
+    val items = List(300) { LazyItem(it) }
+
+    private lateinit var state: LazyStaggeredGridState
+    private lateinit var view: View
+    private lateinit var motionEventHelper: MotionEventHelper
+    private var touchSlop: Float = 0f
+    private var scrollBy: Int = 0
+
+    @Composable
+    fun FirstLargeItem() {
+        Box(Modifier.requiredSize(30.dp))
+    }
+
+    @Composable
+    override fun Content() {
+        scrollBy = if (addNewItemOnToggle) {
+            with(LocalDensity.current) { 15.dp.roundToPx() }
+        } else {
+            5
+        }
+        view = LocalView.current
+        if (!::motionEventHelper.isInitialized) motionEventHelper = MotionEventHelper(view)
+        touchSlop = LocalViewConfiguration.current.touchSlop
+        state = rememberLazyStaggeredGridState()
+        content(state)
+    }
+
+    @Composable
+    fun RegularItem() {
+        Box(Modifier.requiredSize(20.dp).background(Color.Red, RoundedCornerShape(8.dp)))
+    }
+
+    override fun beforeToggle() {
+        runBlocking {
+            state.scrollToItem(0, 0)
+        }
+        if (usePointerInput) {
+            val size = if (isVertical) view.measuredHeight else view.measuredWidth
+            motionEventHelper.sendEvent(MotionEvent.ACTION_DOWN, (size / 2f).toSingleAxisOffset())
+            motionEventHelper.sendEvent(MotionEvent.ACTION_MOVE, touchSlop.toSingleAxisOffset())
+        }
+        assertEquals(0, state.firstVisibleItemIndex)
+        assertEquals(0, state.firstVisibleItemScrollOffset)
+    }
+
+    override fun toggle() {
+        if (usePointerInput) {
+            motionEventHelper
+                .sendEvent(MotionEvent.ACTION_MOVE, -scrollBy.toFloat().toSingleAxisOffset())
+        } else {
+            runBlocking {
+                state.scrollBy(scrollBy.toFloat())
+            }
+        }
+    }
+
+    override fun afterToggle() {
+        assertEquals(0, state.firstVisibleItemIndex)
+        assertEquals(scrollBy, state.firstVisibleItemScrollOffset)
+        if (usePointerInput) {
+            motionEventHelper.sendEvent(MotionEvent.ACTION_UP, Offset.Zero)
+        }
+    }
+
+    private fun Float.toSingleAxisOffset(): Offset =
+        Offset(x = if (isVertical) 0f else this, y = if (isVertical) this else 0f)
+}
diff --git a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
index fc69e3d..e2a3966 100644
--- a/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
+++ b/compose/foundation/foundation/integration-tests/foundation-demos/src/main/java/androidx/compose/foundation/demos/ListDemos.kt
@@ -977,6 +977,8 @@
             modifier = Modifier.fillMaxSize(),
             state = state,
             contentPadding = PaddingValues(vertical = 500.dp, horizontal = 20.dp),
+            horizontalArrangement = Arrangement.spacedBy(10.dp),
+            verticalArrangement = Arrangement.spacedBy(10.dp),
             content = {
                 items(count) {
                     var expanded by rememberSaveable { mutableStateOf(false) }
@@ -985,7 +987,6 @@
                     Box(
                         modifier = Modifier
                             .height(if (!expanded) heights[index] else heights[index] * 2)
-                            .padding(5.dp)
                             .border(2.dp, color, RoundedCornerShape(5.dp))
                             .clickable {
                                 expanded = !expanded
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
index 5815d61..2a6ad37 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/ScrollableTest.kt
@@ -69,6 +69,7 @@
 import androidx.compose.ui.platform.AbstractComposeView
 import androidx.compose.ui.platform.InspectableValue
 import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.platform.isDebugInspectorInfoEnabled
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.ExperimentalTestApi
@@ -76,6 +77,7 @@
 import androidx.compose.ui.test.junit4.ComposeContentTestRule
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onRoot
 import androidx.compose.ui.test.performMouseInput
 import androidx.compose.ui.test.performTouchInput
 import androidx.compose.ui.test.swipe
@@ -983,7 +985,7 @@
             ): Offset {
                 // we should get in post scroll as much as left in controller callback
                 assertThat(available.x).isEqualTo(expectedLeft)
-                return available
+                return if (source == NestedScrollSource.Fling) Offset.Zero else available
             }
 
             override suspend fun onPostFling(
@@ -991,6 +993,7 @@
                 available: Velocity
             ): Velocity {
                 val expected = velocityFlung - consumed.x
+                assertThat(consumed.x).isLessThan(velocityFlung)
                 assertThat(abs(available.x - expected)).isLessThan(0.1f)
                 return available
             }
@@ -1051,7 +1054,7 @@
             ): Offset {
                 // we should get in post scroll as much as left in controller callback
                 assertThat(available.x).isEqualTo(-expectedLeft)
-                return available
+                return if (source == NestedScrollSource.Fling) Offset.Zero else available
             }
 
             override suspend fun onPostFling(
@@ -1979,6 +1982,173 @@
     }
 
     @Test
+    fun nestedScrollable_shouldImmediateScrollIfChildIsFlinging() {
+        var innerDelta = 0f
+        var middleDelta = 0f
+        var outerDelta = 0f
+        var touchSlop = 0f
+
+        val outerStateController = ScrollableState {
+            outerDelta += it
+            0f
+        }
+
+        val middleController = ScrollableState {
+            middleDelta += it
+            0f
+        }
+
+        val innerController = ScrollableState {
+            innerDelta += it
+            it / 2f
+        }
+
+        rule.setContentAndGetScope {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(
+                modifier = Modifier
+                    .testTag("outerScrollable")
+                    .size(600.dp)
+                    .background(Color.Red)
+                    .scrollable(
+                        outerStateController,
+                        orientation = Orientation.Vertical
+                    ),
+                contentAlignment = Alignment.BottomStart
+            ) {
+                Box(
+                    modifier = Modifier
+                        .testTag("middleScrollable")
+                        .size(300.dp)
+                        .background(Color.Blue)
+                        .scrollable(
+                            middleController,
+                            orientation = Orientation.Vertical
+                        ),
+                    contentAlignment = Alignment.BottomStart
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .testTag("innerScrollable")
+                            .size(50.dp)
+                            .background(Color.Yellow)
+                            .scrollable(
+                                innerController,
+                                orientation = Orientation.Vertical
+                            )
+                    )
+                }
+            }
+        }
+
+        rule.mainClock.autoAdvance = false
+        rule.onNodeWithTag("innerScrollable").performTouchInput {
+            swipeUp()
+        }
+
+        rule.mainClock.advanceTimeByFrame()
+        rule.mainClock.advanceTimeByFrame()
+
+        val previousOuter = outerDelta
+
+        rule.onNodeWithTag("outerScrollable").performTouchInput {
+            down(topCenter)
+            // Move less than touch slop, should start immediately
+            moveBy(Offset(0f, touchSlop / 2))
+        }
+
+        rule.mainClock.autoAdvance = true
+
+        rule.runOnIdle {
+            assertThat(outerDelta).isEqualTo(previousOuter + touchSlop / 2)
+        }
+    }
+
+    // b/179417109 Double checks that in a nested scroll cycle, the parent post scroll
+    // consumption is taken into consideration.
+    @Test
+    fun dispatchScroll_shouldReturnConsumedDeltaInNestedScrollChain() {
+        var consumedInner = 0f
+        var consumedOuter = 0f
+        var touchSlop = 0f
+
+        var preScrollAvailable = Offset.Zero
+        var consumedPostScroll = Offset.Zero
+        var postScrollAvailable = Offset.Zero
+
+        val outerStateController = ScrollableState {
+            consumedOuter += it
+            it
+        }
+
+        val innerController = ScrollableState {
+            consumedInner += it / 2
+            it / 2
+        }
+
+        val connection = object : NestedScrollConnection {
+            override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+                preScrollAvailable += available
+                return Offset.Zero
+            }
+
+            override fun onPostScroll(
+                consumed: Offset,
+                available: Offset,
+                source: NestedScrollSource
+            ): Offset {
+                consumedPostScroll += consumed
+                postScrollAvailable += available
+                return Offset.Zero
+            }
+        }
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            Box(modifier = Modifier.nestedScroll(connection)) {
+                Box(
+                    modifier = Modifier
+                        .testTag("outerScrollable")
+                        .size(300.dp)
+                        .scrollable(
+                            outerStateController,
+                            orientation = Orientation.Horizontal
+                        )
+
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .testTag("innerScrollable")
+                            .size(300.dp)
+                            .scrollable(
+                                innerController,
+                                orientation = Orientation.Horizontal
+                            )
+                    )
+                }
+            }
+        }
+
+        val scrollDelta = 200f
+
+        rule.onRoot().performTouchInput {
+            down(center)
+            moveBy(Offset(scrollDelta, 0f))
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(consumedInner).isGreaterThan(0)
+            assertThat(consumedOuter).isGreaterThan(0)
+            assertThat(touchSlop).isGreaterThan(0)
+            assertThat(postScrollAvailable.x).isEqualTo(0f)
+            assertThat(consumedPostScroll.x).isEqualTo(scrollDelta - touchSlop)
+            assertThat(preScrollAvailable.x).isEqualTo(scrollDelta - touchSlop)
+            assertThat(scrollDelta).isEqualTo(consumedInner + consumedOuter + touchSlop)
+        }
+    }
+
+    @Test
     fun testInspectorValue() {
         val controller = ScrollableState(
             consumeScrollDelta = { it }
@@ -2041,7 +2211,8 @@
                 Column(
                     Modifier
                         .size(10.dp)
-                        .verticalScroll(rememberScrollState())) {
+                        .verticalScroll(rememberScrollState())
+                ) {
                     Box(
                         Modifier
                             .size(10.dp)
@@ -2053,13 +2224,13 @@
                         Modifier
                             .size(10.dp)
                             .onFocusChanged { nextItemIsFocused = it.isFocused }
-                            .focusable()
-                    )
+                            .focusable())
                 }
                 Box(
                     Modifier
                         .size(10.dp)
-                        .focusable())
+                        .focusable()
+                )
             }
         }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
index ba65bfd..ffc7d89 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/layout/LazyLayoutStateRestorationTest.kt
@@ -34,6 +34,7 @@
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -45,6 +46,7 @@
     @get:Rule
     val rule = createComposeRule()
 
+    @Ignore // b/244308934
     @Test
     fun visibleItemsStateRestored() {
         val restorationTester = StateRestorationTester(rule)
@@ -81,6 +83,7 @@
         }
     }
 
+    @Ignore // b/244308934
     @Test
     fun itemsStateRestoredWhenWeScrolledBackToIt() {
         var counter0 = 1
@@ -120,6 +123,7 @@
         }
     }
 
+    @Ignore // b/244308934
     @Test
     fun nestedLazy_itemsStateRestoredWhenWeScrolledBackToIt() {
         var counter0 = 1
@@ -202,6 +206,7 @@
         }
     }
 
+    @Ignore // b/244308934
     @Test
     fun stateRestoredWhenUsedWithCustomKeysAfterReordering() {
         val restorationTester = StateRestorationTester(rule)
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
index 0dc7cb9..21ce522 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/BaseLazyStaggeredGridWithOrientation.kt
@@ -20,11 +20,14 @@
 import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.BaseLazyLayoutTestWithOrientation
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.border
 import androidx.compose.foundation.gestures.Orientation
 import androidx.compose.foundation.gestures.animateScrollBy
+import androidx.compose.foundation.layout.Arrangement
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.unit.Dp
 import androidx.compose.ui.unit.dp
 import kotlinx.coroutines.Dispatchers
@@ -41,12 +44,22 @@
         }
     }
 
+    internal fun LazyStaggeredGridState.scrollTo(index: Int) {
+        runBlocking(Dispatchers.Main + AutoTestFrameClock()) {
+            scrollToItem(index)
+        }
+    }
+
+    internal fun Modifier.debugBorder(color: Color = Color.Black) = border(1.dp, color)
+
     @Composable
     internal fun LazyStaggeredGrid(
         lanes: Int,
         modifier: Modifier = Modifier,
         state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
         contentPadding: PaddingValues = PaddingValues(0.dp),
+        mainAxisArrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(0.dp),
+        crossAxisArrangement: Arrangement.HorizontalOrVertical = Arrangement.spacedBy(0.dp),
         content: LazyStaggeredGridScope.() -> Unit,
     ) {
         if (orientation == Orientation.Vertical) {
@@ -54,6 +67,8 @@
                 columns = StaggeredGridCells.Fixed(lanes),
                 modifier = modifier,
                 contentPadding = contentPadding,
+                verticalArrangement = mainAxisArrangement,
+                horizontalArrangement = crossAxisArrangement,
                 state = state,
                 content = content
             )
@@ -62,6 +77,8 @@
                 rows = StaggeredGridCells.Fixed(lanes),
                 modifier = modifier,
                 contentPadding = contentPadding,
+                verticalArrangement = crossAxisArrangement,
+                horizontalArrangement = mainAxisArrangement,
                 state = state,
                 content = content
             )
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt
new file mode 100644
index 0000000..9f7509c
--- /dev/null
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridArrangementsTest.kt
@@ -0,0 +1,220 @@
+/*
+ * 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.
+ */
+
+/*
+ * 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.
+ */
+
+/*
+ * 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.compose.foundation.lazy.staggeredgrid
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.gestures.Orientation
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.text.BasicText
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import androidx.test.filters.MediumTest
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalFoundationApi::class)
+@MediumTest
+@RunWith(Parameterized::class)
+class LazyStaggeredGridArrangementsTest(
+    orientation: Orientation
+) : BaseLazyStaggeredGridWithOrientation(orientation) {
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters(): Array<Any> = arrayOf(
+            Orientation.Vertical,
+            Orientation.Horizontal,
+        )
+
+        private const val LazyStaggeredGrid = "Lazy"
+    }
+
+    private var itemSizeDp: Dp = Dp.Unspecified
+    private val itemSizePx: Int = 100
+
+    private lateinit var state: LazyStaggeredGridState
+
+    @Before
+    fun setUp() {
+        with(rule.density) {
+            itemSizeDp = itemSizePx.toDp()
+        }
+    }
+
+    @Test
+    fun arrangement_addsSpacingInBothDirections() {
+        state = LazyStaggeredGridState()
+        rule.setContent {
+            LazyStaggeredGrid(
+                lanes = 2,
+                modifier = Modifier
+                    .testTag(LazyStaggeredGrid)
+                    .axisSize(itemSizeDp * 3, itemSizeDp * 5),
+                state = state,
+                mainAxisArrangement = Arrangement.spacedBy(itemSizeDp),
+                crossAxisArrangement = Arrangement.spacedBy(itemSizeDp / 2)
+            ) {
+                items(100) {
+                    Spacer(Modifier.testTag("$it").mainAxisSize(itemSizeDp))
+                }
+            }
+        }
+
+        val crossAxisSizeDp = (itemSizeDp * 2.5f) / 2
+        val spacing = itemSizeDp / 2
+
+        rule.onNodeWithTag("0")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertCrossAxisSizeIsEqualTo(crossAxisSizeDp)
+
+        rule.onNodeWithTag("1")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(crossAxisSizeDp + spacing)
+            .assertCrossAxisSizeIsEqualTo(crossAxisSizeDp)
+
+        rule.onNodeWithTag("2")
+            .assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp * 2) // item + spacing
+            .assertMainAxisSizeIsEqualTo(itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertCrossAxisSizeIsEqualTo(crossAxisSizeDp)
+
+        rule.onNodeWithTag("3")
+            .assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp * 2) // item + spacing
+            .assertMainAxisSizeIsEqualTo(itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(crossAxisSizeDp + spacing)
+            .assertCrossAxisSizeIsEqualTo(crossAxisSizeDp)
+    }
+
+    @Test
+    fun arrangement_lastItem_noSpacingMainAxis() {
+        state = LazyStaggeredGridState()
+        rule.setContent {
+            LazyStaggeredGrid(
+                lanes = 2,
+                modifier = Modifier
+                    .testTag(LazyStaggeredGrid)
+                    .axisSize(itemSizeDp * 2, itemSizeDp * 5),
+                state = state,
+                mainAxisArrangement = Arrangement.spacedBy(itemSizeDp)
+            ) {
+                items(100) {
+                    BasicText(
+                        text = "$it",
+                        modifier = Modifier
+                            .testTag("$it")
+                            .mainAxisSize(itemSizeDp)
+                            .debugBorder()
+                    )
+                }
+            }
+        }
+
+        state.scrollTo(100)
+
+        rule.onNodeWithTag("98")
+            .assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp * 5 - itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(0.dp)
+
+        rule.onNodeWithTag("99")
+            .assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp * 5 - itemSizeDp)
+            .assertCrossAxisStartPositionInRootIsEqualTo(itemSizeDp)
+    }
+
+    @Test
+    fun arrangement_negative_itemsVisible() {
+        state = LazyStaggeredGridState()
+        rule.setContent {
+            LazyStaggeredGrid(
+                lanes = 2,
+                modifier = Modifier
+                    .testTag(LazyStaggeredGrid)
+                    .axisSize(itemSizeDp * 2, itemSizeDp * 5),
+                state = state,
+                mainAxisArrangement = Arrangement.spacedBy(-itemSizeDp)
+            ) {
+                items(100) {
+                    BasicText(
+                        text = "$it",
+                        modifier = Modifier
+                            .testTag("$it")
+                            .mainAxisSize(itemSizeDp * 2)
+                    )
+                }
+            }
+        }
+
+        rule.onNodeWithTag("0")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp * 2)
+
+        rule.onNodeWithTag("2")
+            .assertMainAxisStartPositionInRootIsEqualTo(itemSizeDp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp * 2)
+
+        state.scrollBy(itemSizeDp)
+
+        rule.onNodeWithTag("0")
+            .assertMainAxisStartPositionInRootIsEqualTo(-itemSizeDp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp * 2)
+
+        rule.onNodeWithTag("2")
+            .assertMainAxisStartPositionInRootIsEqualTo(0.dp)
+            .assertMainAxisSizeIsEqualTo(itemSizeDp * 2)
+    }
+}
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
index 10c8d13..5f2ab2d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/gestures/Scrollable.kt
@@ -322,6 +322,7 @@
     val flingBehavior: FlingBehavior,
     val overscrollEffect: OverscrollEffect?
 ) {
+    private val isNestedFlinging = mutableStateOf(false)
     fun Float.toOffset(): Offset = when {
         this == 0f -> Offset.Zero
         orientation == Horizontal -> Offset(this, 0f)
@@ -372,7 +373,8 @@
             leftForParent - parentConsumed,
             source
         )
-        return leftForParent
+
+        return leftForParent - parentConsumed
     }
 
     fun overscrollPreConsumeDelta(
@@ -410,6 +412,9 @@
     }
 
     suspend fun onDragStopped(initialVelocity: Velocity) {
+        // Self started flinging, set
+        registerNestedFling(true)
+
         val availableVelocity = initialVelocity.singleAxisVelocity()
         val preOverscrollConsumed =
             if (overscrollEffect != null && overscrollEffect.isEnabled) {
@@ -431,6 +436,9 @@
         if (overscrollEffect != null && overscrollEffect.isEnabled) {
             overscrollEffect.consumePostFling(totalLeft)
         }
+
+        // Self stopped flinging, reset
+        registerNestedFling(false)
     }
 
     suspend fun doFlingAnimation(available: Velocity): Velocity {
@@ -457,9 +465,13 @@
     }
 
     fun shouldScrollImmediately(): Boolean {
-        return scrollableState.isScrollInProgress ||
+        return scrollableState.isScrollInProgress || isNestedFlinging.value ||
             overscrollEffect?.isInProgress ?: false
     }
+
+    fun registerNestedFling(isFlinging: Boolean) {
+        isNestedFlinging.value = isFlinging
+    }
 }
 
 private class ScrollDraggableState(
@@ -495,6 +507,14 @@
     scrollLogic: State<ScrollingLogic>,
     enabled: Boolean
 ): NestedScrollConnection = object : NestedScrollConnection {
+    override fun onPreScroll(available: Offset, source: NestedScrollSource): Offset {
+        // child will fling, set
+        if (source == Fling) {
+            scrollLogic.value.registerNestedFling(true)
+        }
+        return Offset.Zero
+    }
+
     override fun onPostScroll(
         consumed: Offset,
         available: Offset,
@@ -514,6 +534,9 @@
             available - velocityLeft
         } else {
             Velocity.Zero
+        }.also {
+            // Flinging child finished flinging, reset
+            scrollLogic.value.registerNestedFling(false)
         }
     }
 }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
index b79d530..28f690f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridDsl.kt
@@ -38,7 +38,11 @@
  * @param columns description of the size and number of staggered grid columns.
  * @param modifier modifier to apply to the layout.
  * @param state state object that can be used to control and observe staggered grid state.
- * @param contentPadding padding around the content
+ * @param contentPadding padding around the content.
+ * @param verticalArrangement arrangement specifying vertical spacing between items. The item
+ *  arrangement specifics are ignored for now.
+ * @param horizontalArrangement arrangement specifying horizontal spacing between items. The item
+ *  arrangement specifics are ignored for now.
  * @param flingBehavior logic responsible for handling fling.
  * @param userScrollEnabled whether scroll with gestures or accessibility actions are allowed. It is
  *  still possible to scroll programmatically through state when [userScrollEnabled] is set to false
@@ -54,6 +58,8 @@
     modifier: Modifier = Modifier,
     state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(0.dp),
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(0.dp),
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
     content: LazyStaggeredGridScope.() -> Unit
@@ -62,10 +68,12 @@
         modifier = modifier,
         orientation = Orientation.Vertical,
         state = state,
+        verticalArrangement = verticalArrangement,
+        horizontalArrangement = horizontalArrangement,
         contentPadding = contentPadding,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        slotSizesSums = rememberColumnWidthSums(columns, Arrangement.Start, contentPadding),
+        slotSizesSums = rememberColumnWidthSums(columns, horizontalArrangement, contentPadding),
         content = content
     )
 }
@@ -112,7 +120,11 @@
  * @param rows description of the size and number of staggered grid columns.
  * @param modifier modifier to apply to the layout.
  * @param state state object that can be used to control and observe staggered grid state.
- * @param contentPadding padding around the content
+ * @param contentPadding padding around the content.
+ * @param verticalArrangement arrangement specifying vertical spacing between items. The item
+ *  arrangement specifics are ignored for now.
+ * @param horizontalArrangement arrangement specifying horizontal spacing between items. The item
+ *  arrangement specifics are ignored for now.
  * @param flingBehavior logic responsible for handling fling.
  * @param userScrollEnabled whether scroll with gestures or accessibility actions are allowed. It is
  *  still possible to scroll programmatically through state when [userScrollEnabled] is set to false
@@ -128,6 +140,8 @@
     modifier: Modifier = Modifier,
     state: LazyStaggeredGridState = rememberLazyStaggeredGridState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
+    verticalArrangement: Arrangement.Vertical = Arrangement.spacedBy(0.dp),
+    horizontalArrangement: Arrangement.Horizontal = Arrangement.spacedBy(0.dp),
     flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(),
     userScrollEnabled: Boolean = true,
     content: LazyStaggeredGridScope.() -> Unit
@@ -137,9 +151,11 @@
         orientation = Orientation.Horizontal,
         state = state,
         contentPadding = contentPadding,
+        verticalArrangement = verticalArrangement,
+        horizontalArrangement = horizontalArrangement,
         flingBehavior = flingBehavior,
         userScrollEnabled = userScrollEnabled,
-        slotSizesSums = rememberRowHeightSums(rows, Arrangement.Top, contentPadding),
+        slotSizesSums = rememberRowHeightSums(rows, verticalArrangement, contentPadding),
         content = content
     )
 }
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 c4ac4e5..a0c729a 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
@@ -36,7 +36,7 @@
 import kotlin.math.sign
 
 @ExperimentalFoundationApi
-internal fun LazyLayoutMeasureScope.measure(
+internal fun LazyLayoutMeasureScope.measureStaggeredGrid(
     state: LazyStaggeredGridState,
     itemProvider: LazyLayoutItemProvider,
     resolvedSlotSums: IntArray,
@@ -44,6 +44,8 @@
     isVertical: Boolean,
     contentOffset: IntOffset,
     mainAxisAvailableSize: Int,
+    mainAxisSpacing: Int,
+    crossAxisSpacing: Int,
     beforeContentPadding: Int,
     afterContentPadding: Int,
 ): LazyStaggeredGridMeasureResult {
@@ -57,7 +59,9 @@
         mainAxisAvailableSize = mainAxisAvailableSize,
         beforeContentPadding = beforeContentPadding,
         afterContentPadding = afterContentPadding,
-        measureScope = this
+        mainAxisSpacing = mainAxisSpacing,
+        crossAxisSpacing = crossAxisSpacing,
+        measureScope = this,
     )
 
     val initialItemIndices: IntArray
@@ -130,19 +134,23 @@
     val contentOffset: IntOffset,
     val beforeContentPadding: Int,
     val afterContentPadding: Int,
+    val mainAxisSpacing: Int,
+    val crossAxisSpacing: Int,
 ) {
     val measuredItemProvider = LazyStaggeredGridMeasureProvider(
         isVertical,
         itemProvider,
         measureScope,
         resolvedSlotSums
-    ) { index, key, placeables ->
+    ) { index, lane, key, placeables ->
+        val isLastInLane = spans.findNextItemIndex(index, lane) >= itemProvider.itemCount
         LazyStaggeredGridMeasuredItem(
             index,
             key,
             placeables,
             isVertical,
-            contentOffset
+            contentOffset,
+            if (isLastInLane) 0 else mainAxisSpacing
         )
     }
 
@@ -199,15 +207,12 @@
         // scrolling forward we would remove it back
         firstItemOffsets.offsetBy(-beforeContentPadding)
 
-        // define min and max offsets (min offset currently includes beforeContentPadding)
-        val minOffset = -beforeContentPadding
-
         fun hasSpaceBeforeFirst(): Boolean {
             for (lane in firstItemIndices.indices) {
                 val itemIndex = firstItemIndices[lane]
                 val itemOffset = firstItemOffsets[lane]
 
-                if (itemOffset < 0 && itemIndex > 0) {
+                if (itemOffset < -mainAxisSpacing && itemIndex > 0) {
                     return true
                 }
             }
@@ -269,6 +274,19 @@
             return misalignedOffsets || moreItemsInOtherLanes || firstItemInWrongLane
         }
 
+        // define min offset (currently includes beforeContentPadding)
+        val minOffset = -beforeContentPadding
+
+        // we scrolled backward, but there were not enough items to fill the start. this means
+        // some amount of scroll should be left over
+        if (firstItemOffsets[0] < minOffset) {
+            scrollDelta += firstItemOffsets[0]
+            firstItemOffsets.offsetBy(minOffset - firstItemOffsets[0])
+        }
+
+        // neutralize previously added start padding as we stopped filling the before content padding
+        firstItemOffsets.offsetBy(beforeContentPadding)
+
         laneToCheckForGaps =
             if (laneToCheckForGaps == -1) firstItemIndices.indexOf(0) else laneToCheckForGaps
 
@@ -281,20 +299,13 @@
                     initialScrollDelta = scrollDelta,
                     initialItemIndices = IntArray(firstItemIndices.size) { -1 },
                     initialItemOffsets = IntArray(firstItemOffsets.size) {
-                        initialItemOffsets[lane]
+                        firstItemOffsets[lane]
                     },
                     canRestartMeasure = false
                 )
             }
         }
 
-        // we scrolled backward, but there were not enough items to fill the start. this means
-        // some amount of scroll should be left over
-        if (firstItemOffsets[0] < minOffset) {
-            scrollDelta += firstItemOffsets[0]
-            firstItemOffsets.offsetBy(minOffset - firstItemOffsets[0])
-        }
-
         val currentItemIndices = initialItemIndices.copyOf().apply {
             // ensure indices match item count, in case it decreased
             ensureIndicesInRange(this, itemCount)
@@ -303,9 +314,6 @@
             -(initialItemOffsets[it] - scrollDelta)
         }
 
-        // neutralize previously added start padding as we stopped filling the before content padding
-        firstItemOffsets.offsetBy(beforeContentPadding)
-
         val maxOffset = (mainAxisAvailableSize + afterContentPadding).coerceAtLeast(0)
 
         // compose first visible items we received from state
@@ -380,7 +388,7 @@
                 val item = laneItems[i]
                 offset -= item.sizeWithSpacings
                 inBoundsIndex = i
-                if (offset <= minOffset) {
+                if (offset <= minOffset + mainAxisSpacing) {
                     break
                 }
             }
@@ -423,7 +431,9 @@
                         return measure(
                             initialScrollDelta = scrollDelta,
                             initialItemIndices = IntArray(firstItemIndices.size) { -1 },
-                            initialItemOffsets = IntArray(firstItemOffsets.size) { 0 },
+                            initialItemOffsets = IntArray(firstItemOffsets.size) {
+                                firstItemOffsets[laneIndex]
+                            },
                             canRestartMeasure = false
                         )
                     }
@@ -510,16 +520,16 @@
             }
             val item = measuredItems[laneIndex].removeFirst()
 
-            // todo(b/182882362): arrangement/spacing support
+            // todo(b/182882362): arrangement support
             val mainAxisOffset = itemScrollOffsets[laneIndex]
-            val crossAxisOffset = if (laneIndex == 0) 0 else resolvedSlotSums[laneIndex - 1]
-            positionedItems.add(
-                item.position(
-                    laneIndex,
-                    mainAxisOffset,
-                    crossAxisOffset,
-                )
-            )
+            val crossAxisOffset =
+                if (laneIndex == 0) {
+                    0
+                } else {
+                    resolvedSlotSums[laneIndex - 1] + crossAxisSpacing * laneIndex
+                }
+
+            positionedItems += item.position(laneIndex, mainAxisOffset, crossAxisOffset)
             itemScrollOffsets[laneIndex] += item.sizeWithSpacings
         }
 
@@ -612,7 +622,8 @@
     indices: IntArray,
     itemCount: Int
 ) {
-    for (i in indices.indices) {
+    // reverse traverse to make sure last items are recorded to the latter lanes
+    for (i in indices.indices.reversed()) {
         while (indices[i] >= itemCount) {
             indices[i] = findPreviousItemIndex(indices[i], i)
         }
@@ -647,10 +658,10 @@
         }
     }
 
-    fun getAndMeasure(index: Int, slot: Int): LazyStaggeredGridMeasuredItem {
+    fun getAndMeasure(index: Int, lane: Int): LazyStaggeredGridMeasuredItem {
         val key = itemProvider.getKey(index)
-        val placeables = measureScope.measure(index, childConstraints(slot))
-        return measuredItemFactory.createItem(index, key, placeables)
+        val placeables = measureScope.measure(index, childConstraints(lane))
+        return measuredItemFactory.createItem(index, lane, key, placeables)
     }
 }
 
@@ -658,6 +669,7 @@
 private fun interface MeasuredItemFactory {
     fun createItem(
         index: Int,
+        lane: Int,
         key: Any,
         placeables: List<Placeable>
     ): LazyStaggeredGridMeasuredItem
@@ -669,11 +681,14 @@
     val placeables: List<Placeable>,
     val isVertical: Boolean,
     val contentOffset: IntOffset,
+    val spacing: Int
 ) {
-    val sizeWithSpacings: Int = placeables.fastFold(0) { size, placeable ->
+    val mainAxisSize: Int = placeables.fastFold(0) { size, placeable ->
         size + if (isVertical) placeable.height else placeable.width
     }
 
+    val sizeWithSpacings: Int = mainAxisSize + spacing
+
     val crossAxisSize: Int = placeables.fastMaxOfOrNull {
         if (isVertical) it.width else it.height
     }!!
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
index 5734281..4b9097f 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/staggeredgrid/LazyStaggeredGridMeasurePolicy.kt
@@ -89,20 +89,35 @@
             IntOffset(beforeContentPadding, startContentPadding)
         }
 
+        val mainAxisSpacing = if (isVertical) {
+            verticalArrangement.spacing
+        } else {
+            horizontalArrangement.spacing
+        }.roundToPx()
+
+        val crossAxisSpacing = if (isVertical) {
+            horizontalArrangement.spacing
+        } else {
+            verticalArrangement.spacing
+        }.roundToPx()
+
         val horizontalPadding = contentPadding.run {
             calculateStartPadding(layoutDirection) + calculateEndPadding(layoutDirection)
         }.roundToPx()
         val verticalPadding = contentPadding.run {
             calculateTopPadding() + calculateBottomPadding()
         }.roundToPx()
-        measure(
-            state,
-            itemProvider,
-            resolvedSlotSums,
-            constraints.copy(
+
+        measureStaggeredGrid(
+            state = state,
+            itemProvider = itemProvider,
+            resolvedSlotSums = resolvedSlotSums,
+            constraints = constraints.copy(
                 minWidth = constraints.constrainWidth(horizontalPadding),
                 minHeight = constraints.constrainHeight(verticalPadding)
             ),
+            mainAxisSpacing = mainAxisSpacing,
+            crossAxisSpacing = crossAxisSpacing,
             contentOffset = contentOffset,
             mainAxisAvailableSize = mainAxisAvailableSize,
             isVertical = isVertical,
diff --git a/compose/material/material/api/1.3.0-beta03.txt b/compose/material/material/api/1.3.0-beta03.txt
index 5d66762..c523f6c 100644
--- a/compose/material/material/api/1.3.0-beta03.txt
+++ b/compose/material/material/api/1.3.0-beta03.txt
@@ -681,3 +681,19 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  public final class PullRefreshIndicatorKt {
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+  }
+
+  public final class PullRefreshKt {
+  }
+
+  public final class PullRefreshStateKt {
+  }
+
+}
+
diff --git a/compose/material/material/api/current.ignore b/compose/material/material/api/current.ignore
deleted file mode 100644
index 05d758d..0000000
--- a/compose/material/material/api/current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.material.DrawerState#close(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.material.DrawerState.close
-ParameterNameChange: androidx.compose.material.DrawerState#open(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.material.DrawerState.open
-ParameterNameChange: androidx.compose.material.SnackbarHostState#showSnackbar(String, String, androidx.compose.material.SnackbarDuration, kotlin.coroutines.Continuation<? super androidx.compose.material.SnackbarResult>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.compose.material.SnackbarHostState.showSnackbar
-
-
-RemovedMethod: androidx.compose.material.FabPosition#FabPosition():
-    Removed constructor androidx.compose.material.FabPosition()
diff --git a/compose/material/material/api/current.txt b/compose/material/material/api/current.txt
index 5d66762..c523f6c 100644
--- a/compose/material/material/api/current.txt
+++ b/compose/material/material/api/current.txt
@@ -681,3 +681,19 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  public final class PullRefreshIndicatorKt {
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+  }
+
+  public final class PullRefreshKt {
+  }
+
+  public final class PullRefreshStateKt {
+  }
+
+}
+
diff --git a/compose/material/material/api/public_plus_experimental_1.3.0-beta03.txt b/compose/material/material/api/public_plus_experimental_1.3.0-beta03.txt
index 16060f8..d6e80bb 100644
--- a/compose/material/material/api/public_plus_experimental_1.3.0-beta03.txt
+++ b/compose/material/material/api/public_plus_experimental_1.3.0-beta03.txt
@@ -946,3 +946,37 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class PullRefreshDefaults {
+    method public float getRefreshThreshold();
+    method public float getRefreshingOffset();
+    property public final float RefreshThreshold;
+    property public final float RefreshingOffset;
+    field public static final androidx.compose.material.pullrefresh.PullRefreshDefaults INSTANCE;
+  }
+
+  public final class PullRefreshIndicatorKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void PullRefreshIndicator(boolean refreshing, androidx.compose.material.pullrefresh.PullRefreshState state, optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional boolean scale);
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+    method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.ui.Modifier pullRefreshIndicatorTransform(androidx.compose.ui.Modifier, androidx.compose.material.pullrefresh.PullRefreshState state, optional boolean scale);
+  }
+
+  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);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class PullRefreshState {
+    method public float getProgress();
+    property public final float progress;
+  }
+
+  public final class PullRefreshStateKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.pullrefresh.PullRefreshState rememberPullRefreshState(boolean refreshing, kotlin.jvm.functions.Function0<kotlin.Unit> onRefresh, optional float refreshThreshold, optional float refreshingOffset);
+  }
+
+}
+
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index 16060f8..d6e80bb 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -946,3 +946,37 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class PullRefreshDefaults {
+    method public float getRefreshThreshold();
+    method public float getRefreshingOffset();
+    property public final float RefreshThreshold;
+    property public final float RefreshingOffset;
+    field public static final androidx.compose.material.pullrefresh.PullRefreshDefaults INSTANCE;
+  }
+
+  public final class PullRefreshIndicatorKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void PullRefreshIndicator(boolean refreshing, androidx.compose.material.pullrefresh.PullRefreshState state, optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional boolean scale);
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+    method @androidx.compose.material.ExperimentalMaterialApi public static androidx.compose.ui.Modifier pullRefreshIndicatorTransform(androidx.compose.ui.Modifier, androidx.compose.material.pullrefresh.PullRefreshState state, optional boolean scale);
+  }
+
+  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);
+  }
+
+  @androidx.compose.material.ExperimentalMaterialApi public final class PullRefreshState {
+    method public float getProgress();
+    property public final float progress;
+  }
+
+  public final class PullRefreshStateKt {
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static androidx.compose.material.pullrefresh.PullRefreshState rememberPullRefreshState(boolean refreshing, kotlin.jvm.functions.Function0<kotlin.Unit> onRefresh, optional float refreshThreshold, optional float refreshingOffset);
+  }
+
+}
+
diff --git a/compose/material/material/api/restricted_1.3.0-beta03.txt b/compose/material/material/api/restricted_1.3.0-beta03.txt
index 5d66762..c523f6c 100644
--- a/compose/material/material/api/restricted_1.3.0-beta03.txt
+++ b/compose/material/material/api/restricted_1.3.0-beta03.txt
@@ -681,3 +681,19 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  public final class PullRefreshIndicatorKt {
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+  }
+
+  public final class PullRefreshKt {
+  }
+
+  public final class PullRefreshStateKt {
+  }
+
+}
+
diff --git a/compose/material/material/api/restricted_current.ignore b/compose/material/material/api/restricted_current.ignore
deleted file mode 100644
index 05d758d..0000000
--- a/compose/material/material/api/restricted_current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.material.DrawerState#close(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.material.DrawerState.close
-ParameterNameChange: androidx.compose.material.DrawerState#open(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.material.DrawerState.open
-ParameterNameChange: androidx.compose.material.SnackbarHostState#showSnackbar(String, String, androidx.compose.material.SnackbarDuration, kotlin.coroutines.Continuation<? super androidx.compose.material.SnackbarResult>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.compose.material.SnackbarHostState.showSnackbar
-
-
-RemovedMethod: androidx.compose.material.FabPosition#FabPosition():
-    Removed constructor androidx.compose.material.FabPosition()
diff --git a/compose/material/material/api/restricted_current.txt b/compose/material/material/api/restricted_current.txt
index 5d66762..c523f6c 100644
--- a/compose/material/material/api/restricted_current.txt
+++ b/compose/material/material/api/restricted_current.txt
@@ -681,3 +681,19 @@
 
 }
 
+package androidx.compose.material.pullrefresh {
+
+  public final class PullRefreshIndicatorKt {
+  }
+
+  public final class PullRefreshIndicatorTransformKt {
+  }
+
+  public final class PullRefreshKt {
+  }
+
+  public final class PullRefreshStateKt {
+  }
+
+}
+
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
index d31c42f..6eaf8ed 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/MaterialDemos.kt
@@ -27,6 +27,9 @@
 import androidx.compose.material.samples.ModalDrawerSample
 import androidx.compose.material.samples.BottomSheetScaffoldSample
 import androidx.compose.material.samples.ContentAlphaSample
+import androidx.compose.material.samples.CustomPullRefreshSample
+import androidx.compose.material.samples.PullRefreshIndicatorTransformSample
+import androidx.compose.material.samples.PullRefreshSample
 import androidx.compose.material.samples.ScaffoldWithBottomBarAndCutout
 import androidx.compose.material.samples.ScaffoldWithCoroutinesSnackbar
 import androidx.compose.material.samples.ScaffoldWithSimpleSnackbar
@@ -109,6 +112,14 @@
                 ComposableDemo("Textfield decoration box") { DecorationBoxDemos() },
                 ComposableDemo("Alignment inside text fields") { VerticalAlignmentsInTextField() }
             )
+        ),
+        DemoCategory(
+            "PullRefresh",
+            listOf(
+                ComposableDemo("PullRefresh") { PullRefreshSample() },
+                ComposableDemo("Custom PullRefresh") { CustomPullRefreshSample() },
+                ComposableDemo("Custom Indicator") { PullRefreshIndicatorTransformSample() }
+            )
         )
     )
 )
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
new file mode 100644
index 0000000..2abcf1f
--- /dev/null
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/PullRefreshSamples.kt
@@ -0,0 +1,205 @@
+/*
+ * 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.compose.material.samples
+
+import androidx.annotation.Sampled
+import androidx.compose.animation.AnimatedVisibility
+import androidx.compose.animation.core.animate
+import androidx.compose.animation.core.animateFloatAsState
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.shape.RoundedCornerShape
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.LinearProgressIndicator
+import androidx.compose.material.ListItem
+import androidx.compose.material.Surface
+import androidx.compose.material.Text
+import androidx.compose.material.pullrefresh.PullRefreshIndicator
+import androidx.compose.material.pullrefresh.pullRefresh
+import androidx.compose.material.pullrefresh.pullRefreshIndicatorTransform
+import androidx.compose.material.pullrefresh.rememberPullRefreshState
+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.draw.rotate
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.dp
+import kotlinx.coroutines.delay
+import kotlinx.coroutines.launch
+
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun PullRefreshSample() {
+    val refreshScope = rememberCoroutineScope()
+    var refreshing by remember { mutableStateOf(false) }
+    var itemCount by remember { mutableStateOf(15) }
+
+    fun refresh() = refreshScope.launch {
+        refreshing = true
+        delay(1500)
+        itemCount += 5
+        refreshing = false
+    }
+
+    val state = rememberPullRefreshState(refreshing, ::refresh)
+
+    Box(Modifier.pullRefresh(state)) {
+        LazyColumn(Modifier.fillMaxSize()) {
+            if (!refreshing) {
+                items(itemCount) {
+                    ListItem { Text(text = "Item ${itemCount - it}") }
+                }
+            }
+        }
+
+        PullRefreshIndicator(refreshing, state, Modifier.align(Alignment.TopCenter))
+    }
+}
+
+/**
+ * An example to show how [pullRefresh] could be used to build a custom
+ * pull to refresh component.
+ */
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun CustomPullRefreshSample() {
+    val refreshScope = rememberCoroutineScope()
+    val threshold = with(LocalDensity.current) { 160.dp.toPx() }
+
+    var refreshing by remember { mutableStateOf(false) }
+    var itemCount by remember { mutableStateOf(15) }
+    var currentDistance by remember { mutableStateOf(0f) }
+
+    val progress = currentDistance / threshold
+
+    fun refresh() = refreshScope.launch {
+        refreshing = true
+        delay(1500)
+        itemCount += 5
+        refreshing = false
+    }
+
+    fun onPull(pullDelta: Float): Float = when {
+        refreshing -> 0f
+        else -> {
+            val newOffset = (currentDistance + pullDelta).coerceAtLeast(0f)
+            val dragConsumed = newOffset - currentDistance
+            currentDistance = newOffset
+            dragConsumed
+        }
+    }
+
+    suspend fun onRelease() {
+        if (refreshing) return // Already refreshing - don't call refresh again.
+        if (currentDistance > threshold) refresh()
+
+        animate(initialValue = currentDistance, targetValue = 0f) { value, _ ->
+            currentDistance = value
+        }
+    }
+
+    Box(Modifier.pullRefresh(::onPull, { onRelease() })) {
+        LazyColumn {
+            if (!refreshing) {
+                items(itemCount) {
+                    ListItem { Text(text = "Item ${itemCount - it}") }
+                }
+            }
+        }
+
+        // Custom progress indicator
+        AnimatedVisibility(visible = (refreshing || progress > 0)) {
+            if (refreshing) {
+                LinearProgressIndicator(Modifier.fillMaxWidth())
+            } else {
+                LinearProgressIndicator(progress, Modifier.fillMaxWidth())
+            }
+        }
+    }
+}
+
+/**
+ * An example to show how [pullRefreshIndicatorTransform] can be given custom contents to create
+ * a custom indicator.
+ */
+@Sampled
+@Composable
+@OptIn(ExperimentalMaterialApi::class)
+fun PullRefreshIndicatorTransformSample() {
+    val refreshScope = rememberCoroutineScope()
+    var refreshing by remember { mutableStateOf(false) }
+    var itemCount by remember { mutableStateOf(15) }
+
+    fun refresh() = refreshScope.launch {
+        refreshing = true
+        delay(1500)
+        itemCount += 5
+        refreshing = false
+    }
+
+    val state = rememberPullRefreshState(refreshing, ::refresh)
+    val rotation = animateFloatAsState(state.progress * 120)
+
+    Box(
+        Modifier
+            .fillMaxSize()
+            .pullRefresh(state)
+    ) {
+        LazyColumn {
+            if (!refreshing) {
+                items(itemCount) {
+                    ListItem { Text(text = "Item ${itemCount - it}") }
+                }
+            }
+        }
+
+        Surface(
+            modifier = Modifier
+                .size(40.dp)
+                .align(Alignment.TopCenter)
+                .pullRefreshIndicatorTransform(state)
+                .rotate(rotation.value),
+            shape = RoundedCornerShape(10.dp),
+            color = Color.DarkGray,
+            elevation = if (state.progress > 0 || refreshing) 20.dp else 0.dp,
+        ) {
+            Box {
+                if (refreshing) {
+                    CircularProgressIndicator(
+                        modifier = Modifier
+                            .align(Alignment.Center)
+                            .size(25.dp),
+                        color = Color.White,
+                        strokeWidth = 3.dp
+                    )
+                }
+            }
+        }
+    }
+}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTest.kt
new file mode 100644
index 0000000..98399ac
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTest.kt
@@ -0,0 +1,170 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.ListItem
+import androidx.compose.material.Text
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.assertIsDisplayed
+import androidx.compose.ui.test.assertIsNotDisplayed
+import androidx.compose.ui.test.getUnclippedBoundsInRoot
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onChild
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class PullRefreshIndicatorTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @Test
+    fun indicatorDisplayed_refreshingInitially() {
+        rule.setContent {
+            val state = rememberPullRefreshState(true, {})
+            Box(Modifier.fillMaxSize()) {
+                PullRefreshIndicator(true, state, Modifier.testTag(IndicatorTag))
+            }
+        }
+        indicatorNode.assertIsDisplayed()
+    }
+
+    @Test
+    fun indicatorDisplayed_setRefreshing() {
+        var refreshing by mutableStateOf(false)
+
+        rule.setContent {
+            val state = rememberPullRefreshState(refreshing, {})
+            Box(Modifier.fillMaxSize()) {
+                PullRefreshIndicator(refreshing, state, Modifier.testTag(IndicatorTag))
+            }
+        }
+
+        indicatorNode.assertIsNotDisplayed()
+
+        refreshing = true
+
+        rule.waitForIdle()
+        indicatorNode.assertIsDisplayed()
+    }
+
+    @Test
+    fun indicatorDisplayed_pullRefresh() {
+        var refreshCount = 0
+
+        var refreshing by mutableStateOf(false)
+
+        rule.setContent {
+            val state = rememberPullRefreshState(refreshing, { refreshing = true; refreshCount++ })
+
+            Box(
+                Modifier
+                    .pullRefresh(state)
+                    .testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(30) {
+                        ListItem {
+                            Text("Item: $it")
+                        }
+                    }
+                }
+                PullRefreshIndicator(refreshing, state, Modifier.testTag(IndicatorTag))
+            }
+        }
+
+        indicatorNode.assertIsNotDisplayed()
+        pullRefreshNode.performTouchInput { swipeDown() }
+
+        rule.waitForIdle()
+        assertThat(refreshCount).isEqualTo(1)
+        assertThat(refreshing).isTrue()
+        indicatorNode.assertIsDisplayed()
+
+        refreshing = false
+
+        rule.waitForIdle()
+        indicatorNode.assertIsNotDisplayed()
+    }
+
+    @Test
+    fun refreshingIndicator_returnsToRest() {
+        var refreshing by mutableStateOf(false)
+
+        rule.setContent {
+            val state = rememberPullRefreshState(refreshing, {})
+
+            Box(
+                Modifier
+                    .pullRefresh(state)
+                    .testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(30) {
+                        ListItem {
+                            Text("Item: $it")
+                        }
+                    }
+                }
+                PullRefreshIndicator(refreshing, state, Modifier.testTag(IndicatorTag))
+            }
+        }
+
+        indicatorNode.assertIsNotDisplayed()
+        val restingBounds = indicatorNode.getUnclippedBoundsInRoot()
+
+        refreshing = true
+
+        rule.waitForIdle()
+        indicatorNode.assertIsDisplayed()
+        val refreshingBounds = indicatorNode.getUnclippedBoundsInRoot()
+
+        pullRefreshNode.performTouchInput { swipeDown() }
+
+        rule.waitForIdle()
+        indicatorNode.assertIsDisplayed()
+        assertThat(indicatorNode.getUnclippedBoundsInRoot()).isEqualTo(refreshingBounds)
+
+        refreshing = false
+
+        rule.waitForIdle()
+        indicatorNode.assertIsNotDisplayed()
+        assertThat(indicatorNode.getUnclippedBoundsInRoot()).isEqualTo(restingBounds)
+    }
+
+    private val pullRefreshNode get() = rule.onNodeWithTag(PullRefreshTag)
+    private val indicatorNode get() = rule.onNodeWithTag(IndicatorTag).onChild()
+}
+
+private const val PullRefreshTag = "pull-refresh"
+private const val IndicatorTag = "pull-refresh-indicator"
\ No newline at end of file
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
new file mode 100644
index 0000000..63c1027
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshStateTest.kt
@@ -0,0 +1,327 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Text
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+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 org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class PullRefreshStateTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val pullRefreshNode = rule.onNodeWithTag(PullRefreshTag)
+
+    @Test
+    fun pullBeyondThreshold_triggersRefresh() {
+
+        var refreshCount = 0
+        var touchSlop = 0f
+        val threshold = 400f
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            val state = rememberPullRefreshState(
+                refreshing = false,
+                 refreshCount++ },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        // Account for PullModifier - pull down twice the threshold value.
+        pullRefreshNode.performTouchInput { swipeDown(endY = 2 * threshold + touchSlop + 1f) }
+
+        rule.runOnIdle { assertThat(refreshCount).isEqualTo(1) }
+    }
+
+    @Test
+    fun pullLessThanOrEqualToThreshold_doesNot_triggerRefresh() {
+        var refreshCount = 0
+        var touchSlop = 0f
+        val threshold = 400f
+
+        rule.setContent {
+            touchSlop = LocalViewConfiguration.current.touchSlop
+            val state = rememberPullRefreshState(
+                refreshing = false,
+                 refreshCount++ },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        // Account for PullModifier - pull down twice the threshold value.
+
+        // Less than threshold
+        pullRefreshNode.performTouchInput { swipeDown(endY = 2 * threshold + touchSlop - 1f) }
+
+        rule.waitForIdle()
+
+        // Equal to threshold
+        pullRefreshNode.performTouchInput { swipeDown(endY = 2 * threshold + touchSlop) }
+
+        rule.runOnIdle { assertThat(refreshCount).isEqualTo(0) }
+    }
+
+    @Test
+    fun progressAndPosition_scaleCorrectly_untilThreshold() {
+        lateinit var state: PullRefreshState
+        var refreshCount = 0
+        val threshold = 400f
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                >
+                    state.setRefreshing(true)
+                    refreshCount++
+                    state.setRefreshing(false)
+                },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        state.onPull(threshold)
+
+        rule.runOnIdle {
+            val adjustedDistancePulled = threshold / 2 // Account for PullMultiplier.
+            assertThat(state.progress).isEqualTo(0.5f)
+            assertThat(state.position).isEqualTo(adjustedDistancePulled)
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.onPull(threshold + 1f)
+
+        rule.runOnIdle {
+            val adjustedDistancePulled = (2 * threshold + 1f) / 2 // Account for PullMultiplier.
+            assertThat(state.progress).isEqualTo(adjustedDistancePulled / threshold)
+            assertThat(state.position).isEqualTo(
+                calculateIndicatorPosition(adjustedDistancePulled, threshold)
+            )
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.onRelease()
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(0f)
+            assertThat(state.position).isEqualTo(0f)
+            assertThat(refreshCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun progressAndPosition_scaleCorrectly_beyondThreshold() {
+        lateinit var state: PullRefreshState
+        var refreshCount = 0
+        val threshold = 400f
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                 refreshCount++ },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        state.onPull(2 * threshold)
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(1f)
+            assertThat(state.position).isEqualTo(threshold) // Account for PullMultiplier.
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.onPull(threshold)
+
+        rule.runOnIdle {
+            val adjustedDistancePulled = 3 * threshold / 2 // Account for PullMultiplier.
+            assertThat(state.progress).isEqualTo(1.5f)
+            assertThat(state.position).isEqualTo(
+                calculateIndicatorPosition(adjustedDistancePulled, threshold)
+            )
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.onRelease()
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(0f)
+            assertThat(refreshCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun positionIsCapped() {
+        lateinit var state: PullRefreshState
+        var refreshCount = 0
+        val threshold = 400f
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                 refreshCount++ },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        state.onPull(10 * threshold)
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(5f) // Account for PullMultiplier.
+            // Indicator position is capped to 2 times the refresh threshold.
+            assertThat(state.position).isEqualTo(2 * threshold)
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.onRelease()
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(0f)
+            assertThat(refreshCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun pullInterrupted() {
+        lateinit var state: PullRefreshState
+        var refreshCount = 0
+        val threshold = 400f
+        val refreshingOffset = 200f
+
+        rule.setContent {
+            state = rememberPullRefreshState(
+                refreshing = false,
+                 refreshCount++ },
+                refreshThreshold = with(LocalDensity.current) { threshold.toDp() },
+                refreshingOffset = with(LocalDensity.current) { refreshingOffset.toDp() }
+            )
+
+            Box(Modifier.pullRefresh(state).testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+
+        state.onPull(threshold)
+
+        rule.runOnIdle {
+            val adjustedDistancePulled = threshold / 2 // Account for PullMultiplier.
+            assertThat(state.progress).isEqualTo(0.5f)
+            assertThat(state.position).isEqualTo(adjustedDistancePulled)
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.setRefreshing(true)
+
+        val consumed = state.onPull(threshold)
+
+        rule.runOnIdle {
+            assertThat(consumed).isEqualTo(0f)
+            assertThat(state.progress).isEqualTo(0f)
+            assertThat(state.position).isEqualTo(refreshingOffset)
+            assertThat(refreshCount).isEqualTo(0)
+        }
+
+        state.setRefreshing(false)
+
+        rule.runOnIdle {
+            assertThat(state.progress).isEqualTo(0f)
+            assertThat(state.position).isEqualTo(0f)
+            assertThat(refreshCount).isEqualTo(0)
+        }
+    }
+
+    /**
+     * Taken from the private function of the same name in [PullRefreshState].
+     */
+    private fun calculateIndicatorPosition(distance: Float, threshold: Float): Float = when {
+        distance <= threshold -> distance
+        else -> {
+            val overshootPercent = abs(distance / threshold) - 1.0f
+            val linearTension = overshootPercent.coerceIn(0f, 2f)
+            val tensionPercent = linearTension - linearTension.pow(2) / 4
+            val extraOffset = threshold * tensionPercent
+            threshold + extraOffset
+        }
+    }
+}
+
+private const val PullRefreshTag = "PullRefresh"
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshTest.kt
new file mode 100644
index 0000000..260d45f
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/pullrefresh/PullRefreshTest.kt
@@ -0,0 +1,225 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.Text
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.platform.LocalViewConfiguration
+import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.performTouchInput
+import androidx.compose.ui.test.swipeDown
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@OptIn(ExperimentalMaterialApi::class)
+class PullRefreshTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private val pullRefreshNode = rule.onNodeWithTag(PullRefreshTag)
+
+    @Test
+    fun pullDownFromTop_producesCorrect_pullDelta() {
+        var distancePulled = 0f
+
+        fun onPull(pullDelta: Float): Float {
+            distancePulled += pullDelta
+            return pullDelta // Consume whole delta.
+        }
+
+        val touchSlop = setPullRefreshAndReturnSlop(::onPull) { 0f }
+        val pullAmount = 400f
+
+        pullRefreshNode.performTouchInput {
+            swipeDown(endY = pullAmount + touchSlop)
+        }
+
+        rule.runOnIdle { assertThat(distancePulled).isEqualTo(pullAmount) }
+    }
+
+    @Test
+    fun onRelease_calledWhen_pullEnds() {
+        var releaseCount = 0
+
+        setPullRefreshAndReturnSlop({ it }) { releaseCount++; 0f }
+
+        pullRefreshNode.performTouchInput {
+            down(Offset.Zero)
+            moveBy(Offset(0f, 400f))
+        }
+
+        rule.runOnIdle { assertThat(releaseCount).isEqualTo(0) }
+
+        pullRefreshNode.performTouchInput { up() }
+
+        rule.runOnIdle { assertThat(releaseCount).isEqualTo(1) }
+    }
+
+    @Test
+    fun swipeUp_pullDown() {
+        var distancePulled = 0f
+        var deltaGiven = 0f
+        var releaseCount = 0
+
+        fun onPull(pullDelta: Float): Float {
+            deltaGiven += pullDelta
+            val newDist = (distancePulled + pullDelta).coerceAtLeast(0f)
+            val consumed = newDist - distancePulled
+            distancePulled = newDist
+            return consumed
+        }
+
+        val touchSlop = setPullRefreshAndReturnSlop(::onPull) { releaseCount++; 0f }
+
+        pullRefreshNode.performTouchInput {
+            down(Offset(0f, 800f))
+            moveBy(Offset(0f, -(400f + touchSlop)))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(-400f)
+            assertThat(distancePulled).isEqualTo(0f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, 200f))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(-400f)
+            assertThat(distancePulled).isEqualTo(0f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, 200f))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(-400f)
+            assertThat(distancePulled).isEqualTo(0f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, 200f))
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(-200f)
+            assertThat(distancePulled).isEqualTo(200f)
+            assertThat(releaseCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun pullDown_swipeUp() {
+        var distancePulled = 0f
+        var deltaGiven = 0f
+        var releaseCount = 0
+
+        fun onPull(pullDelta: Float): Float {
+            deltaGiven += pullDelta
+            val newDist = (distancePulled + pullDelta).coerceAtLeast(0f)
+            val consumed = newDist - distancePulled
+            distancePulled = newDist
+            return consumed
+        }
+
+        val touchSlop = setPullRefreshAndReturnSlop(::onPull) { releaseCount++; 0f }
+
+        pullRefreshNode.performTouchInput {
+            down(Offset.Zero)
+            moveBy(Offset(0f, 400f + touchSlop))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(400f)
+            assertThat(distancePulled).isEqualTo(400f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, -200f))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(200f)
+            assertThat(distancePulled).isEqualTo(200f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, -200f))
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(0f)
+            assertThat(distancePulled).isEqualTo(0f)
+            assertThat(releaseCount).isEqualTo(0)
+        }
+
+        pullRefreshNode.performTouchInput {
+            moveBy(Offset(0f, -200f))
+            up()
+        }
+
+        rule.runOnIdle {
+            assertThat(deltaGiven).isEqualTo(-200f)
+            assertThat(distancePulled).isEqualTo(0f)
+            assertThat(releaseCount).isEqualTo(1)
+        }
+    }
+
+    private fun setPullRefreshAndReturnSlop(
+        onPull: (pullDelta: Float) -> Float,
+        onRelease: (flingVelocity: Float) -> Float,
+    ): Float {
+        var slop = 0f
+        rule.setContent {
+            slop = LocalViewConfiguration.current.touchSlop
+            Box(
+                Modifier
+                    .pullRefresh({ onPull(it) }, { onRelease(it) })
+                    .testTag(PullRefreshTag)) {
+                LazyColumn {
+                    items(100) {
+                        Text("item $it")
+                    }
+                }
+            }
+        }
+        return slop
+    }
+}
+
+private const val PullRefreshTag = "PullRefresh"
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
new file mode 100644
index 0000000..2ef2c9b
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefresh.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.input.nestedscroll.NestedScrollConnection
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource
+import androidx.compose.ui.input.nestedscroll.NestedScrollSource.Companion.Drag
+import androidx.compose.ui.input.nestedscroll.nestedScroll
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.platform.inspectable
+import androidx.compose.ui.unit.Velocity
+
+/**
+ * PullRefresh modifier to be used in conjunction with [PullRefreshState]. Provides a connection
+ * to the nested scroll system. Based on Android's SwipeRefreshLayout.
+ *
+ * @sample androidx.compose.material.samples.PullRefreshSample
+ *
+ * @param state The [PullRefreshState] associated with this pull-to-refresh component.
+ * The state will be updated by this modifier.
+ * @param enabled If not enabled, all scroll delta and fling velocity will be ignored.
+ */
+// TODO(b/244423199): Move pullRefresh into its own material library similar to material-ripple.
+@ExperimentalMaterialApi
+fun Modifier.pullRefresh(
+    state: PullRefreshState,
+    enabled: Boolean = true
+) = inspectable(inspectorInfo = debugInspectorInfo {
+    name = "pullRefresh"
+    properties["state"] = state
+    properties["enabled"] = enabled
+}) {
+    Modifier.pullRefresh(state::onPull, { state.onRelease() }, enabled)
+}
+
+/**
+ * A modifier for building pull-to-refresh components. Provides a connection to the nested scroll
+ * system.
+ *
+ * @sample androidx.compose.material.samples.CustomPullRefreshSample
+ *
+ * @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.
+ * @param onRelease Callback for when drag is released, takes float flingVelocity as argument.
+ * @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,
+    enabled: Boolean = true
+) = inspectable(inspectorInfo = debugInspectorInfo {
+    name = "pullRefresh"
+    properties["onPull"] = onPull
+    properties["onRelease"] = onRelease
+    properties["enabled"] = enabled
+}) {
+    Modifier.nestedScroll(PullRefreshNestedScrollConnection(onPull, onRelease, enabled))
+}
+
+private class PullRefreshNestedScrollConnection(
+    private val onPull: (pullDelta: Float) -> Float,
+    private val onRelease: suspend (flingVelocity: Float) -> Unit,
+    private val enabled: Boolean
+) : NestedScrollConnection {
+
+    override fun onPreScroll(
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset = when {
+        !enabled -> Offset.Zero
+        source == Drag && available.y < 0 -> Offset(0f, onPull(available.y)) // Swiping up
+        else -> Offset.Zero
+    }
+
+    override fun onPostScroll(
+        consumed: Offset,
+        available: Offset,
+        source: NestedScrollSource
+    ): Offset = when {
+        !enabled -> Offset.Zero
+        source == Drag && available.y > 0 -> Offset(0f, onPull(available.y)) // Pulling down
+        else -> Offset.Zero
+    }
+
+    override suspend fun onPreFling(available: Velocity): Velocity {
+        onRelease(available.y)
+        return Velocity.Zero
+    }
+}
\ 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
new file mode 100644
index 0000000..371e4cd
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicator.kt
@@ -0,0 +1,221 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.animation.Crossfade
+import androidx.compose.animation.core.tween
+import androidx.compose.foundation.Canvas
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.foundation.shape.CircleShape
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.Surface
+import androidx.compose.material.contentColorFor
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.derivedStateOf
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.geometry.Offset
+import androidx.compose.ui.geometry.Rect
+import androidx.compose.ui.geometry.center
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.PathFillType
+import androidx.compose.ui.graphics.StrokeCap
+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
+import kotlin.math.max
+import kotlin.math.min
+import kotlin.math.pow
+
+/**
+ * The default indicator for Compose pull-to-refresh, based on Android's SwipeRefreshLayout.
+ *
+ * @sample androidx.compose.material.samples.PullRefreshSample
+ *
+ * @param refreshing A boolean representing whether a refresh is occurring.
+ * @param state The [PullRefreshState] which controls where and how the indicator will be drawn.
+ * @param modifier Modifiers for the indicator.
+ * @param backgroundColor The color of the indicator's background.
+ * @param contentColor The color of the indicator's arc and arrow.
+ * @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
+ */
+@Composable
+@ExperimentalMaterialApi
+// TODO(b/244423199): Consider whether the state parameter should be replaced with lambdas to
+//  enable people to use this indicator with custom pull-to-refresh components.
+fun PullRefreshIndicator(
+    refreshing: Boolean,
+    state: PullRefreshState,
+    modifier: Modifier = Modifier,
+    backgroundColor: Color = MaterialTheme.colors.surface,
+    contentColor: Color = contentColorFor(backgroundColor),
+    scale: Boolean = false
+) {
+    val showElevation by remember(refreshing, state) {
+        derivedStateOf { refreshing || state.position > 0.5f }
+    }
+
+    Surface(
+        modifier = modifier
+            .size(IndicatorSize)
+            .pullRefreshIndicatorTransform(state, scale),
+        shape = SpinnerShape,
+        color = backgroundColor,
+        elevation = if (showElevation) Elevation else 0.dp,
+    ) {
+        Crossfade(
+            targetState = refreshing,
+            animationSpec = tween(durationMillis = CrossfadeDurationMs)
+        ) { refreshing ->
+            Box(
+                modifier = Modifier.fillMaxSize(),
+                contentAlignment = Alignment.Center
+            ) {
+                val spinnerSize = (ArcRadius + StrokeWidth).times(2)
+
+                if (refreshing) {
+                    CircularProgressIndicator(
+                        color = contentColor,
+                        strokeWidth = StrokeWidth,
+                        modifier = Modifier.size(spinnerSize),
+                    )
+                } else {
+                    CircularArrowIndicator(state, contentColor, Modifier.size(spinnerSize))
+                }
+            }
+        }
+    }
+}
+
+/**
+ * Modifier.size MUST be specified.
+ */
+@Composable
+@ExperimentalMaterialApi
+private fun CircularArrowIndicator(
+    state: PullRefreshState,
+    color: Color,
+    modifier: Modifier,
+) {
+    val path = remember { Path().apply { fillType = PathFillType.EvenOdd } }
+
+    Canvas(modifier.semantics { contentDescription = "Refreshing" }) {
+        val values = ArrowValues(state.progress)
+
+        rotate(degrees = values.rotation) {
+            val arcRadius = ArcRadius.toPx() + StrokeWidth.toPx() / 2f
+            val arcBounds = Rect(
+                size.center.x - arcRadius,
+                size.center.y - arcRadius,
+                size.center.x + arcRadius,
+                size.center.y + arcRadius
+            )
+            drawArc(
+                color = color,
+                alpha = values.alpha,
+                startAngle = values.startAngle,
+                sweepAngle = values.endAngle - values.startAngle,
+                useCenter = false,
+                topLeft = arcBounds.topLeft,
+                size = arcBounds.size,
+                style = Stroke(
+                    width = StrokeWidth.toPx(),
+                    cap = StrokeCap.Square
+                )
+            )
+            drawArrow(path, arcBounds, color, values)
+        }
+    }
+}
+
+@Immutable
+private class ArrowValues(
+    val alpha: Float,
+    val rotation: Float,
+    val startAngle: Float,
+    val endAngle: Float,
+    val scale: Float
+)
+
+private fun ArrowValues(progress: Float): ArrowValues {
+    // Discard first 40% of progress. Scale remaining progress to full range between 0 and 100%.
+    val adjustedPercent = max(min(1f, progress) - 0.4f, 0f) * 5 / 3
+    // How far beyond the threshold pull has gone, as a percentage of the threshold.
+    val overshootPercent = abs(progress) - 1.0f
+    // Limit the overshoot to 200%. Linear between 0 and 200.
+    val linearTension = overshootPercent.coerceIn(0f, 2f)
+    // Non-linear tension. Increases with linearTension, but at a decreasing rate.
+    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)
+}
+
+private fun DrawScope.drawArrow(arrow: Path, bounds: Rect, color: Color, 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
+
+    // Line to tip of arrow
+    arrow.lineTo(
+        x = ArrowWidth.toPx() * values.scale / 2,
+        y = ArrowHeight.toPx() * values.scale
+    )
+
+    val radius = min(bounds.width, bounds.height) / 2f
+    val inset = ArrowWidth.toPx() * values.scale / 2f
+    arrow.translate(
+        Offset(
+            x = radius + bounds.center.x - inset,
+            y = bounds.center.y + StrokeWidth.toPx() / 2f
+        )
+    )
+    arrow.close()
+    rotate(degrees = values.endAngle) {
+        drawPath(path = arrow, color = color, alpha = values.alpha)
+    }
+}
+
+private const val CrossfadeDurationMs = 100
+private const val MaxProgressArc = 0.8f
+
+private val IndicatorSize = 40.dp
+private val SpinnerShape = CircleShape
+private val ArcRadius = 7.5.dp
+private val StrokeWidth = 2.5.dp
+private val ArrowWidth = 10.dp
+private val ArrowHeight = 5.dp
+private val Elevation = 6.dp
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
new file mode 100644
index 0000000..7ca9609
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshIndicatorTransform.kt
@@ -0,0 +1,65 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.material.ExperimentalMaterialApi
+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.composed
+import androidx.compose.ui.graphics.graphicsLayer
+import androidx.compose.ui.layout.onSizeChanged
+import androidx.compose.ui.platform.debugInspectorInfo
+
+/**
+ * A modifier for translating the position and scaling the size of a pull-to-refresh indicator
+ * based on the given [PullRefreshState].
+ *
+ * @sample androidx.compose.material.samples.PullRefreshIndicatorTransformSample
+ *
+ * @param state The [PullRefreshState] which determines the position of the indicator.
+ * @param scale A boolean controlling whether the indicator's size scales with pull progress or not.
+ */
+@ExperimentalMaterialApi
+// TODO: Consider whether the state parameter should be replaced with lambdas.
+fun Modifier.pullRefreshIndicatorTransform(
+    state: PullRefreshState,
+    scale: Boolean = false,
+) = composed(inspectorInfo = debugInspectorInfo {
+    name = "pullRefreshIndicatorTransform"
+    properties["state"] = state
+    properties["scale"] = scale
+}) {
+    var height by remember { mutableStateOf(0) }
+
+    Modifier
+        .onSizeChanged { height = it.height }
+        .graphicsLayer {
+            translationY = state.position - height
+
+            if (scale && !state.refreshing) {
+                val scaleFraction = LinearOutSlowInEasing
+                    .transform(state.position / state.threshold)
+                    .coerceIn(0f, 1f)
+                scaleX = scaleFraction
+                scaleY = scaleFraction
+            }
+        }
+}
\ No newline at end of file
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
new file mode 100644
index 0000000..e673400
--- /dev/null
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/pullrefresh/PullRefreshState.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.compose.material.pullrefresh
+
+import androidx.compose.animation.core.animate
+import androidx.compose.material.ExperimentalMaterialApi
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.State
+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.rememberUpdatedState
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Dp
+import androidx.compose.ui.unit.dp
+import kotlin.math.abs
+import kotlin.math.pow
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.launch
+
+/**
+ * Creates a [PullRefreshState] that is remembered across compositions.
+ *
+ * Changes to [refreshing] will result in [PullRefreshState] being updated.
+ *
+ * @sample androidx.compose.material.samples.PullRefreshSample
+ *
+ * @param refreshing A boolean representing whether a refresh is currently occurring.
+ * @param onRefresh The function to be called to trigger a refresh.
+ * @param refreshThreshold The threshold below which, if a release
+ * occurs, [onRefresh] will be called.
+ * @param refreshingOffset The offset at which the indicator will be drawn while refreshing. This
+ * offset corresponds to the position of the bottom of the indicator.
+ */
+@Composable
+@ExperimentalMaterialApi
+fun rememberPullRefreshState(
+    refreshing: Boolean,
+    onRefresh: () -> Unit,
+    refreshThreshold: Dp = PullRefreshDefaults.RefreshThreshold,
+    refreshingOffset: Dp = PullRefreshDefaults.RefreshingOffset,
+): PullRefreshState {
+    require(refreshThreshold > 0.dp) { "The refresh trigger must be greater than zero!" }
+
+    val scope = rememberCoroutineScope()
+    val >
+    val thresholdPx: Float
+    val refreshingOffsetPx: Float
+
+    with(LocalDensity.current) {
+        thresholdPx = refreshThreshold.toPx()
+        refreshingOffsetPx = refreshingOffset.toPx()
+    }
+
+    // refreshThreshold and refreshingOffset should not be changed after instantiation, so any
+    // changes to these values are ignored.
+    val state = remember(scope) {
+        PullRefreshState(scope, onRefreshState, refreshingOffsetPx, thresholdPx)
+    }
+
+    SideEffect {
+        state.setRefreshing(refreshing)
+    }
+
+    return state
+}
+
+/**
+ * A state object that can be used in conjunction with [pullRefresh] to add pull-to-refresh
+ * behaviour to a scroll component. Based on Android's SwipeRefreshLayout.
+ *
+ * Provides [progress], a float representing how far the user has pulled as a percentage of the
+ * refreshThreshold. Values of one or less indicate that the user has not yet pulled past the
+ * threshold. Values greater than one indicate how far past the threshold the user has pulled.
+ *
+ * Can be used in conjunction with [pullRefreshIndicatorTransform] to implement Android-like
+ * pull-to-refresh behaviour with a custom indicator.
+ *
+ * Should be created using [rememberPullRefreshState].
+ */
+@ExperimentalMaterialApi
+class PullRefreshState internal constructor(
+    private val animationScope: CoroutineScope,
+    private val onRefreshState: State<() -> Unit>,
+    private val refreshingOffset: Float,
+    internal val threshold: Float
+) {
+    /**
+     * A float representing how far the user has pulled as a percentage of the refreshThreshold.
+     *
+     * If the component has not been pulled at all, progress is zero. If the pull has reached
+     * halfway to the threshold, progress is 0.5f. A value greater than 1 indicates that pull has
+     * gone beyond the refreshThreshold - e.g. a value of 2f indicates that the user has pulled to
+     * two times the refreshThreshold.
+     */
+    val progress get() = adjustedDistancePulled / threshold
+
+    internal val refreshing get() = _refreshing
+    internal val position get() = _position
+
+    private val adjustedDistancePulled by derivedStateOf { distancePulled * DragMultiplier }
+
+    private var _refreshing by mutableStateOf(false)
+    private var _position by mutableStateOf(0f)
+    private var distancePulled by mutableStateOf(0f)
+
+    internal fun onPull(pullDelta: Float): Float {
+        if (this._refreshing) return 0f // Already refreshing, do nothing.
+
+        val newOffset = (distancePulled + pullDelta).coerceAtLeast(0f)
+        val dragConsumed = newOffset - distancePulled
+        distancePulled = newOffset
+        _position = calculateIndicatorPosition()
+        return dragConsumed
+    }
+
+    internal fun onRelease() {
+        if (!this._refreshing) {
+            if (adjustedDistancePulled > threshold) {
+                onRefreshState.value()
+            } else {
+                animateIndicatorTo(0f)
+            }
+        }
+        distancePulled = 0f
+    }
+
+    internal fun setRefreshing(refreshing: Boolean) {
+        if (this._refreshing != refreshing) {
+            this._refreshing = refreshing
+            this.distancePulled = 0f
+            animateIndicatorTo(if (refreshing) refreshingOffset else 0f)
+        }
+    }
+
+    private fun animateIndicatorTo(offset: Float) = animationScope.launch {
+        animate(initialValue = _position, targetValue = offset) { value, _ ->
+            _position = value
+        }
+    }
+
+    private fun calculateIndicatorPosition(): Float = when {
+        // If drag hasn't gone past the threshold, the position is the adjustedDistancePulled.
+        adjustedDistancePulled <= threshold -> adjustedDistancePulled
+        else -> {
+            // How far beyond the threshold pull has gone, as a percentage of the threshold.
+            val overshootPercent = abs(progress) - 1.0f
+            // Limit the overshoot to 200%. Linear between 0 and 200.
+            val linearTension = overshootPercent.coerceIn(0f, 2f)
+            // Non-linear tension. Increases with linearTension, but at a decreasing rate.
+            val tensionPercent = linearTension - linearTension.pow(2) / 4
+            // The additional offset beyond the threshold.
+            val extraOffset = threshold * tensionPercent
+            threshold + extraOffset
+        }
+    }
+}
+
+/**
+ * Default parameter values for [rememberPullRefreshState].
+ */
+@ExperimentalMaterialApi
+object PullRefreshDefaults {
+    /**
+     * If the indicator is below this threshold offset when it is released, a refresh
+     * will be triggered.
+     */
+    val RefreshThreshold = 80.dp
+
+    /**
+     * The offset at which the indicator should be rendered whilst a refresh is occurring.
+     */
+    val RefreshingOffset = 56.dp
+}
+
+/**
+ * The distance pulled is multiplied by this value to give us the adjusted distance pulled, which
+ * is used in calculating the indicator position (when the adjusted distance pulled is less than
+ * the refresh threshold, it is the indicator position, otherwise the indicator position is
+ * derived from the progress).
+ */
+private const val DragMultiplier = 0.5f
diff --git a/compose/material3/material3/api/current.ignore b/compose/material3/material3/api/current.ignore
deleted file mode 100644
index a8bfe80..0000000
--- a/compose/material3/material3/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.material3.SafeDrawingInsets_androidKt:
-    Removed class androidx.compose.material3.SafeDrawingInsets_androidKt
diff --git a/compose/material3/material3/api/public_plus_experimental_1.0.0-beta03.txt b/compose/material3/material3/api/public_plus_experimental_1.0.0-beta03.txt
index 9ab602f..ef8d098 100644
--- a/compose/material3/material3/api/public_plus_experimental_1.0.0-beta03.txt
+++ b/compose/material3/material3/api/public_plus_experimental_1.0.0-beta03.txt
@@ -353,12 +353,15 @@
   @androidx.compose.material3.ExperimentalMaterial3Api public interface ExposedDropdownMenuBoxScope {
     method @androidx.compose.runtime.Composable public default void ExposedDropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method public androidx.compose.ui.Modifier exposedDropdownSize(androidx.compose.ui.Modifier, optional boolean matchTextFieldWidth);
+    method public androidx.compose.ui.Modifier menuAnchor(androidx.compose.ui.Modifier);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api public final class ExposedDropdownMenuDefaults {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded, optional kotlin.jvm.functions.Function0<kotlin.Unit> onIconClick);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded);
+    method public androidx.compose.foundation.layout.PaddingValues getItemContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+    property public final androidx.compose.foundation.layout.PaddingValues ItemContentPadding;
     field public static final androidx.compose.material3.ExposedDropdownMenuDefaults INSTANCE;
   }
 
diff --git a/compose/material3/material3/api/public_plus_experimental_current.txt b/compose/material3/material3/api/public_plus_experimental_current.txt
index 9ab602f..ef8d098 100644
--- a/compose/material3/material3/api/public_plus_experimental_current.txt
+++ b/compose/material3/material3/api/public_plus_experimental_current.txt
@@ -353,12 +353,15 @@
   @androidx.compose.material3.ExperimentalMaterial3Api public interface ExposedDropdownMenuBoxScope {
     method @androidx.compose.runtime.Composable public default void ExposedDropdownMenu(boolean expanded, kotlin.jvm.functions.Function0<kotlin.Unit> onDismissRequest, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.ColumnScope,kotlin.Unit> content);
     method public androidx.compose.ui.Modifier exposedDropdownSize(androidx.compose.ui.Modifier, optional boolean matchTextFieldWidth);
+    method public androidx.compose.ui.Modifier menuAnchor(androidx.compose.ui.Modifier);
   }
 
   @androidx.compose.material3.ExperimentalMaterial3Api public final class ExposedDropdownMenuDefaults {
-    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded, optional kotlin.jvm.functions.Function0<kotlin.Unit> onIconClick);
+    method @androidx.compose.material3.ExperimentalMaterial3Api @androidx.compose.runtime.Composable public void TrailingIcon(boolean expanded);
+    method public androidx.compose.foundation.layout.PaddingValues getItemContentPadding();
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors outlinedTextFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedBorderColor, optional long unfocusedBorderColor, optional long disabledBorderColor, optional long errorBorderColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
     method @androidx.compose.runtime.Composable public androidx.compose.material3.TextFieldColors textFieldColors(optional long textColor, optional long disabledTextColor, optional long containerColor, optional long cursorColor, optional long errorCursorColor, optional androidx.compose.foundation.text.selection.TextSelectionColors selectionColors, optional long focusedIndicatorColor, optional long unfocusedIndicatorColor, optional long disabledIndicatorColor, optional long errorIndicatorColor, optional long focusedLeadingIconColor, optional long unfocusedLeadingIconColor, optional long disabledLeadingIconColor, optional long errorLeadingIconColor, optional long focusedTrailingIconColor, optional long unfocusedTrailingIconColor, optional long disabledTrailingIconColor, optional long errorTrailingIconColor, optional long focusedLabelColor, optional long unfocusedLabelColor, optional long disabledLabelColor, optional long errorLabelColor, optional long placeholderColor, optional long disabledPlaceholderColor);
+    property public final androidx.compose.foundation.layout.PaddingValues ItemContentPadding;
     field public static final androidx.compose.material3.ExposedDropdownMenuDefaults INSTANCE;
   }
 
diff --git a/compose/material3/material3/api/restricted_current.ignore b/compose/material3/material3/api/restricted_current.ignore
deleted file mode 100644
index a8bfe80..0000000
--- a/compose/material3/material3/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.material3.SafeDrawingInsets_androidKt:
-    Removed class androidx.compose.material3.SafeDrawingInsets_androidKt
diff --git a/compose/material3/material3/build.gradle b/compose/material3/material3/build.gradle
index 5163b14..4e70b4d 100644
--- a/compose/material3/material3/build.gradle
+++ b/compose/material3/material3/build.gradle
@@ -44,7 +44,7 @@
         api("androidx.compose.material:material-ripple:1.0.0")
         api("androidx.compose.runtime:runtime:1.0.1")
         api("androidx.compose.ui:ui-graphics:1.0.1")
-        api("androidx.compose.ui:ui:1.1.1")
+        api(project(":compose:ui:ui"))
         // TODO: pin this to 1.3.0 stable, when PlatformTextStyle is not experimental
         api(project(":compose:ui:ui-text"))
 
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ExposedDropdownMenuSamples.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ExposedDropdownMenuSamples.kt
index 37d0ab2..52bba3c 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ExposedDropdownMenuSamples.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/ExposedDropdownMenuSamples.kt
@@ -28,6 +28,7 @@
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
 
 @OptIn(ExperimentalMaterial3Api::class)
 @Sampled
@@ -42,6 +43,8 @@
          expanded = !expanded },
     ) {
         TextField(
+            // The `menuAnchor` modifier must be passed to the text field for correctness.
+            modifier = Modifier.menuAnchor(),
             readOnly = true,
             value = selectedOptionText,
             >
@@ -59,7 +62,8 @@
                     >
                         selectedOptionText = selectionOption
                         expanded = false
-                    }
+                    },
+                    contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
                 )
             }
         }
@@ -78,6 +82,8 @@
          expanded = !expanded },
     ) {
         TextField(
+            // The `menuAnchor` modifier must be passed to the text field for correctness.
+            modifier = Modifier.menuAnchor(),
             value = selectedOptionText,
              selectedOptionText = it },
             label = { Text("Label") },
@@ -97,7 +103,8 @@
                         >
                             selectedOptionText = selectionOption
                             expanded = false
-                        }
+                        },
+                        contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
                     )
                 }
             }
diff --git a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
index cfee610..4e1f2c5 100644
--- a/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
+++ b/compose/material3/material3/samples/src/main/java/androidx/compose/material3/samples/SliderSample.kt
@@ -149,6 +149,7 @@
 fun SliderWithCustomTrackAndThumb() {
     var sliderPosition by remember { mutableStateOf(0f) }
     val interactionSource = MutableInteractionSource()
+    val colors = SliderDefaults.colors(thumbColor = Color.Red, activeTrackColor = Color.Red)
     Column {
         Text(text = sliderPosition.toString())
         Slider(
@@ -164,12 +165,12 @@
             thumb = {
                 SliderDefaults.Thumb(
                     interactionSource = interactionSource,
-                    colors = SliderDefaults.colors(thumbColor = Color.Red)
+                    colors = colors
                 )
             },
             track = { sliderPositions ->
                 SliderDefaults.Track(
-                    colors = SliderDefaults.colors(activeTrackColor = Color.Red),
+                    colors = colors,
                     sliderPositions = sliderPositions
                 )
             }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
index 198a756..42821e5 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/AppBarTest.kt
@@ -17,10 +17,13 @@
 package androidx.compose.material3
 
 import android.os.Build
+import androidx.compose.foundation.background
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.WindowInsets
 import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.wrapContentHeight
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyListState
 import androidx.compose.foundation.lazy.LazyRow
@@ -268,6 +271,33 @@
             .assertHeightIsEqualTo(TopAppBarSmallTokens.ContainerHeight - scrollHeightOffsetDp)
     }
 
+    @OptIn(ExperimentalMaterial3Api::class)
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun smallTopAppBar_transparentContainerColor() {
+        val expectedColorBehindTopAppBar: Color = Color.Red
+        rule.setMaterialContent(lightColorScheme()) {
+            Box(
+                modifier = Modifier
+                    .wrapContentHeight()
+                    .fillMaxWidth()
+                    .background(color = expectedColorBehindTopAppBar)
+            ) {
+                TopAppBar(
+                    modifier = Modifier.testTag(TopAppBarTestTag),
+                    title = {
+                        Text("Title", Modifier.testTag(TitleTestTag))
+                    },
+                    colors =
+                    TopAppBarDefaults.smallTopAppBarColors(containerColor = Color.Transparent),
+                    scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(),
+                )
+            }
+        }
+        rule.onNodeWithTag(TopAppBarTestTag).captureToImage()
+            .assertContainsColor(expectedColorBehindTopAppBar)
+    }
+
     @Test
     fun centerAlignedTopAppBar_expandsToScreen() {
         rule.setMaterialContentForSizeAssertions {
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
index 3b92a5c..6c546cb 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ExposedDropdownMenuTest.kt
@@ -57,7 +57,6 @@
     @get:Rule
     val rule = createComposeRule()
 
-    private val EDMBoxTag = "ExposedDropdownMenuBoxTag"
     private val TFTag = "TextFieldTag"
     private val TrailingIconTag = "TrailingIconTag"
     private val EDMTag = "ExposedDropdownMenuTag"
@@ -193,7 +192,7 @@
                             setContent {
                                 Box {
                                     ExposedDropdownMenuBox(expanded = true,  {
-                                        Box(Modifier.size(20.dp))
+                                        Box(Modifier.menuAnchor().size(20.dp))
                                     }
                                 }
                             }
@@ -222,12 +221,12 @@
         var selectedOptionText by remember { mutableStateOf("") }
         Box(Modifier.fillMaxSize()) {
             ExposedDropdownMenuBox(
-                modifier = Modifier.testTag(EDMBoxTag).align(Alignment.Center),
+                modifier = Modifier.align(Alignment.Center),
                 expanded = expanded,
                  onExpandChange(!expanded) }
             ) {
                 TextField(
-                    modifier = Modifier.testTag(TFTag)
+                    modifier = Modifier.menuAnchor().testTag(TFTag)
                         .onGloballyPositioned {
                             onTextFieldBoundsChanged?.invoke(it.boundsInRoot())
                         },
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
index 54a55f4..bf5bc93 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/ScaffoldTest.kt
@@ -33,6 +33,8 @@
 import androidx.compose.ui.layout.LayoutCoordinates
 import androidx.compose.ui.layout.onGloballyPositioned
 import androidx.compose.ui.layout.positionInParent
+import androidx.compose.ui.layout.positionInRoot
+import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.test.assertHeightIsEqualTo
@@ -40,6 +42,7 @@
 import androidx.compose.ui.test.captureToImage
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.unit.Density
 import androidx.compose.ui.unit.IntSize
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.toSize
@@ -48,6 +51,7 @@
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import com.google.common.truth.Truth.assertThat
+import kotlin.math.roundToInt
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -345,4 +349,73 @@
             }
         }
     }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun scaffold_insetsTests_snackbarRespectsInsets() {
+        val hostState = SnackbarHostState()
+        var snackbarSize: IntSize? = null
+        var snackbarPosition: Offset? = null
+        var density: Density? = null
+        rule.setContent {
+            Box(Modifier.requiredSize(10.dp, 20.dp)) {
+                density = LocalDensity.current
+                Scaffold(
+                    contentWindowInsets = WindowInsets(top = 5.dp, bottom = 3.dp),
+                    snackbarHost = {
+                        SnackbarHost(hostState = hostState,
+                            modifier = Modifier
+                                .onGloballyPositioned {
+                                    snackbarSize = it.size
+                                    snackbarPosition = it.positionInRoot()
+                                })
+                    }
+                ) {
+                    Box(
+                        Modifier
+                            .requiredSize(10.dp)
+                            .background(color = Color.White)
+                    )
+                }
+            }
+        }
+        val snackbarBottomOffsetDp =
+            with(density!!) { (snackbarPosition!!.y.roundToInt() + snackbarSize!!.height).toDp() }
+        assertThat(rule.rootHeight() - snackbarBottomOffsetDp - 3.dp).isLessThan(1.dp)
+    }
+
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+    @Test
+    fun scaffold_insetsTests_FabRespectsInsets() {
+        var fabSize: IntSize? = null
+        var fabPosition: Offset? = null
+        var density: Density? = null
+        rule.setContent {
+            Box(Modifier.requiredSize(10.dp, 20.dp)) {
+                density = LocalDensity.current
+                Scaffold(
+                    contentWindowInsets = WindowInsets(top = 5.dp, bottom = 3.dp),
+                    floatingActionButton = {
+                        FloatingActionButton(>
+                            modifier = Modifier
+                                .onGloballyPositioned {
+                                    fabSize = it.size
+                                    fabPosition = it.positionInRoot()
+                                }) {
+                            Text("Fab")
+                        }
+                    },
+                ) {
+                    Box(
+                        Modifier
+                            .requiredSize(10.dp)
+                            .background(color = Color.White)
+                    )
+                }
+            }
+        }
+        val fabBottomOffsetDp =
+            with(density!!) { (fabPosition!!.y.roundToInt() + fabSize!!.height).toDp() }
+        assertThat(rule.rootHeight() - fabBottomOffsetDp - 3.dp).isLessThan(1.dp)
+    }
 }
diff --git a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
index 01bdce2..173c217 100644
--- a/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
+++ b/compose/material3/material3/src/androidAndroidTest/kotlin/androidx/compose/material3/SliderTest.kt
@@ -30,7 +30,10 @@
 import androidx.compose.foundation.layout.requiredSize
 import androidx.compose.foundation.layout.width
 import androidx.compose.material3.tokens.SliderTokens
+import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionLocalProvider
+import androidx.compose.runtime.SideEffect
+import androidx.compose.runtime.Stable
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.rememberCoroutineScope
@@ -639,6 +642,62 @@
 
     @OptIn(ExperimentalMaterial3Api::class)
     @Test
+    fun slider_thumb_recomposition() {
+        val state = mutableStateOf(0f)
+        val recompositionCounter = SliderRecompositionCounter()
+
+        rule.setContent {
+            Slider(
+                modifier = Modifier.testTag(tag),
+                value = state.value,
+                 state.value = it },
+                thumb = { sliderPositions -> recompositionCounter.OuterContent(sliderPositions) }
+            )
+        }
+
+        rule.onNodeWithTag(tag)
+            .performTouchInput {
+                down(center)
+                moveBy(Offset(100f, 0f))
+                moveBy(Offset(-100f, 0f))
+                moveBy(Offset(100f, 0f))
+            }
+        rule.runOnIdle {
+            Truth.assertThat(recompositionCounter.outerRecomposition).isEqualTo(1)
+            Truth.assertThat(recompositionCounter.innerRecomposition).isEqualTo(4)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
+    fun slider_track_recomposition() {
+        val state = mutableStateOf(0f)
+        val recompositionCounter = SliderRecompositionCounter()
+
+        rule.setContent {
+            Slider(
+                modifier = Modifier.testTag(tag),
+                value = state.value,
+                 state.value = it },
+                track = { sliderPositions -> recompositionCounter.OuterContent(sliderPositions) }
+            )
+        }
+
+        rule.onNodeWithTag(tag)
+            .performTouchInput {
+                down(center)
+                moveBy(Offset(100f, 0f))
+                moveBy(Offset(-100f, 0f))
+                moveBy(Offset(100f, 0f))
+            }
+        rule.runOnIdle {
+            Truth.assertThat(recompositionCounter.outerRecomposition).isEqualTo(1)
+            Truth.assertThat(recompositionCounter.innerRecomposition).isEqualTo(4)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Test
     fun rangeSlider_dragThumb() {
         val state = mutableStateOf(0f..1f)
         var slop = 0f
@@ -1093,4 +1152,27 @@
         rule.onAllNodes(isFocusable(), true)[1]
             .assertRangeInfoEquals(ProgressBarRangeInfo(15f, 10f..20f, 1))
     }
+}
+
+@Stable
+class SliderRecompositionCounter {
+    var innerRecomposition = 0
+    var outerRecomposition = 0
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    fun OuterContent(sliderPositions: SliderPositions) {
+        SideEffect { ++outerRecomposition }
+        Column {
+            Text("OuterContent")
+            InnerContent(sliderPositions)
+        }
+    }
+
+    @OptIn(ExperimentalMaterial3Api::class)
+    @Composable
+    private fun InnerContent(sliderPositions: SliderPositions) {
+        SideEffect { ++innerRecomposition }
+        Text("InnerContent: ${sliderPositions.positionFraction}")
+    }
 }
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
index f8b334b..e270215 100644
--- a/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
+++ b/compose/material3/material3/src/androidMain/kotlin/androidx/compose/material3/ExposedDropdownMenu.kt
@@ -23,6 +23,7 @@
 import androidx.compose.foundation.gestures.forEachGesture
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.ColumnScope
+import androidx.compose.foundation.layout.PaddingValues
 import androidx.compose.foundation.layout.heightIn
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.text.selection.LocalTextSelectionColors
@@ -40,6 +41,7 @@
 import androidx.compose.runtime.remember
 import androidx.compose.runtime.setValue
 import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
 import androidx.compose.ui.draw.rotate
 import androidx.compose.ui.focus.FocusRequester
 import androidx.compose.ui.focus.focusRequester
@@ -55,11 +57,13 @@
 import androidx.compose.ui.node.Ref
 import androidx.compose.ui.platform.LocalDensity
 import androidx.compose.ui.platform.LocalView
-import androidx.compose.ui.semantics.clearAndSetSemantics
+import androidx.compose.ui.platform.debugInspectorInfo
 import androidx.compose.ui.semantics.contentDescription
 import androidx.compose.ui.semantics.onClick
 import androidx.compose.ui.semantics.semantics
+import androidx.compose.ui.semantics.stateDescription
 import androidx.compose.ui.unit.DpOffset
+import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.fastAll
 import kotlin.math.max
 import kotlinx.coroutines.coroutineScope
@@ -70,10 +74,10 @@
  * Menus display a list of choices on a temporary surface. They appear when users interact with a
  * button, action, or other control.
  *
- * Exposed dropdown menus display the currently selected item in a text field above the menu. In
- * some cases, it can accept and display user input (whether or not it’s listed as a menu choice).
- * If the text field input is used to filter results in the menu, the component is also known as
- * "autocomplete" or a "combobox".
+ * Exposed dropdown menus display the currently selected item in a text field to which the menu is
+ * anchored. In some cases, it can accept and display user input (whether or not it’s listed as a
+ * menu choice). If the text field input is used to filter results in the menu, the component is
+ * also known as "autocomplete" or a "combobox".
  *
  * ![Exposed dropdown menu image](https://developer.android.com/images/reference/androidx/compose/material3/exposed-dropdown-menu.png)
  *
@@ -90,8 +94,9 @@
  * @param onExpandedChange called when the exposed dropdown menu is clicked and the expansion state
  * changes.
  * @param modifier the [Modifier] to be applied to this exposed dropdown menu
- * @param content the content of this exposed dropdown menu, typically a [TextField] and a
- * [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]
+ * @param content the content of this exposed dropdown menu, typically a [TextField] and an
+ * [ExposedDropdownMenuBoxScope.ExposedDropdownMenu]. The [TextField] within [content] should be
+ * passed the [ExposedDropdownMenuBoxScope.menuAnchor] modifier for proper menu behavior.
  */
 @ExperimentalMaterial3Api
 @Composable
@@ -108,37 +113,39 @@
     val verticalMarginInPx = with(density) { MenuVerticalMargin.roundToPx() }
     val coordinates = remember { Ref<LayoutCoordinates>() }
 
-    val scope = remember(density, menuHeight, width) {
+    val focusRequester = remember { FocusRequester() }
+
+    val scope = remember(expanded, onExpandedChange, density, menuHeight, width) {
         object : ExposedDropdownMenuBoxScope {
+            override fun Modifier.menuAnchor(): Modifier {
+                return composed(inspectorInfo = debugInspectorInfo { name = "menuAnchor" }) {
+                    onGloballyPositioned {
+                        width = it.size.width
+                        coordinates.value = it
+                        updateHeight(view.rootView, coordinates.value, verticalMarginInPx) {
+                            newHeight -> menuHeight = newHeight
+                        }
+                    }.expandable(
+                        expanded = expanded,
+                         onExpandedChange(!expanded) },
+                    ).focusRequester(focusRequester)
+                }
+            }
             override fun Modifier.exposedDropdownSize(matchTextFieldWidth: Boolean): Modifier {
                 return with(density) {
                     heightIn(max = menuHeight.toDp()).let {
                         if (matchTextFieldWidth) {
                             it.width(width.toDp())
-                        } else it
+                        } else {
+                            it
+                        }
                     }
                 }
             }
         }
     }
-    val focusRequester = remember { FocusRequester() }
 
-    Box(
-        modifier.onGloballyPositioned {
-            width = it.size.width
-            coordinates.value = it
-            updateHeight(
-                view.rootView,
-                coordinates.value,
-                verticalMarginInPx
-            ) { newHeight ->
-                menuHeight = newHeight
-            }
-        }.expandable(
-             onExpandedChange(!expanded) },
-            menuLabel = getString(Strings.ExposedDropdownMenu)
-        ).focusRequester(focusRequester)
-    ) {
+    Box(modifier) {
         scope.content()
     }
 
@@ -149,11 +156,7 @@
     DisposableEffect(view) {
         val listener = OnGlobalLayoutListener(view) {
             // We want to recalculate the menu height on relayout - e.g. when keyboard shows up.
-            updateHeight(
-                view.rootView,
-                coordinates.value,
-                verticalMarginInPx
-            ) { newHeight ->
+            updateHeight(view.rootView, coordinates.value, verticalMarginInPx) { newHeight ->
                 menuHeight = newHeight
             }
         }
@@ -206,6 +209,13 @@
 @ExperimentalMaterial3Api
 interface ExposedDropdownMenuBoxScope {
     /**
+     * Modifier which should be applied to a [TextField] (or [OutlinedTextField]) placed inside the
+     * scope. It's responsible for properly anchoring the [ExposedDropdownMenu], handling semantics
+     * of the component, and requesting focus.
+     */
+    fun Modifier.menuAnchor(): Modifier
+
+    /**
      * Modifier which should be applied to an [ExposedDropdownMenu] placed inside the scope. It's
      * responsible for setting the width of the [ExposedDropdownMenu], which will match the width of
      * the [TextField] (if [matchTextFieldWidth] is set to true). It will also change the height of
@@ -254,8 +264,8 @@
             val popupPositionProvider = DropdownMenuPositionProvider(
                 DpOffset.Zero,
                 density
-            ) { parentBounds, menuBounds ->
-                transformOriginState.value = calculateTransformOrigin(parentBounds, menuBounds)
+            ) { anchorBounds, menuBounds ->
+                transformOriginState.value = calculateTransformOrigin(anchorBounds, menuBounds)
             }
 
             ExposedDropdownMenuPopup(
@@ -283,30 +293,15 @@
      *
      * @param expanded whether [ExposedDropdownMenuBoxScope.ExposedDropdownMenu] is expanded or not.
      * Affects the appearance of the icon.
-     * @param onIconClick called when the icon is clicked
      */
     @ExperimentalMaterial3Api
     @Composable
-    fun TrailingIcon(
-        expanded: Boolean,
-        onIconClick: () -> Unit = {}
-    ) {
-        // Clear semantics here as otherwise icon will be a11y focusable but without an
-        // action. When there's an API to check if Talkback is on, developer will be able to
-        // expand the menu on icon click in a11y mode only esp. if using their own custom
-        // trailing icon.
-        IconButton( modifier = Modifier.clearAndSetSemantics { }) {
-            Icon(
-                Icons.Filled.ArrowDropDown,
-                "Trailing icon for exposed dropdown menu",
-                Modifier.rotate(
-                    if (expanded)
-                        180f
-                    else
-                        360f
-                )
-            )
-        }
+    fun TrailingIcon(expanded: Boolean) {
+        Icon(
+            Icons.Filled.ArrowDropDown,
+            null,
+            Modifier.rotate(if (expanded) 180f else 0f)
+        )
     }
 
     /**
@@ -512,11 +507,25 @@
             placeholderColor = placeholderColor,
             disabledPlaceholderColor = disabledPlaceholderColor
         )
+
+    /**
+     * Padding for [DropdownMenuItem]s within [ExposedDropdownMenuBoxScope.ExposedDropdownMenu] to
+     * align them properly with [TextField] components.
+     */
+    val ItemContentPadding: PaddingValues = PaddingValues(
+        horizontal = ExposedDropdownMenuItemHorizontalPadding,
+        vertical = 0.dp
+    )
 }
 
+@Suppress("ComposableModifierFactory")
+@Composable
 private fun Modifier.expandable(
+    expanded: Boolean,
     onExpandedChange: () -> Unit,
-    menuLabel: String
+    menuDescription: String = getString(Strings.ExposedDropdownMenu),
+    expandedDescription: String = getString(Strings.MenuExpanded),
+    collapsedDescription: String = getString(Strings.MenuCollapsed),
 ) = pointerInput(Unit) {
     forEachGesture {
         coroutineScope {
@@ -527,12 +536,13 @@
                 } while (
                     !event.changes.fastAll { it.changedToUp() }
                 )
-                onExpandedChange.invoke()
+                onExpandedChange()
             }
         }
     }
 }.semantics {
-    contentDescription = menuLabel // this should be a localised string
+    stateDescription = if (expanded) expandedDescription else collapsedDescription
+    contentDescription = menuDescription
     onClick {
         onExpandedChange()
         true
@@ -555,3 +565,5 @@
         visibleWindowBounds.bottom - visibleWindowBounds.top - coordinates.boundsInWindow().bottom
     onHeightUpdate(max(heightAbove, heightBelow).toInt() - verticalMarginInPx)
 }
+
+private val ExposedDropdownMenuItemHorizontalPadding = 16.dp
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 a88443e..1452240 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
@@ -34,6 +34,8 @@
         Strings.SliderRangeStart -> resources.getString(R.string.range_start)
         Strings.SliderRangeEnd -> resources.getString(R.string.range_end)
         Strings.Dialog -> resources.getString(androidx.compose.material3.R.string.dialog)
+        Strings.MenuExpanded -> resources.getString(androidx.compose.material3.R.string.expanded)
+        Strings.MenuCollapsed -> resources.getString(androidx.compose.material3.R.string.collapsed)
         else -> ""
     }
 }
\ No newline at end of file
diff --git a/compose/material3/material3/src/androidMain/res/values/strings.xml b/compose/material3/material3/src/androidMain/res/values/strings.xml
index 5938eaa..ddb6526 100644
--- a/compose/material3/material3/src/androidMain/res/values/strings.xml
+++ b/compose/material3/material3/src/androidMain/res/values/strings.xml
@@ -18,4 +18,8 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android" xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <!-- Spoken description of a dialog -->
     <string name="dialog">"Dialog"</string>
+    <!-- Spoken description of expanded state of an expandable item -->
+    <string name="expanded">Expanded</string>
+    <!-- Spoken description of collapsed state of an expandable item -->
+    <string name="collapsed">Collapsed</string>
 </resources>
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
index b41fb99..af531f7 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/AppBar.kt
@@ -177,7 +177,7 @@
         "TopAppBar(title, modifier, navigationIcon, actions, windowInsets, colors, " +
             "scrollBehavior)"
     ),
-    level = DeprecationLevel.ERROR
+    level = DeprecationLevel.WARNING
 )
 @ExperimentalMaterial3Api
 @Composable
@@ -1045,14 +1045,13 @@
     // The surface's background color is animated as specified above.
     // The height of the app bar is determined by subtracting the bar's height offset from the
     // app bar's defined constant height value (i.e. the ContainerHeight token).
-    Surface(modifier = modifier.then(appBarDragModifier)) {
+    Surface(modifier = modifier.then(appBarDragModifier), color = appBarContainerColor) {
         val height = LocalDensity.current.run {
             TopAppBarSmallTokens.ContainerHeight.toPx() + (scrollBehavior?.state?.heightOffset
                 ?: 0f)
         }
         TopAppBarLayout(
             modifier = Modifier
-                .background(color = appBarContainerColor)
                 .windowInsetsPadding(windowInsets)
                 // clip after padding so we don't show the title over the inset area
                 .clipToBounds(),
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
index cbad81d..52d652c 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Scaffold.kt
@@ -30,6 +30,7 @@
 import androidx.compose.ui.layout.SubcomposeLayout
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
+import androidx.compose.ui.unit.offset
 
 /**
  * <a href="https://material.io/design/layout/understanding-layout.html" class="external" target="_blank">Material Design layout</a>.
@@ -134,7 +135,19 @@
             val topBarHeight = topBarPlaceables.maxByOrNull { it.height }?.height ?: 0
 
             val snackbarPlaceables = subcompose(ScaffoldLayoutContent.Snackbar, snackbar).map {
-                it.measure(looseConstraints)
+                // respect only bottom and horizontal for snackbar and fab
+                val leftInset = contentWindowInsets
+                    .getLeft(this@SubcomposeLayout, layoutDirection)
+                val rightInset = contentWindowInsets
+                    .getRight(this@SubcomposeLayout, layoutDirection)
+                val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                // offset the snackbar constraints by the insets values
+                it.measure(
+                    looseConstraints.offset(
+                        -leftInset - rightInset,
+                        -bottomInset
+                    )
+                )
             }
 
             val snackbarHeight = snackbarPlaceables.maxByOrNull { it.height }?.height ?: 0
@@ -142,7 +155,19 @@
 
             val fabPlaceables =
                 subcompose(ScaffoldLayoutContent.Fab, fab).mapNotNull { measurable ->
-                    measurable.measure(looseConstraints).takeIf { it.height != 0 && it.width != 0 }
+                    // respect only bottom and horizontal for snackbar and fab
+                    val leftInset =
+                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection)
+                    val rightInset =
+                        contentWindowInsets.getRight(this@SubcomposeLayout, layoutDirection)
+                    val bottomInset = contentWindowInsets.getBottom(this@SubcomposeLayout)
+                    measurable.measure(
+                        looseConstraints.offset(
+                            -leftInset - rightInset,
+                            -bottomInset
+                        )
+                    )
+                        .takeIf { it.height != 0 && it.width != 0 }
                 }
 
             val fabPlacement = if (fabPlaceables.isNotEmpty()) {
@@ -175,10 +200,11 @@
                 )
             }.map { it.measure(looseConstraints) }
 
-            val bottomBarHeight = bottomBarPlaceables.maxByOrNull { it.height }?.height ?: 0
+            val bottomBarHeight = bottomBarPlaceables.maxByOrNull { it.height }?.height
             val fabOffsetFromBottom = fabPlacement?.let {
-                if (bottomBarHeight == 0) {
-                    it.height + FabSpacing.roundToPx()
+                if (bottomBarHeight == null) {
+                    it.height + FabSpacing.roundToPx() +
+                        contentWindowInsets.getBottom(this@SubcomposeLayout)
                 } else {
                     // Total height is the bottom bar height + the FAB height + the padding
                     // between the FAB and bottom bar
@@ -187,7 +213,9 @@
             }
 
             val snackbarOffsetFromBottom = if (snackbarHeight != 0) {
-                snackbarHeight + (fabOffsetFromBottom ?: bottomBarHeight)
+                snackbarHeight +
+                    (fabOffsetFromBottom ?: bottomBarHeight
+                    ?: contentWindowInsets.getBottom(this@SubcomposeLayout))
             } else {
                 0
             }
@@ -202,7 +230,7 @@
                         topBarHeight.toDp()
                     },
                     bottom =
-                    if (bottomBarPlaceables.isEmpty()) {
+                    if (bottomBarPlaceables.isEmpty() || bottomBarHeight == null) {
                         insets.calculateBottomPadding()
                     } else {
                         bottomBarHeight.toDp()
@@ -223,13 +251,14 @@
             }
             snackbarPlaceables.forEach {
                 it.place(
-                    (layoutWidth - snackbarWidth) / 2,
+                    (layoutWidth - snackbarWidth) / 2 +
+                        contentWindowInsets.getLeft(this@SubcomposeLayout, layoutDirection),
                     layoutHeight - snackbarOffsetFromBottom
                 )
             }
             // The bottom bar is always at the bottom of the layout
             bottomBarPlaceables.forEach {
-                it.place(0, layoutHeight - bottomBarHeight)
+                it.place(0, layoutHeight - (bottomBarHeight ?: 0))
             }
             // Explicitly not using placeRelative here as `leftOffset` already accounts for RTL
             fabPlacement?.let { placement ->
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
index b76ab43..61299ed 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/Slider.kt
@@ -173,20 +173,20 @@
         >
         colors = colors,
         interactionSource = interactionSource,
-        thumb = {
+        thumb = remember(interactionSource, colors, enabled) { {
             SliderDefaults.Thumb(
                 interactionSource = interactionSource,
                 colors = colors,
                 enabled = enabled
             )
-        },
-        track = { sliderPositions ->
+        } },
+        track = remember(colors, enabled) { { sliderPositions ->
             SliderDefaults.Track(
                 colors = colors,
                 enabled = enabled,
                 sliderPositions = sliderPositions
             )
-        }
+        } }
     )
 }
 
@@ -256,13 +256,13 @@
         colors = colors,
         interactionSource = interactionSource,
         thumb = thumb,
-        track = { sliderPositions ->
+        track = remember(colors, enabled) { { sliderPositions ->
             SliderDefaults.Track(
                 colors = colors,
                 enabled = enabled,
                 sliderPositions = sliderPositions
             )
-        }
+        } }
     )
 }
 
@@ -321,13 +321,14 @@
     onValueChangeFinished: (() -> Unit)? = null,
     colors: SliderColors = SliderDefaults.colors(),
     interactionSource: MutableInteractionSource = remember { MutableInteractionSource() },
-    thumb: @Composable (SliderPositions) -> Unit = {
-        SliderDefaults.Thumb(
-            colors = colors,
-            enabled = enabled,
-            interactionSource = interactionSource
-        )
-    }
+    thumb: @Composable (SliderPositions) -> Unit =
+        remember(interactionSource, colors, enabled) { {
+            SliderDefaults.Thumb(
+                interactionSource = interactionSource,
+                colors = colors,
+                enabled = enabled
+            )
+        } }
 ) {
     require(steps >= 0) { "steps should be >= 0" }
 
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 e491a5d..ce93d56f 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
@@ -31,6 +31,8 @@
         val SliderRangeStart = Strings(5)
         val SliderRangeEnd = Strings(6)
         val Dialog = Strings(7)
+        val MenuExpanded = Strings(8)
+        val MenuCollapsed = Strings(9)
     }
 }
 
diff --git a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ColorDarkTokens.kt b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ColorDarkTokens.kt
index 82a7273..5487613 100644
--- a/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ColorDarkTokens.kt
+++ b/compose/material3/material3/src/commonMain/kotlin/androidx/compose/material3/tokens/ColorDarkTokens.kt
@@ -13,7 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-// VERSION: v0_103
+// VERSION: v0_126
 // GENERATED CODE - DO NOT MODIFY BY HAND
 
 package androidx.compose.material3.tokens
@@ -27,7 +27,7 @@
     val InverseSurface = PaletteTokens.Neutral90
     val >
     val >
-    val >
+    val >
     val >
     val >
     val >
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 bcd0170..99f6608 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
@@ -28,6 +28,8 @@
         Strings.SliderRangeStart -> "Range Start"
         Strings.SliderRangeEnd -> "Range End"
         Strings.Dialog -> "Dialog"
+        Strings.MenuExpanded -> "Expanded"
+        Strings.MenuCollapsed -> "Collapsed"
         else -> ""
     }
 }
\ No newline at end of file
diff --git a/compose/runtime/runtime/api/current.ignore b/compose/runtime/runtime/api/current.ignore
deleted file mode 100644
index 74190e7..0000000
--- a/compose/runtime/runtime/api/current.ignore
+++ /dev/null
@@ -1,165 +0,0 @@
-// Baseline format: 1.0
-AddSealed: androidx.compose.runtime.Composer:
-    Cannot add 'sealed' modifier to class androidx.compose.runtime.Composer: Incompatible change
-AddSealed: androidx.compose.runtime.ControlledComposition:
-    Cannot add 'sealed' modifier to class androidx.compose.runtime.ControlledComposition: Incompatible change
-
-
-AddedAbstractMethod: androidx.compose.runtime.Composer#deactivateToEndGroup(boolean):
-    Added method androidx.compose.runtime.Composer.deactivateToEndGroup(boolean)
-AddedAbstractMethod: androidx.compose.runtime.Composer#getRecomposeScopeIdentity():
-    Added method androidx.compose.runtime.Composer.getRecomposeScopeIdentity()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#applyLateChanges():
-    Added method androidx.compose.runtime.ControlledComposition.applyLateChanges()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#changesApplied():
-    Added method androidx.compose.runtime.ControlledComposition.changesApplied()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#delegateInvalidations(androidx.compose.runtime.ControlledComposition, int, kotlin.jvm.functions.Function0<? extends R>):
-    Added method androidx.compose.runtime.ControlledComposition.delegateInvalidations(androidx.compose.runtime.ControlledComposition,int,kotlin.jvm.functions.Function0<? extends R>)
-
-
-AddedFinal: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
-    Method androidx.compose.runtime.Composer.apply has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(Object):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(boolean):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(byte):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(char):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(double):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(float):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(int):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(long):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(short):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#collectParameterInformation():
-    Method androidx.compose.runtime.Composer.collectParameterInformation has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
-    Method androidx.compose.runtime.Composer.createNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#disableReusing():
-    Method androidx.compose.runtime.Composer.disableReusing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#enableReusing():
-    Method androidx.compose.runtime.Composer.enableReusing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endDefaults():
-    Method androidx.compose.runtime.Composer.endDefaults has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endMovableGroup():
-    Method androidx.compose.runtime.Composer.endMovableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endNode():
-    Method androidx.compose.runtime.Composer.endNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endReplaceableGroup():
-    Method androidx.compose.runtime.Composer.endReplaceableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endRestartGroup():
-    Method androidx.compose.runtime.Composer.endRestartGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endReusableGroup():
-    Method androidx.compose.runtime.Composer.endReusableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getApplier():
-    Method androidx.compose.runtime.Composer.getApplier has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getApplyCoroutineContext():
-    Method androidx.compose.runtime.Composer.getApplyCoroutineContext has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getComposition():
-    Method androidx.compose.runtime.Composer.getComposition has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getCompositionData():
-    Method androidx.compose.runtime.Composer.getCompositionData has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getCompoundKeyHash():
-    Method androidx.compose.runtime.Composer.getCompoundKeyHash has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getDefaultsInvalid():
-    Method androidx.compose.runtime.Composer.getDefaultsInvalid has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getInserting():
-    Method androidx.compose.runtime.Composer.getInserting has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getRecomposeScope():
-    Method androidx.compose.runtime.Composer.getRecomposeScope has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getSkipping():
-    Method androidx.compose.runtime.Composer.getSkipping has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#joinKey(Object, Object):
-    Method androidx.compose.runtime.Composer.joinKey has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#rememberedValue():
-    Method androidx.compose.runtime.Composer.rememberedValue has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#skipCurrentGroup():
-    Method androidx.compose.runtime.Composer.skipCurrentGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#skipToGroupEnd():
-    Method androidx.compose.runtime.Composer.skipToGroupEnd has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformation(String):
-    Method androidx.compose.runtime.Composer.sourceInformation has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
-    Method androidx.compose.runtime.Composer.sourceInformationMarkerEnd has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
-    Method androidx.compose.runtime.Composer.sourceInformationMarkerStart has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startDefaults():
-    Method androidx.compose.runtime.Composer.startDefaults has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
-    Method androidx.compose.runtime.Composer.startMovableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startNode():
-    Method androidx.compose.runtime.Composer.startNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReplaceableGroup(int):
-    Method androidx.compose.runtime.Composer.startReplaceableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startRestartGroup(int):
-    Method androidx.compose.runtime.Composer.startRestartGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
-    Method androidx.compose.runtime.Composer.startReusableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReusableNode():
-    Method androidx.compose.runtime.Composer.startReusableNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#updateRememberedValue(Object):
-    Method androidx.compose.runtime.Composer.updateRememberedValue has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#useNode():
-    Method androidx.compose.runtime.Composer.useNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#applyChanges():
-    Method androidx.compose.runtime.ControlledComposition.applyChanges has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#composeContent(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Method androidx.compose.runtime.ControlledComposition.composeContent has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#getHasPendingChanges():
-    Method androidx.compose.runtime.ControlledComposition.getHasPendingChanges has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#invalidateAll():
-    Method androidx.compose.runtime.ControlledComposition.invalidateAll has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#isComposing():
-    Method androidx.compose.runtime.ControlledComposition.isComposing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#observesAnyOf(java.util.Set<?>):
-    Method androidx.compose.runtime.ControlledComposition.observesAnyOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#prepareCompose(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Method androidx.compose.runtime.ControlledComposition.prepareCompose has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recompose():
-    Method androidx.compose.runtime.ControlledComposition.recompose has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordModificationsOf(java.util.Set<?>):
-    Method androidx.compose.runtime.ControlledComposition.recordModificationsOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordReadOf(Object):
-    Method androidx.compose.runtime.ControlledComposition.recordReadOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordWriteOf(Object):
-    Method androidx.compose.runtime.ControlledComposition.recordWriteOf has added 'final' qualifier
-
-
-ParameterNameChange: androidx.compose.runtime.AbstractApplier#setCurrent(T) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.AbstractApplier.setCurrent
-ParameterNameChange: androidx.compose.runtime.BroadcastFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.BroadcastFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameMillis
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameMillis
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MutableState#setValue(T) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.MutableState.setValue
-ParameterNameChange: androidx.compose.runtime.PausableMonotonicFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.PausableMonotonicFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.ProduceStateScope#awaitDispose(kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.coroutines.Continuation<?>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.ProduceStateScope.awaitDispose
-ParameterNameChange: androidx.compose.runtime.Recomposer#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.awaitIdle
-ParameterNameChange: androidx.compose.runtime.Recomposer#join(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.join
-ParameterNameChange: androidx.compose.runtime.Recomposer#runRecomposeAndApplyChanges(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.runRecomposeAndApplyChanges
-ParameterNameChange: androidx.compose.runtime.RecomposerKt#withRunningRecomposer(kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.runtime.Recomposer,? super kotlin.coroutines.Continuation<? super R>,?>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.RecomposerKt.withRunningRecomposer
-
-
-RemovedMethod: androidx.compose.runtime.SkippableUpdater#SkippableUpdater():
-    Removed constructor androidx.compose.runtime.SkippableUpdater()
-RemovedMethod: androidx.compose.runtime.Updater#Updater():
-    Removed constructor androidx.compose.runtime.Updater()
diff --git a/compose/runtime/runtime/api/restricted_1.3.0-beta03.txt b/compose/runtime/runtime/api/restricted_1.3.0-beta03.txt
index f82be3b..b50e6f8 100644
--- a/compose/runtime/runtime/api/restricted_1.3.0-beta03.txt
+++ b/compose/runtime/runtime/api/restricted_1.3.0-beta03.txt
@@ -902,6 +902,7 @@
 
   public final class SnapshotKt {
     method @kotlin.PublishedApi internal static <T extends androidx.compose.runtime.snapshots.StateRecord> T current(T r, androidx.compose.runtime.snapshots.Snapshot snapshot);
+    method @kotlin.PublishedApi internal static <T extends androidx.compose.runtime.snapshots.StateRecord> T current(T r);
     method @kotlin.PublishedApi internal static void notifyWrite(androidx.compose.runtime.snapshots.Snapshot snapshot, androidx.compose.runtime.snapshots.StateObject state);
     method public static <T extends androidx.compose.runtime.snapshots.StateRecord> T readable(T, androidx.compose.runtime.snapshots.StateObject state);
     method public static <T extends androidx.compose.runtime.snapshots.StateRecord> T readable(T, androidx.compose.runtime.snapshots.StateObject state, androidx.compose.runtime.snapshots.Snapshot snapshot);
diff --git a/compose/runtime/runtime/api/restricted_current.ignore b/compose/runtime/runtime/api/restricted_current.ignore
deleted file mode 100644
index 74190e7..0000000
--- a/compose/runtime/runtime/api/restricted_current.ignore
+++ /dev/null
@@ -1,165 +0,0 @@
-// Baseline format: 1.0
-AddSealed: androidx.compose.runtime.Composer:
-    Cannot add 'sealed' modifier to class androidx.compose.runtime.Composer: Incompatible change
-AddSealed: androidx.compose.runtime.ControlledComposition:
-    Cannot add 'sealed' modifier to class androidx.compose.runtime.ControlledComposition: Incompatible change
-
-
-AddedAbstractMethod: androidx.compose.runtime.Composer#deactivateToEndGroup(boolean):
-    Added method androidx.compose.runtime.Composer.deactivateToEndGroup(boolean)
-AddedAbstractMethod: androidx.compose.runtime.Composer#getRecomposeScopeIdentity():
-    Added method androidx.compose.runtime.Composer.getRecomposeScopeIdentity()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#applyLateChanges():
-    Added method androidx.compose.runtime.ControlledComposition.applyLateChanges()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#changesApplied():
-    Added method androidx.compose.runtime.ControlledComposition.changesApplied()
-AddedAbstractMethod: androidx.compose.runtime.ControlledComposition#delegateInvalidations(androidx.compose.runtime.ControlledComposition, int, kotlin.jvm.functions.Function0<? extends R>):
-    Added method androidx.compose.runtime.ControlledComposition.delegateInvalidations(androidx.compose.runtime.ControlledComposition,int,kotlin.jvm.functions.Function0<? extends R>)
-
-
-AddedFinal: androidx.compose.runtime.Composer#apply(V, kotlin.jvm.functions.Function2<? super T,? super V,kotlin.Unit>):
-    Method androidx.compose.runtime.Composer.apply has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(Object):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(boolean):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(byte):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(char):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(double):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(float):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(int):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(long):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#changed(short):
-    Method androidx.compose.runtime.Composer.changed has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#collectParameterInformation():
-    Method androidx.compose.runtime.Composer.collectParameterInformation has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#createNode(kotlin.jvm.functions.Function0<? extends T>):
-    Method androidx.compose.runtime.Composer.createNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#disableReusing():
-    Method androidx.compose.runtime.Composer.disableReusing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#enableReusing():
-    Method androidx.compose.runtime.Composer.enableReusing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endDefaults():
-    Method androidx.compose.runtime.Composer.endDefaults has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endMovableGroup():
-    Method androidx.compose.runtime.Composer.endMovableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endNode():
-    Method androidx.compose.runtime.Composer.endNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endReplaceableGroup():
-    Method androidx.compose.runtime.Composer.endReplaceableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endRestartGroup():
-    Method androidx.compose.runtime.Composer.endRestartGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#endReusableGroup():
-    Method androidx.compose.runtime.Composer.endReusableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getApplier():
-    Method androidx.compose.runtime.Composer.getApplier has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getApplyCoroutineContext():
-    Method androidx.compose.runtime.Composer.getApplyCoroutineContext has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getComposition():
-    Method androidx.compose.runtime.Composer.getComposition has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getCompositionData():
-    Method androidx.compose.runtime.Composer.getCompositionData has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getCompoundKeyHash():
-    Method androidx.compose.runtime.Composer.getCompoundKeyHash has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getDefaultsInvalid():
-    Method androidx.compose.runtime.Composer.getDefaultsInvalid has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getInserting():
-    Method androidx.compose.runtime.Composer.getInserting has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getRecomposeScope():
-    Method androidx.compose.runtime.Composer.getRecomposeScope has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#getSkipping():
-    Method androidx.compose.runtime.Composer.getSkipping has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#joinKey(Object, Object):
-    Method androidx.compose.runtime.Composer.joinKey has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#rememberedValue():
-    Method androidx.compose.runtime.Composer.rememberedValue has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#skipCurrentGroup():
-    Method androidx.compose.runtime.Composer.skipCurrentGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#skipToGroupEnd():
-    Method androidx.compose.runtime.Composer.skipToGroupEnd has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformation(String):
-    Method androidx.compose.runtime.Composer.sourceInformation has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformationMarkerEnd():
-    Method androidx.compose.runtime.Composer.sourceInformationMarkerEnd has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#sourceInformationMarkerStart(int, String):
-    Method androidx.compose.runtime.Composer.sourceInformationMarkerStart has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startDefaults():
-    Method androidx.compose.runtime.Composer.startDefaults has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startMovableGroup(int, Object):
-    Method androidx.compose.runtime.Composer.startMovableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startNode():
-    Method androidx.compose.runtime.Composer.startNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReplaceableGroup(int):
-    Method androidx.compose.runtime.Composer.startReplaceableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startRestartGroup(int):
-    Method androidx.compose.runtime.Composer.startRestartGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReusableGroup(int, Object):
-    Method androidx.compose.runtime.Composer.startReusableGroup has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#startReusableNode():
-    Method androidx.compose.runtime.Composer.startReusableNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#updateRememberedValue(Object):
-    Method androidx.compose.runtime.Composer.updateRememberedValue has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.Composer#useNode():
-    Method androidx.compose.runtime.Composer.useNode has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#applyChanges():
-    Method androidx.compose.runtime.ControlledComposition.applyChanges has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#composeContent(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Method androidx.compose.runtime.ControlledComposition.composeContent has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#getHasPendingChanges():
-    Method androidx.compose.runtime.ControlledComposition.getHasPendingChanges has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#invalidateAll():
-    Method androidx.compose.runtime.ControlledComposition.invalidateAll has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#isComposing():
-    Method androidx.compose.runtime.ControlledComposition.isComposing has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#observesAnyOf(java.util.Set<?>):
-    Method androidx.compose.runtime.ControlledComposition.observesAnyOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#prepareCompose(kotlin.jvm.functions.Function0<kotlin.Unit>):
-    Method androidx.compose.runtime.ControlledComposition.prepareCompose has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recompose():
-    Method androidx.compose.runtime.ControlledComposition.recompose has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordModificationsOf(java.util.Set<?>):
-    Method androidx.compose.runtime.ControlledComposition.recordModificationsOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordReadOf(Object):
-    Method androidx.compose.runtime.ControlledComposition.recordReadOf has added 'final' qualifier
-AddedFinal: androidx.compose.runtime.ControlledComposition#recordWriteOf(Object):
-    Method androidx.compose.runtime.ControlledComposition.recordWriteOf has added 'final' qualifier
-
-
-ParameterNameChange: androidx.compose.runtime.AbstractApplier#setCurrent(T) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.AbstractApplier.setCurrent
-ParameterNameChange: androidx.compose.runtime.BroadcastFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.BroadcastFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameMillis(androidx.compose.runtime.MonotonicFrameClock, kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameMillis
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameMillis(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameMillis
-ParameterNameChange: androidx.compose.runtime.MonotonicFrameClockKt#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.MonotonicFrameClockKt.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.MutableState#setValue(T) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.MutableState.setValue
-ParameterNameChange: androidx.compose.runtime.PausableMonotonicFrameClock#withFrameNanos(kotlin.jvm.functions.Function1<? super java.lang.Long,? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.PausableMonotonicFrameClock.withFrameNanos
-ParameterNameChange: androidx.compose.runtime.ProduceStateScope#awaitDispose(kotlin.jvm.functions.Function0<kotlin.Unit>, kotlin.coroutines.Continuation<?>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.ProduceStateScope.awaitDispose
-ParameterNameChange: androidx.compose.runtime.Recomposer#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.awaitIdle
-ParameterNameChange: androidx.compose.runtime.Recomposer#join(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.join
-ParameterNameChange: androidx.compose.runtime.Recomposer#runRecomposeAndApplyChanges(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.runtime.Recomposer.runRecomposeAndApplyChanges
-ParameterNameChange: androidx.compose.runtime.RecomposerKt#withRunningRecomposer(kotlin.jvm.functions.Function3<? super kotlinx.coroutines.CoroutineScope,? super androidx.compose.runtime.Recomposer,? super kotlin.coroutines.Continuation<? super R>,?>, kotlin.coroutines.Continuation<? super R>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.compose.runtime.RecomposerKt.withRunningRecomposer
-
-
-RemovedMethod: androidx.compose.runtime.SkippableUpdater#SkippableUpdater():
-    Removed constructor androidx.compose.runtime.SkippableUpdater()
-RemovedMethod: androidx.compose.runtime.Updater#Updater():
-    Removed constructor androidx.compose.runtime.Updater()
diff --git a/compose/runtime/runtime/api/restricted_current.txt b/compose/runtime/runtime/api/restricted_current.txt
index f82be3b..b50e6f8 100644
--- a/compose/runtime/runtime/api/restricted_current.txt
+++ b/compose/runtime/runtime/api/restricted_current.txt
@@ -902,6 +902,7 @@
 
   public final class SnapshotKt {
     method @kotlin.PublishedApi internal static <T extends androidx.compose.runtime.snapshots.StateRecord> T current(T r, androidx.compose.runtime.snapshots.Snapshot snapshot);
+    method @kotlin.PublishedApi internal static <T extends androidx.compose.runtime.snapshots.StateRecord> T current(T r);
     method @kotlin.PublishedApi internal static void notifyWrite(androidx.compose.runtime.snapshots.Snapshot snapshot, androidx.compose.runtime.snapshots.StateObject state);
     method public static <T extends androidx.compose.runtime.snapshots.StateRecord> T readable(T, androidx.compose.runtime.snapshots.StateObject state);
     method public static <T extends androidx.compose.runtime.snapshots.StateRecord> T readable(T, androidx.compose.runtime.snapshots.StateObject state, androidx.compose.runtime.snapshots.Snapshot snapshot);
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
index 757dad6a..8a99350 100644
--- a/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
+++ b/compose/runtime/runtime/compose-runtime-benchmark/build.gradle
@@ -41,6 +41,8 @@
     androidTestImplementation(projectOrArtifact(":compose:runtime:runtime"))
     androidTestImplementation(projectOrArtifact(":compose:ui:ui-text"))
     androidTestImplementation(projectOrArtifact(":compose:ui:ui-util"))
+    androidTestImplementation(projectOrArtifact(":compose:test-utils"))
+    androidTestImplementation(projectOrArtifact(":compose:benchmark-utils"))
 
     androidTestImplementation(libs.junit)
     androidTestImplementation(libs.testExtJunit)
diff --git a/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/TrivialCompositionBenchmarkValidator.kt b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/TrivialCompositionBenchmarkValidator.kt
new file mode 100644
index 0000000..70bc144
--- /dev/null
+++ b/compose/runtime/runtime/compose-runtime-benchmark/src/androidTest/java/androidx/compose/runtime/benchmark/TrivialCompositionBenchmarkValidator.kt
@@ -0,0 +1,75 @@
+/*
+ * 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.compose.runtime.benchmark
+
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.testutils.LayeredComposeTestCase
+import androidx.compose.testutils.ToggleableTestCase
+import androidx.compose.testutils.benchmark.ComposeBenchmarkRule
+import androidx.compose.testutils.benchmark.toggleStateBenchmarkRecompose
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.layout.Layout
+import androidx.compose.ui.layout.MeasurePolicy
+import androidx.test.filters.LargeTest
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+class TrivialCompositionBenchmarkValidator : LayeredComposeTestCase(), ToggleableTestCase {
+    private var toggleState = mutableStateOf(false)
+
+    @Composable
+    override fun MeasuredContent() {
+        Layout(modifier = modifier, measurePolicy = measurePolicy)
+    }
+
+    override fun toggleState() {
+        toggleState.value = !toggleState.value
+    }
+}
+
+private val measurePolicy = MeasurePolicy { _, _ ->
+    layout(300, 300) {}
+}
+
+private val modifier = Modifier.fillMaxSize()
+
+@LargeTest
+@RunWith(Parameterized::class)
+class TrivialCompositionBenchmarkValidatorTest(private val testIteration: Int) {
+
+    @get:Rule
+    val benchmarkRule = ComposeBenchmarkRule()
+
+    private val caseFactory = {
+        TrivialCompositionBenchmarkValidator()
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "testIteration={0}")
+        fun initParameters(): Array<Any> = Array(20) { it + 1 }
+    }
+
+    @Test
+    fun recomposeIsConstantOverhead() {
+        benchmarkRule.toggleStateBenchmarkRecompose(caseFactory, requireRecomposition = false)
+    }
+}
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 9f1b15d..ce6ea33 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
@@ -21,7 +21,7 @@
  * Recomposition will always return the value produced by composition.
  */
 @Composable
-inline fun <T> remember(calculation: @DisallowComposableCalls () -> T): T =
+inline fun <T> remember(crossinline calculation: @DisallowComposableCalls () -> T): T =
     currentComposer.cache(false, calculation)
 
 /**
@@ -31,7 +31,7 @@
 @Composable
 inline fun <T> remember(
     key1: Any?,
-    calculation: @DisallowComposableCalls () -> T
+    crossinline calculation: @DisallowComposableCalls () -> T
 ): T {
     return currentComposer.cache(currentComposer.changed(key1), calculation)
 }
@@ -44,7 +44,7 @@
 inline fun <T> remember(
     key1: Any?,
     key2: Any?,
-    calculation: @DisallowComposableCalls () -> T
+    crossinline calculation: @DisallowComposableCalls () -> T
 ): T {
     return currentComposer.cache(
         currentComposer.changed(key1) or currentComposer.changed(key2),
@@ -61,7 +61,7 @@
     key1: Any?,
     key2: Any?,
     key3: Any?,
-    calculation: @DisallowComposableCalls () -> T
+    crossinline calculation: @DisallowComposableCalls () -> T
 ): T {
     return currentComposer.cache(
         currentComposer.changed(key1) or
@@ -78,7 +78,7 @@
 @Composable
 inline fun <T> remember(
     vararg keys: Any?,
-    calculation: @DisallowComposableCalls () -> T
+    crossinline calculation: @DisallowComposableCalls () -> T
 ): T {
     var invalid = false
     for (key in keys) invalid = invalid or currentComposer.changed(key)
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 bc62e6c..44515859 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
@@ -1237,6 +1237,7 @@
     private var writer: SlotWriter = insertTable.openWriter().also { it.close() }
     private var writerHasAProvider = false
     private var providerCache: CompositionLocalMap? = null
+    internal var deferredChanges: MutableList<Change>? = null
 
     private var insertAnchor: Anchor = insertTable.read { it.anchor(0) }
     private val insertFixups = mutableListOf<Change>()
@@ -1416,8 +1417,12 @@
         entersStack.clear()
         providersInvalidStack.clear()
         providerUpdates.clear()
-        if (!reader.closed) { reader.close() }
-        if (!writer.closed) { writer.close() }
+        if (!reader.closed) {
+            reader.close()
+        }
+        if (!writer.closed) {
+            writer.close()
+        }
         createFreshInsertTable()
         compoundKeyHash = 0
         childrenComposing = 0
@@ -1445,12 +1450,13 @@
      * True if the composition should be checking if the composable functions can be skipped.
      */
     @ComposeCompilerApi
-    override val skipping: Boolean get() {
-        return !inserting && !reusing &&
-            !providersInvalid &&
-            currentRecomposeScope?.requiresRecompose == false &&
-            !forciblyRecompose
-    }
+    override val skipping: Boolean
+        get() {
+            return !inserting && !reusing &&
+                !providersInvalid &&
+                currentRecomposeScope?.requiresRecompose == false &&
+                !forciblyRecompose
+        }
 
     /**
      * Returns the hash of the compound key calculated as a combination of the keys of all the
@@ -1947,21 +1953,23 @@
      */
     override fun buildContext(): CompositionContext {
         startGroup(referenceKey, reference)
+        if (inserting)
+            writer.markGroup()
 
-        var ref = nextSlot() as? CompositionContextHolder
-        if (ref == null) {
-            ref = CompositionContextHolder(
+        var holder = nextSlot() as? CompositionContextHolder
+        if (holder == null) {
+            holder = CompositionContextHolder(
                 CompositionContextImpl(
                     compoundKeyHash,
                     forceRecomposeScopes
                 )
             )
-            updateValue(ref)
+            updateValue(holder)
         }
-        ref.ref.updateCompositionLocalScope(currentCompositionLocalScope())
+        holder.ref.updateCompositionLocalScope(currentCompositionLocalScope())
         endGroup()
 
-        return ref.ref
+        return holder.ref
     }
 
     private fun <T> resolveCompositionLocal(
@@ -2477,7 +2485,7 @@
         // An early out if the group and anchor are the same
         if (anchorGroup == group) return index
 
-        // Walk down from the anchor group counting nodes of siblings in front of this group
+        // Walk down from the anc ghor group counting nodes of siblings in front of this group
         var current = anchorGroup
         val nodeIndexLimit = index + (updatedNodeCount(anchorGroup) - reader.nodeCount(group))
         loop@ while (index < nodeIndexLimit) {
@@ -2575,8 +2583,9 @@
                     compoundKeyOf(
                         reader.parent(group),
                         recomposeGroup,
-                        recomposeKey) rol 3
-                ) xor groupKey
+                        recomposeKey
+                    ) rol 3
+                    ) xor groupKey
         }
     }
 
@@ -3049,10 +3058,10 @@
                                     invalidations = from.invalidations
                                 ) {
                                     invokeMovableContentLambda(
-                                            to.content,
-                                            to.locals,
-                                            to.parameter,
-                                            force = true
+                                        to.content,
+                                        to.locals,
+                                        to.parameter,
+                                        force = true
                                     )
                                 }
                             }
@@ -3173,6 +3182,7 @@
             isComposing = false
         }
     }
+
     /**
      * Synchronously recompose all invalidated groups. This collects the changes which must be
      * applied by [ControlledComposition.applyChanges] to have an effect.
@@ -3480,74 +3490,70 @@
      *
      * Returns the number of nodes left in place which is used to calculate the node index of
      * any nested calls.
+     *
+     * @param groupBeingRemoved The group that is being removed from the table or 0 if the entire
+     *   table is being removed.
      */
     private fun reportFreeMovableContent(groupBeingRemoved: Int) {
 
         fun reportGroup(group: Int, needsNodeDelete: Boolean, nodeIndex: Int): Int {
-            // If the group has a mark (e.g. it is a movable content group), schedule it to be
-            // removed and report that it is free to be moved to the parentContext. Nested
-            // movable content is recomposed if necessary once the group has been claimed by
-            // another insert. If the nested movable content ends up being removed this is reported
-            // during that recomposition so there is no need to look at child movable content here.
             return if (reader.hasMark(group)) {
-                @Suppress("UNCHECKED_CAST") // The mark is only used when this cast is valid.
-                val value = reader.groupObjectKey(group) as MovableContent<Any?>
-                val parameter = reader.groupGet(group, 0)
-                val anchor = reader.anchor(group)
-                val end = group + reader.groupSize(group)
-                val invalidations = this.invalidations.filterToRange(group, end).fastMap {
-                    it.scope to it.instances
-                }
-                val reference = MovableContentStateReference(
-                    value,
-                    parameter,
-                    composition,
-                    slotTable,
-                    anchor,
-                    invalidations,
-                    currentCompositionLocalScope(group)
-                )
-                parentContext.deletedMovableContent(reference)
-                recordSlotEditing()
-                record { _, slots, _ ->
-                    val slotTable = SlotTable()
-
-                    // Write a table that as if it was written by a calling
-                    // invokeMovableContentLambda because this might be removed from the
-                    // composition before the new composition can be composed to receive it. When
-                    // the new composition receives the state it must recompose over the state by
-                    // calling invokeMovableContentLambda.
-                    slotTable.write { writer ->
-                        writer.beginInsert()
-
-                        // This is the prefix created by invokeMovableContentLambda
-                        writer.startGroup(movableContentKey, value)
-                        writer.markGroup()
-                        writer.update(parameter)
-
-                        // Move the content into current location
-                        slots.moveTo(anchor, 1, writer)
-
-                        // skip the group that was just inserted.
-                        writer.skipGroup()
-
-                        // End the group that represents the call to invokeMovableContentLambda
-                        writer.endGroup()
-
-                        writer.endInsert()
+                // If the group has a mark then it is either a movable content group or a
+                // composition context group
+                val key = reader.groupKey(group)
+                val objectKey = reader.groupObjectKey(group)
+                if (key == movableContentKey && objectKey is MovableContent<*>) {
+                    // If the group is a movable content block schedule it to be removed and report
+                    // that it is free to be moved to the parentContext. Nested movable content is
+                    // recomposed if necessary once the group has been claimed by another insert.
+                    // If the nested movable content ends up being removed this is reported during
+                    // that recomposition so there is no need to look at child movable content here.
+                    @Suppress("UNCHECKED_CAST")
+                    val movableContent = objectKey as MovableContent<Any?>
+                    val parameter = reader.groupGet(group, 0)
+                    val anchor = reader.anchor(group)
+                    val end = group + reader.groupSize(group)
+                    val invalidations = this.invalidations.filterToRange(group, end).fastMap {
+                        it.scope to it.instances
                     }
-                    val state = MovableContentState(slotTable)
-                    parentContext.movableContentStateReleased(reference, state)
-                }
-                if (needsNodeDelete) {
-                    realizeMovement()
-                    realizeUps()
-                    realizeDowns()
-                    val nodeCount = if (reader.isNode(group)) 1 else reader.nodeCount(group)
-                    if (nodeCount > 0) {
-                        recordRemoveNode(nodeIndex, nodeCount)
+                    val reference = MovableContentStateReference(
+                        movableContent,
+                        parameter,
+                        composition,
+                        slotTable,
+                        anchor,
+                        invalidations,
+                        currentCompositionLocalScope(group)
+                    )
+                    parentContext.deletedMovableContent(reference)
+                    recordSlotEditing()
+                    record { _, slots, _ -> releaseMovableGroupAtCurrent(reference, slots) }
+                    if (needsNodeDelete) {
+                        realizeMovement()
+                        realizeUps()
+                        realizeDowns()
+                        val nodeCount = if (reader.isNode(group)) 1 else reader.nodeCount(group)
+                        if (nodeCount > 0) {
+                            recordRemoveNode(nodeIndex, nodeCount)
+                        }
+                        0 // These nodes were deleted
+                    } else reader.nodeCount(group)
+                } else if (key == referenceKey && objectKey == reference) {
+                    // Group is a composition context reference. As this is being removed assume
+                    // all movable groups in the composition that have this context will also be
+                    // released whe the compositions are disposed.
+                    val contextHolder = reader.groupGet(group, 0) as? CompositionContextHolder
+                    if (contextHolder != null) {
+                        // The contextHolder can be EMPTY in cases wher the content has been
+                        // deactivated. Content is deactivated if the content is just being
+                        // held onto for recycling and is not otherwise active. In this case
+                        // the composers we are likely to find here have already been disposed.
+                        val compositionContext = contextHolder.ref
+                        compositionContext.composers.forEach { composer ->
+                            composer.reportAllMovableContent()
+                        }
                     }
-                    0 // These nodes were deleted
+                    reader.nodeCount(group)
                 } else reader.nodeCount(group)
             } else if (reader.containsMark(group)) {
                 // Traverse the group freeing the child movable content. This group is known to
@@ -3589,6 +3595,66 @@
     }
 
     /**
+     * Release the reference the movable group stored in [slots] to the recomposer for to be used
+     * to insert to insert to other locations.
+     */
+    private fun releaseMovableGroupAtCurrent(
+        reference: MovableContentStateReference,
+        slots: SlotWriter
+    ) {
+        val slotTable = SlotTable()
+
+        // Write a table that as if it was written by a calling
+        // invokeMovableContentLambda because this might be removed from the
+        // composition before the new composition can be composed to receive it. When
+        // the new composition receives the state it must recompose over the state by
+        // calling invokeMovableContentLambda.
+        slotTable.write { writer ->
+            writer.beginInsert()
+
+            // This is the prefix created by invokeMovableContentLambda
+            writer.startGroup(movableContentKey, reference.content)
+            writer.markGroup()
+            writer.update(reference.parameter)
+
+            // Move the content into current location
+            slots.moveTo(reference.anchor, 1, writer)
+
+            // skip the group that was just inserted.
+            writer.skipGroup()
+
+            // End the group that represents the call to invokeMovableContentLambda
+            writer.endGroup()
+
+            writer.endInsert()
+        }
+        val state = MovableContentState(slotTable)
+        parentContext.movableContentStateReleased(reference, state)
+    }
+
+    /**
+     * Called during composition to report all the content of the composition will be released
+     * as this composition is to be disposed.
+     */
+    private fun reportAllMovableContent() {
+        if (slotTable.containsMark()) {
+            val changes = mutableListOf<Change>()
+            deferredChanges = changes
+            slotTable.read { reader ->
+                this.reader = reader
+                withChanges(changes) {
+                    reportFreeMovableContent(0)
+                    realizeUps()
+                    if (startedGroup) {
+                        record(skipToGroupEndInstance)
+                        recordEndRoot()
+                    }
+                }
+            }
+        }
+    }
+
+    /**
      * Called when reader current is moved directly, such as when a group moves, to [location].
      */
     private fun recordReaderMoving(location: Int) {
@@ -3605,15 +3671,17 @@
             val reader = reader
             val location = reader.parent
 
-            if (startedGroups.peekOr(-1) != location) {
+            if (startedGroups.peekOr(invalidGroupLocation) != location) {
                 if (!startedGroup && implicitRootStart) {
                     // We need to ensure the root group is started.
                     recordSlotTableOperation(change = startRootGroup)
                     startedGroup = true
                 }
-                val anchor = reader.anchor(location)
-                startedGroups.push(location)
-                recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
+                if (location > 0) {
+                    val anchor = reader.anchor(location)
+                    startedGroups.push(location)
+                    recordSlotTableOperation { _, slots, _ -> slots.ensureStarted(anchor) }
+                }
             }
         }
     }
@@ -4298,6 +4366,8 @@
 @PublishedApi
 internal const val reuseKey = 207
 
+private const val invalidGroupLocation = -2
+
 internal class ComposeRuntimeError(override val message: String) : IllegalStateException()
 
 internal inline fun runtimeCheck(value: Boolean, lazyMessage: () -> Any) {
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 a32afee..0f27069 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
@@ -595,6 +595,24 @@
             if (!disposed) {
                 disposed = true
                 composable = {}
+
+                // Changes are deferred if the composition contains movable content that needs
+                // to be released. NOTE: Applying these changes leaves the slot table in
+                // potentially invalid state. The routine use to produce this change list reuses
+                // code that extracts movable content from groups that are being deleted. This code
+                // does not bother to correctly maintain the node counts of a group nested groups
+                // that are going to be removed anyway so the node counts of the groups affected
+                // are might be incorrect after the changes have been applied.
+                val deferredChanges = composer.deferredChanges
+                if (deferredChanges != null) {
+                    applyChangesInLocked(deferredChanges)
+                }
+
+                // Dispatch all the `onForgotten` events for object that are no longer part of a
+                // composition because this composition is being discarded. It is important that
+                // this is done after applying deferred changes above to avoid sending `
+                // onForgotten` notification to objects that are still part of movable content that
+                // will be moved to a new location.
                 val nonEmptySlotTable = slotTable.groupsSize > 0
                 if (nonEmptySlotTable || abandonSet.isNotEmpty()) {
                     val manager = RememberEventDispatcher(abandonSet)
@@ -791,7 +809,6 @@
                     }
                     changes.clear()
                 }
-
                 applier.onEndChanges()
             }
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
index 76d772b..59a68e6 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/DerivedState.kt
@@ -100,17 +100,17 @@
                             return@forEach
                         }
 
-                        if (stateObject is DerivedSnapshotState<*>) {
+                        // Find the first record without triggering an observer read.
+                        val record = if (stateObject is DerivedSnapshotState<*>) {
                             // eagerly access the parent derived states without recording the
                             // read
                             // that way we can be sure derived states in deps were recalculated,
                             // and are updated to the last values
-                            stateObject.refresh(stateObject.firstStateRecord, snapshot)
+                            stateObject.current(snapshot)
+                        } else {
+                            current(stateObject.firstStateRecord, snapshot)
                         }
 
-                        // Find the first record without triggering an observer read.
-                        val record = current(stateObject.firstStateRecord, snapshot)
-
                         hash = 31 * hash + identityHashCode(record)
                         hash = 31 * hash + record.snapshotId
                     }
@@ -120,10 +120,15 @@
         }
     }
 
-    fun refresh(record: StateRecord, snapshot: Snapshot) {
+    /**
+     * Get current record in snapshot. Forces recalculation if record is invalid to refresh
+     * state value.
+     *
+     * @return latest state record for the derived state.
+     */
+    fun current(snapshot: Snapshot): StateRecord =
         @Suppress("UNCHECKED_CAST")
-        currentRecord(record as ResultRecord<T>, snapshot, false, calculation)
-    }
+        currentRecord(current(first, snapshot), snapshot, false, calculation)
 
     private fun currentRecord(
         readable: ResultRecord<T>,
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
index 2308db7..1254875 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Effects.kt
@@ -470,7 +470,8 @@
  */
 @Composable
 inline fun rememberCoroutineScope(
-    getContext: @DisallowComposableCalls () -> CoroutineContext = { EmptyCoroutineContext }
+    crossinline getContext: @DisallowComposableCalls () -> CoroutineContext =
+        { EmptyCoroutineContext }
 ): CoroutineScope {
     val composer = currentComposer
     val wrapper = remember {
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
index 725fb0d..7da810e 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/Recomposer.kt
@@ -622,13 +622,13 @@
                         }
                     }
 
-                    discardUnusedValues()
-
                     synchronized(stateLock) {
                         deriveStateLocked()
                     }
                 }
             }
+
+            discardUnusedValues()
         }
     }
 
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
index 612736f..4b55add1 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/SlotTable.kt
@@ -351,6 +351,13 @@
     }
 
     /**
+     * Turns true if the first group (considered the root group) contains a mark.
+     */
+    fun containsMark(): Boolean {
+        return groupsSize >= 0 && groups.containsMark(0)
+    }
+
+    /**
      * Find the nearest recompose scope for [group] that, when invalidated, will cause [group]
      * group to be recomposed.
      */
@@ -469,7 +476,7 @@
         var lastLocation = -1
         anchors.fastForEach { anchor ->
             val location = anchor.toIndexFor(this)
-            require(location in 0..groupsSize) { "Location out of bound" }
+            require(location in 0..groupsSize) { "Invalid anchor, location out of bound" }
             require(lastLocation < location) { "Anchor is out of order" }
             lastLocation = location
         }
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
index 489920f..25e87d6 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/Snapshot.kt
@@ -1828,8 +1828,19 @@
  * Return the current readable state record for the current snapshot. It is assumed that [this]
  * is the first record of [state]
  */
-fun <T : StateRecord> T.readable(state: StateObject): T =
-    readable(state, currentSnapshot())
+fun <T : StateRecord> T.readable(state: StateObject): T {
+    val snapshot = Snapshot.current
+    snapshot.readObserver?.invoke(state)
+    return readable(this, snapshot.id, snapshot.invalid) ?: sync {
+        // Readable can return null when the global snapshot has been advanced by another thread
+        // and state written to the object was overwritten while this thread was paused. Repeating
+        // the read is valid here as either this will return the same result as the previous call
+        // or will find a valid record. Being in a sync block prevents other threads from writing
+        // to this state object until the read completes.
+        val syncSnapshot = Snapshot.current
+        readable(this, syncSnapshot.id, syncSnapshot.invalid)
+    } ?: readError()
+}
 
 /**
  * Return the current readable state record for the [snapshot]. It is assumed that [this]
@@ -2089,13 +2100,23 @@
 internal fun <T : StateRecord> current(r: T, snapshot: Snapshot) =
     readable(r, snapshot.id, snapshot.invalid) ?: readError()
 
+@PublishedApi
+internal fun <T : StateRecord> current(r: T) =
+    Snapshot.current.let { snapshot ->
+        readable(r, snapshot.id, snapshot.invalid) ?: sync {
+            Snapshot.current.let { syncSnapshot ->
+                readable(r, syncSnapshot.id, syncSnapshot.invalid)
+            }
+        } ?: readError()
+    }
+
 /**
  * Provides a [block] with the current record, without notifying any read observers.
  *
  * @see readable
  */
 inline fun <T : StateRecord, R> T.withCurrent(block: (r: T) -> R): R =
-    block(current(this, Snapshot.current))
+    block(current(this))
 
 /**
  * Helper routine to add a range of values ot a snapshot set
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
index 7ded476..24a3119 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/MovableContentTests.kt
@@ -1027,8 +1027,8 @@
         expectUnused()
     }
 
-    @Test // Regression test for 230830644
-    fun deferredSubcompose_conditional() = compositionTest {
+    @Test // Regression test for 230830644 and 235398298
+    fun deferredSubcompose_conditional_rootLevelChildren() = compositionTest {
         var subcompose by mutableStateOf(false)
         var lastPrivateState: State<Int> = mutableStateOf(0)
 
@@ -1072,7 +1072,71 @@
         expectChanges()
         revalidate()
 
-        assertEquals(expectedState, lastPrivateState)
+        assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
+
+        subcompose = false
+        expectChanges()
+        revalidate()
+
+        assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
+    }
+
+    @Test // Regression test for 230830644 and 235398298
+    fun deferredSubcompose_conditional_nestedChildren() = compositionTest {
+        var subcompose by mutableStateOf(false)
+        var lastPrivateState: State<Int> = mutableStateOf(0)
+
+        val content = movableContentOf {
+            lastPrivateState = remember { mutableStateOf(0) }
+            Text("Movable content")
+        }
+
+        compose {
+            Text("Main content start")
+            if (!subcompose) {
+                content()
+            }
+            Text("Main content end")
+            if (subcompose) {
+                DeferredSubcompose {
+                    Column {
+                        Text("Sub-composed content start")
+                        content()
+                        Text("Sub-composed content end")
+                    }
+                }
+            }
+        }
+
+        validate {
+            Text("Main content start")
+            if (!subcompose) {
+                Text("Movable content")
+            }
+            Text("Main content end")
+            if (subcompose) {
+                DeferredSubcompose {
+                    Column {
+                        Text("Sub-composed content start")
+                        Text("Movable content")
+                        Text("Sub-composed content end")
+                    }
+                }
+            }
+        }
+
+        val expectedState = lastPrivateState
+        subcompose = true
+        expectChanges()
+        revalidate()
+
+        assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
+
+        subcompose = false
+        expectChanges()
+        revalidate()
+
+        assertEquals(expectedState, lastPrivateState, "Movable content was unexpectedly recreated")
     }
 
     @Test // Regression test for 230830644
@@ -1525,10 +1589,11 @@
     ComposeNode<View, ViewApplier>(factory = { host }, update = { })
     val parent = rememberCompositionContext()
     val composition = remember { Composition(ViewApplier(host), parent) }
-    SideEffect {
+    LaunchedEffect(content as Any) {
         composition.setContent(content)
     }
     DisposableEffect(Unit) {
+
         onDispose { composition.dispose() }
     }
 }
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/View.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/View.kt
index 4b63669..32cd5cf 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/View.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/View.kt
@@ -44,9 +44,11 @@
     }
 
     fun addAt(index: Int, view: View) {
-        if (view.parent != null) {
+        val parent = view.parent
+        if (parent != null) {
             error(
-                "Inserting a view named ${view.name} already has a parent into a view named $name"
+                "Inserting a view named ${view.name} into a view named $name which already has " +
+                    "a parent named ${parent.name}"
             )
         }
         view.parent = this
@@ -79,6 +81,11 @@
         }
     }
 
+    fun removeAllChildren() {
+        children.fastForEach { child -> child.parent = null }
+        children.clear()
+    }
+
     fun attribute(name: String, value: Any) { attributes[name] = value }
 
     var value: String?
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/ViewApplier.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
index 28ebae3..4b1b684 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/mock/ViewApplier.kt
@@ -43,7 +43,7 @@
     }
 
     override fun onClear() {
-        root.children.clear()
+        root.removeAllChildren()
     }
 
     override fun onBeginChanges() {
diff --git a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
index d7ec4ee..3457ab2 100644
--- a/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
+++ b/compose/runtime/runtime/src/commonTest/kotlin/androidx/compose/runtime/snapshots/DerivedSnapshotStateTests.kt
@@ -244,6 +244,44 @@
         assertEquals(1, runs)
     }
 
+    @Test
+    fun stateUpdatedInSnapshotIsNotRecalculated() {
+        var runs = 0
+        val dependency = mutableStateOf(0)
+        val a = derivedStateOf {
+            runs++
+            dependency.value
+        }
+        val b = derivedStateOf {
+            a.value
+        }
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        dependency.value++
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        Snapshot.takeMutableSnapshot().apply {
+            enter {
+                b.value
+            }
+            apply()
+        }
+
+        assertEquals(2, runs)
+    }
+
     private var count = 0
 
     @BeforeTest
diff --git a/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTestsJvm.kt b/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTestsJvm.kt
new file mode 100644
index 0000000..307a8eb
--- /dev/null
+++ b/compose/runtime/runtime/src/jvmTest/kotlin/androidx/compose/runtime/snapshots/SnapshotTestsJvm.kt
@@ -0,0 +1,77 @@
+/*
+ * 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.compose.runtime.snapshots
+
+import androidx.compose.runtime.AtomicInt
+import androidx.compose.runtime.AtomicReference
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.postIncrement
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlin.concurrent.thread
+import kotlin.random.Random
+import kotlin.test.Test
+import kotlin.test.assertNull
+
+class SnapshotTestsJvm {
+
+    @Test
+    fun testMultiThreadedReadingAndWritingOfGlobalScope() {
+        val running = AtomicBoolean(true)
+        val reads = AtomicInt(0)
+        val writes = AtomicInt(0)
+        val lowNumberSeen = AtomicInt(0)
+        val exception = AtomicReference<Throwable?>(null)
+        try {
+            val state = mutableStateOf(0)
+            Snapshot.notifyObjectsInitialized()
+
+            // Create 100 reader threads of state
+            repeat(100) {
+                thread {
+                    try {
+                        while (running.get()) {
+                            reads.postIncrement()
+                            if (state.value < 1000) lowNumberSeen.postIncrement()
+                        }
+                    } catch (e: Throwable) {
+                        exception.set(e)
+                        running.set(false)
+                    }
+                }
+            }
+
+            // Create 10 writer threads
+            repeat(10) {
+                thread {
+                    while (running.get()) {
+                        writes.postIncrement()
+                        state.value = Random.nextInt(10000)
+                        Snapshot.sendApplyNotifications()
+                    }
+                }
+            }
+
+            while (running.get() && writes.get() < 10000) {
+                Thread.sleep(0)
+            }
+        } finally {
+            running.set(false)
+        }
+
+        assertNull(exception.get())
+    }
+}
\ No newline at end of file
diff --git a/compose/runtime/settings.gradle b/compose/runtime/settings.gradle
index 152212a..b1ee839 100644
--- a/compose/runtime/settings.gradle
+++ b/compose/runtime/settings.gradle
@@ -31,6 +31,7 @@
         if (name.startsWith(":compose:runtime") && name != ":compose:runtime") return true
         if (name == ":annotation:annotation-sampled") return true
         if (isNeededForComposePlayground(name)) return true
+        if (name == ":compose:benchmark-utils") return true
         return false
     })
 }
diff --git a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
index 3f0d59f..788d48f 100644
--- a/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
+++ b/compose/test-utils/src/androidMain/kotlin/androidx/compose/testutils/AndroidComposeTestCaseRunner.android.kt
@@ -251,6 +251,7 @@
         view = null
         testCase = null
         simulationState = SimulationState.Initialized
+        recomposer.close()
     }
 
     override fun capturePreviewPictureToActivity() {
diff --git a/compose/ui/ui-geometry/api/current.ignore b/compose/ui/ui-geometry/api/current.ignore
deleted file mode 100644
index 1963ca7..0000000
--- a/compose/ui/ui-geometry/api/current.ignore
+++ /dev/null
@@ -1,17 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setBottom(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setBottom
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setLeft(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setLeft
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setRight(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setRight
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setTop(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setTop
-
-
-RemovedMethod: androidx.compose.ui.geometry.CornerRadius#CornerRadius():
-    Removed constructor androidx.compose.ui.geometry.CornerRadius()
-RemovedMethod: androidx.compose.ui.geometry.Offset#Offset():
-    Removed constructor androidx.compose.ui.geometry.Offset()
-RemovedMethod: androidx.compose.ui.geometry.Size#Size():
-    Removed constructor androidx.compose.ui.geometry.Size()
diff --git a/compose/ui/ui-geometry/api/restricted_current.ignore b/compose/ui/ui-geometry/api/restricted_current.ignore
deleted file mode 100644
index 1963ca7..0000000
--- a/compose/ui/ui-geometry/api/restricted_current.ignore
+++ /dev/null
@@ -1,17 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setBottom(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setBottom
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setLeft(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setLeft
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setRight(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setRight
-ParameterNameChange: androidx.compose.ui.geometry.MutableRect#setTop(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.geometry.MutableRect.setTop
-
-
-RemovedMethod: androidx.compose.ui.geometry.CornerRadius#CornerRadius():
-    Removed constructor androidx.compose.ui.geometry.CornerRadius()
-RemovedMethod: androidx.compose.ui.geometry.Offset#Offset():
-    Removed constructor androidx.compose.ui.geometry.Offset()
-RemovedMethod: androidx.compose.ui.geometry.Size#Size():
-    Removed constructor androidx.compose.ui.geometry.Size()
diff --git a/compose/ui/ui-graphics/api/current.ignore b/compose/ui/ui-graphics/api/current.ignore
deleted file mode 100644
index 2e8c07c..0000000
--- a/compose/ui/ui-graphics/api/current.ignore
+++ /dev/null
@@ -1,97 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setAlpha(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setAlpha
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setAntiAlias(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setAntiAlias
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setBlendMode(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setBlendMode
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setColor(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setColor
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setColorFilter(androidx.compose.ui.graphics.ColorFilter) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setColorFilter
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setFilterQuality(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setFilterQuality
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setPathEffect(androidx.compose.ui.graphics.PathEffect) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setPathEffect
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setShader(android.graphics.Shader) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setShader
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeCap(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeCap
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeJoin(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeJoin
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeMiterLimit(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeMiterLimit
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeWidth(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeWidth
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStyle(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStyle
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPath#setFillType(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPath.setFillType
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setAlpha(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setAlpha
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setAntiAlias(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setAntiAlias
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setBlendMode(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setBlendMode
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setColor(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setColor
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setColorFilter(androidx.compose.ui.graphics.ColorFilter) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setColorFilter
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setFilterQuality(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setFilterQuality
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setPathEffect(androidx.compose.ui.graphics.PathEffect) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setPathEffect
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setShader(android.graphics.Shader) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setShader
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeCap(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeCap
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeJoin(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeJoin
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeMiterLimit(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeMiterLimit
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeWidth(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeWidth
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStyle(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStyle
-ParameterNameChange: androidx.compose.ui.graphics.Path#setFillType(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Path.setFillType
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.DrawContext#setSize(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.DrawContext.setSize
-
-
-RemovedMethod: androidx.compose.ui.graphics.BlendMode#BlendMode():
-    Removed constructor androidx.compose.ui.graphics.BlendMode()
-RemovedMethod: androidx.compose.ui.graphics.ClipOp#ClipOp():
-    Removed constructor androidx.compose.ui.graphics.ClipOp()
-RemovedMethod: androidx.compose.ui.graphics.Color#Color():
-    Removed constructor androidx.compose.ui.graphics.Color()
-RemovedMethod: androidx.compose.ui.graphics.ColorMatrix#ColorMatrix():
-    Removed constructor androidx.compose.ui.graphics.ColorMatrix()
-RemovedMethod: androidx.compose.ui.graphics.FilterQuality#FilterQuality():
-    Removed constructor androidx.compose.ui.graphics.FilterQuality()
-RemovedMethod: androidx.compose.ui.graphics.ImageBitmapConfig#ImageBitmapConfig():
-    Removed constructor androidx.compose.ui.graphics.ImageBitmapConfig()
-RemovedMethod: androidx.compose.ui.graphics.Matrix#Matrix():
-    Removed constructor androidx.compose.ui.graphics.Matrix()
-RemovedMethod: androidx.compose.ui.graphics.PaintingStyle#PaintingStyle():
-    Removed constructor androidx.compose.ui.graphics.PaintingStyle()
-RemovedMethod: androidx.compose.ui.graphics.PathFillType#PathFillType():
-    Removed constructor androidx.compose.ui.graphics.PathFillType()
-RemovedMethod: androidx.compose.ui.graphics.PathOperation#PathOperation():
-    Removed constructor androidx.compose.ui.graphics.PathOperation()
-RemovedMethod: androidx.compose.ui.graphics.PointMode#PointMode():
-    Removed constructor androidx.compose.ui.graphics.PointMode()
-RemovedMethod: androidx.compose.ui.graphics.StampedPathEffectStyle#StampedPathEffectStyle():
-    Removed constructor androidx.compose.ui.graphics.StampedPathEffectStyle()
-RemovedMethod: androidx.compose.ui.graphics.StrokeCap#StrokeCap():
-    Removed constructor androidx.compose.ui.graphics.StrokeCap()
-RemovedMethod: androidx.compose.ui.graphics.StrokeJoin#StrokeJoin():
-    Removed constructor androidx.compose.ui.graphics.StrokeJoin()
-RemovedMethod: androidx.compose.ui.graphics.TileMode#TileMode():
-    Removed constructor androidx.compose.ui.graphics.TileMode()
-RemovedMethod: androidx.compose.ui.graphics.VertexMode#VertexMode():
-    Removed constructor androidx.compose.ui.graphics.VertexMode()
-RemovedMethod: androidx.compose.ui.graphics.colorspace.ColorModel#ColorModel():
-    Removed constructor androidx.compose.ui.graphics.colorspace.ColorModel()
-RemovedMethod: androidx.compose.ui.graphics.colorspace.RenderIntent#RenderIntent():
-    Removed constructor androidx.compose.ui.graphics.colorspace.RenderIntent()
diff --git a/compose/ui/ui-graphics/api/restricted_current.ignore b/compose/ui/ui-graphics/api/restricted_current.ignore
deleted file mode 100644
index 9061a73..0000000
--- a/compose/ui/ui-graphics/api/restricted_current.ignore
+++ /dev/null
@@ -1,105 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setAlpha(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setAlpha
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setAntiAlias(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setAntiAlias
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setBlendMode(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setBlendMode
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setColor(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setColor
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setColorFilter(androidx.compose.ui.graphics.ColorFilter) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setColorFilter
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setFilterQuality(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setFilterQuality
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setPathEffect(androidx.compose.ui.graphics.PathEffect) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setPathEffect
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setShader(android.graphics.Shader) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setShader
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeCap(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeCap
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeJoin(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeJoin
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeMiterLimit(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeMiterLimit
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStrokeWidth(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStrokeWidth
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPaint#setStyle(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPaint.setStyle
-ParameterNameChange: androidx.compose.ui.graphics.AndroidPath#setFillType(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.AndroidPath.setFillType
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setAlpha(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setAlpha
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setAntiAlias(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setAntiAlias
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setBlendMode(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setBlendMode
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setColor(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setColor
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setColorFilter(androidx.compose.ui.graphics.ColorFilter) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setColorFilter
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setFilterQuality(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setFilterQuality
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setPathEffect(androidx.compose.ui.graphics.PathEffect) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setPathEffect
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setShader(android.graphics.Shader) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setShader
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeCap(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeCap
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeJoin(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeJoin
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeMiterLimit(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeMiterLimit
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStrokeWidth(float) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStrokeWidth
-ParameterNameChange: androidx.compose.ui.graphics.Paint#setStyle(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Paint.setStyle
-ParameterNameChange: androidx.compose.ui.graphics.Path#setFillType(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.Path.setFillType
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams#setCanvas(androidx.compose.ui.graphics.Canvas) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams.setCanvas
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams#setDensity(androidx.compose.ui.unit.Density) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams.setDensity
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams#setLayoutDirection(androidx.compose.ui.unit.LayoutDirection) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams.setLayoutDirection
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams#setSize(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.CanvasDrawScope.DrawParams.setSize
-ParameterNameChange: androidx.compose.ui.graphics.drawscope.DrawContext#setSize(long) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.graphics.drawscope.DrawContext.setSize
-
-
-RemovedMethod: androidx.compose.ui.graphics.BlendMode#BlendMode():
-    Removed constructor androidx.compose.ui.graphics.BlendMode()
-RemovedMethod: androidx.compose.ui.graphics.ClipOp#ClipOp():
-    Removed constructor androidx.compose.ui.graphics.ClipOp()
-RemovedMethod: androidx.compose.ui.graphics.Color#Color():
-    Removed constructor androidx.compose.ui.graphics.Color()
-RemovedMethod: androidx.compose.ui.graphics.ColorMatrix#ColorMatrix():
-    Removed constructor androidx.compose.ui.graphics.ColorMatrix()
-RemovedMethod: androidx.compose.ui.graphics.FilterQuality#FilterQuality():
-    Removed constructor androidx.compose.ui.graphics.FilterQuality()
-RemovedMethod: androidx.compose.ui.graphics.ImageBitmapConfig#ImageBitmapConfig():
-    Removed constructor androidx.compose.ui.graphics.ImageBitmapConfig()
-RemovedMethod: androidx.compose.ui.graphics.Matrix#Matrix():
-    Removed constructor androidx.compose.ui.graphics.Matrix()
-RemovedMethod: androidx.compose.ui.graphics.PaintingStyle#PaintingStyle():
-    Removed constructor androidx.compose.ui.graphics.PaintingStyle()
-RemovedMethod: androidx.compose.ui.graphics.PathFillType#PathFillType():
-    Removed constructor androidx.compose.ui.graphics.PathFillType()
-RemovedMethod: androidx.compose.ui.graphics.PathOperation#PathOperation():
-    Removed constructor androidx.compose.ui.graphics.PathOperation()
-RemovedMethod: androidx.compose.ui.graphics.PointMode#PointMode():
-    Removed constructor androidx.compose.ui.graphics.PointMode()
-RemovedMethod: androidx.compose.ui.graphics.StampedPathEffectStyle#StampedPathEffectStyle():
-    Removed constructor androidx.compose.ui.graphics.StampedPathEffectStyle()
-RemovedMethod: androidx.compose.ui.graphics.StrokeCap#StrokeCap():
-    Removed constructor androidx.compose.ui.graphics.StrokeCap()
-RemovedMethod: androidx.compose.ui.graphics.StrokeJoin#StrokeJoin():
-    Removed constructor androidx.compose.ui.graphics.StrokeJoin()
-RemovedMethod: androidx.compose.ui.graphics.TileMode#TileMode():
-    Removed constructor androidx.compose.ui.graphics.TileMode()
-RemovedMethod: androidx.compose.ui.graphics.VertexMode#VertexMode():
-    Removed constructor androidx.compose.ui.graphics.VertexMode()
-RemovedMethod: androidx.compose.ui.graphics.colorspace.ColorModel#ColorModel():
-    Removed constructor androidx.compose.ui.graphics.colorspace.ColorModel()
-RemovedMethod: androidx.compose.ui.graphics.colorspace.RenderIntent#RenderIntent():
-    Removed constructor androidx.compose.ui.graphics.colorspace.RenderIntent()
diff --git a/compose/ui/ui-test-junit4/api/current.ignore b/compose/ui/ui-test-junit4/api/current.ignore
deleted file mode 100644
index 48720cb..0000000
--- a/compose/ui/ui-test-junit4/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.test.junit4.AndroidComposeTestRule#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.junit4.AndroidComposeTestRule.awaitIdle
-ParameterNameChange: androidx.compose.ui.test.junit4.ComposeTestRule#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.junit4.ComposeTestRule.awaitIdle
diff --git a/compose/ui/ui-test-junit4/api/restricted_current.ignore b/compose/ui/ui-test-junit4/api/restricted_current.ignore
deleted file mode 100644
index 48720cb..0000000
--- a/compose/ui/ui-test-junit4/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.test.junit4.AndroidComposeTestRule#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.junit4.AndroidComposeTestRule.awaitIdle
-ParameterNameChange: androidx.compose.ui.test.junit4.ComposeTestRule#awaitIdle(kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.junit4.ComposeTestRule.awaitIdle
diff --git a/compose/ui/ui-test/api/current.ignore b/compose/ui/ui-test/api/current.ignore
deleted file mode 100644
index f74c6fd..0000000
--- a/compose/ui/ui-test/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.test.MainTestClock#setAutoAdvance(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.MainTestClock.setAutoAdvance
diff --git a/compose/ui/ui-test/api/restricted_current.ignore b/compose/ui/ui-test/api/restricted_current.ignore
deleted file mode 100644
index f74c6fd..0000000
--- a/compose/ui/ui-test/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.compose.ui.test.MainTestClock#setAutoAdvance(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.compose.ui.test.MainTestClock.setAutoAdvance
diff --git a/compose/ui/ui-text/api/current.ignore b/compose/ui/ui-text/api/current.ignore
deleted file mode 100644
index 85b4b37..0000000
--- a/compose/ui/ui-text/api/current.ignore
+++ /dev/null
@@ -1,91 +0,0 @@
-// Baseline format: 1.0
-AddSealed: androidx.compose.ui.text.Paragraph:
-    Cannot add 'sealed' modifier to class androidx.compose.ui.text.Paragraph: Incompatible change
-
-
-AddedFinal: androidx.compose.ui.text.Paragraph#getBidiRunDirection(int):
-    Method androidx.compose.ui.text.Paragraph.getBidiRunDirection has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getBoundingBox(int):
-    Method androidx.compose.ui.text.Paragraph.getBoundingBox has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getCursorRect(int):
-    Method androidx.compose.ui.text.Paragraph.getCursorRect has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getDidExceedMaxLines():
-    Method androidx.compose.ui.text.Paragraph.getDidExceedMaxLines has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getFirstBaseline():
-    Method androidx.compose.ui.text.Paragraph.getFirstBaseline has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getHeight():
-    Method androidx.compose.ui.text.Paragraph.getHeight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getHorizontalPosition(int, boolean):
-    Method androidx.compose.ui.text.Paragraph.getHorizontalPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLastBaseline():
-    Method androidx.compose.ui.text.Paragraph.getLastBaseline has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineBottom(int):
-    Method androidx.compose.ui.text.Paragraph.getLineBottom has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineCount():
-    Method androidx.compose.ui.text.Paragraph.getLineCount has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineEnd(int, boolean):
-    Method androidx.compose.ui.text.Paragraph.getLineEnd has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineForOffset(int):
-    Method androidx.compose.ui.text.Paragraph.getLineForOffset has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineForVerticalPosition(float):
-    Method androidx.compose.ui.text.Paragraph.getLineForVerticalPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineHeight(int):
-    Method androidx.compose.ui.text.Paragraph.getLineHeight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineLeft(int):
-    Method androidx.compose.ui.text.Paragraph.getLineLeft has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineRight(int):
-    Method androidx.compose.ui.text.Paragraph.getLineRight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineStart(int):
-    Method androidx.compose.ui.text.Paragraph.getLineStart has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineTop(int):
-    Method androidx.compose.ui.text.Paragraph.getLineTop has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineWidth(int):
-    Method androidx.compose.ui.text.Paragraph.getLineWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getMaxIntrinsicWidth():
-    Method androidx.compose.ui.text.Paragraph.getMaxIntrinsicWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getMinIntrinsicWidth():
-    Method androidx.compose.ui.text.Paragraph.getMinIntrinsicWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getOffsetForPosition(long):
-    Method androidx.compose.ui.text.Paragraph.getOffsetForPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getParagraphDirection(int):
-    Method androidx.compose.ui.text.Paragraph.getParagraphDirection has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getPathForRange(int, int):
-    Method androidx.compose.ui.text.Paragraph.getPathForRange has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getPlaceholderRects():
-    Method androidx.compose.ui.text.Paragraph.getPlaceholderRects has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getWidth():
-    Method androidx.compose.ui.text.Paragraph.getWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getWordBoundary(int):
-    Method androidx.compose.ui.text.Paragraph.getWordBoundary has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#isLineEllipsized(int):
-    Method androidx.compose.ui.text.Paragraph.isLineEllipsized has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration):
-    Method androidx.compose.ui.text.Paragraph.paint has added 'final' qualifier
-
-
-DefaultValueChange: androidx.compose.ui.text.Paragraph#getLineEnd(int, boolean) parameter #1:
-    Attempted to remove default value from parameter visibleEnd in androidx.compose.ui.text.Paragraph.getLineEnd
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #1:
-    Attempted to remove default value from parameter color in androidx.compose.ui.text.Paragraph.paint
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #2:
-    Attempted to remove default value from parameter shadow in androidx.compose.ui.text.Paragraph.paint
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #3:
-    Attempted to remove default value from parameter textDecoration in androidx.compose.ui.text.Paragraph.paint
-
-
-RemovedMethod: androidx.compose.ui.text.ParagraphStyle#equals(Object):
-    Removed method androidx.compose.ui.text.ParagraphStyle.equals(Object)
-RemovedMethod: androidx.compose.ui.text.Placeholder#equals(Object):
-    Removed method androidx.compose.ui.text.Placeholder.equals(Object)
-RemovedMethod: androidx.compose.ui.text.SpanStyle#equals(Object):
-    Removed method androidx.compose.ui.text.SpanStyle.equals(Object)
-RemovedMethod: androidx.compose.ui.text.TextLayoutInput#equals(Object):
-    Removed method androidx.compose.ui.text.TextLayoutInput.equals(Object)
-RemovedMethod: androidx.compose.ui.text.TextLayoutResult#equals(Object):
-    Removed method androidx.compose.ui.text.TextLayoutResult.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextDecoration#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextDecoration.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextGeometricTransform#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextGeometricTransform.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextIndent#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextIndent.equals(Object)
diff --git a/compose/ui/ui-text/api/public_plus_experimental_1.3.0-beta03.txt b/compose/ui/ui-text/api/public_plus_experimental_1.3.0-beta03.txt
index 0e0b313..6d70e25 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_1.3.0-beta03.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_1.3.0-beta03.txt
@@ -202,7 +202,8 @@
     method public long getWordBoundary(int offset);
     method public boolean isLineEllipsized(int lineIndex);
     method public void paint(androidx.compose.ui.graphics.Canvas canvas, long color, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration);
-    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration);
+    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, long color, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration, androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle);
+    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration, androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
     property public abstract float height;
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 0e0b313..6d70e25 100644
--- a/compose/ui/ui-text/api/public_plus_experimental_current.txt
+++ b/compose/ui/ui-text/api/public_plus_experimental_current.txt
@@ -202,7 +202,8 @@
     method public long getWordBoundary(int offset);
     method public boolean isLineEllipsized(int lineIndex);
     method public void paint(androidx.compose.ui.graphics.Canvas canvas, long color, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration);
-    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration);
+    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, long color, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration, androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle);
+    method @androidx.compose.ui.text.ExperimentalTextApi public void paint(androidx.compose.ui.graphics.Canvas canvas, androidx.compose.ui.graphics.Brush brush, float alpha, androidx.compose.ui.graphics.Shadow? shadow, androidx.compose.ui.text.style.TextDecoration? textDecoration, androidx.compose.ui.graphics.drawscope.DrawStyle? drawStyle);
     property public abstract boolean didExceedMaxLines;
     property public abstract float firstBaseline;
     property public abstract float height;
diff --git a/compose/ui/ui-text/api/restricted_current.ignore b/compose/ui/ui-text/api/restricted_current.ignore
deleted file mode 100644
index 85b4b37..0000000
--- a/compose/ui/ui-text/api/restricted_current.ignore
+++ /dev/null
@@ -1,91 +0,0 @@
-// Baseline format: 1.0
-AddSealed: androidx.compose.ui.text.Paragraph:
-    Cannot add 'sealed' modifier to class androidx.compose.ui.text.Paragraph: Incompatible change
-
-
-AddedFinal: androidx.compose.ui.text.Paragraph#getBidiRunDirection(int):
-    Method androidx.compose.ui.text.Paragraph.getBidiRunDirection has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getBoundingBox(int):
-    Method androidx.compose.ui.text.Paragraph.getBoundingBox has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getCursorRect(int):
-    Method androidx.compose.ui.text.Paragraph.getCursorRect has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getDidExceedMaxLines():
-    Method androidx.compose.ui.text.Paragraph.getDidExceedMaxLines has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getFirstBaseline():
-    Method androidx.compose.ui.text.Paragraph.getFirstBaseline has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getHeight():
-    Method androidx.compose.ui.text.Paragraph.getHeight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getHorizontalPosition(int, boolean):
-    Method androidx.compose.ui.text.Paragraph.getHorizontalPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLastBaseline():
-    Method androidx.compose.ui.text.Paragraph.getLastBaseline has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineBottom(int):
-    Method androidx.compose.ui.text.Paragraph.getLineBottom has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineCount():
-    Method androidx.compose.ui.text.Paragraph.getLineCount has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineEnd(int, boolean):
-    Method androidx.compose.ui.text.Paragraph.getLineEnd has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineForOffset(int):
-    Method androidx.compose.ui.text.Paragraph.getLineForOffset has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineForVerticalPosition(float):
-    Method androidx.compose.ui.text.Paragraph.getLineForVerticalPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineHeight(int):
-    Method androidx.compose.ui.text.Paragraph.getLineHeight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineLeft(int):
-    Method androidx.compose.ui.text.Paragraph.getLineLeft has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineRight(int):
-    Method androidx.compose.ui.text.Paragraph.getLineRight has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineStart(int):
-    Method androidx.compose.ui.text.Paragraph.getLineStart has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineTop(int):
-    Method androidx.compose.ui.text.Paragraph.getLineTop has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getLineWidth(int):
-    Method androidx.compose.ui.text.Paragraph.getLineWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getMaxIntrinsicWidth():
-    Method androidx.compose.ui.text.Paragraph.getMaxIntrinsicWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getMinIntrinsicWidth():
-    Method androidx.compose.ui.text.Paragraph.getMinIntrinsicWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getOffsetForPosition(long):
-    Method androidx.compose.ui.text.Paragraph.getOffsetForPosition has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getParagraphDirection(int):
-    Method androidx.compose.ui.text.Paragraph.getParagraphDirection has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getPathForRange(int, int):
-    Method androidx.compose.ui.text.Paragraph.getPathForRange has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getPlaceholderRects():
-    Method androidx.compose.ui.text.Paragraph.getPlaceholderRects has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getWidth():
-    Method androidx.compose.ui.text.Paragraph.getWidth has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#getWordBoundary(int):
-    Method androidx.compose.ui.text.Paragraph.getWordBoundary has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#isLineEllipsized(int):
-    Method androidx.compose.ui.text.Paragraph.isLineEllipsized has added 'final' qualifier
-AddedFinal: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration):
-    Method androidx.compose.ui.text.Paragraph.paint has added 'final' qualifier
-
-
-DefaultValueChange: androidx.compose.ui.text.Paragraph#getLineEnd(int, boolean) parameter #1:
-    Attempted to remove default value from parameter visibleEnd in androidx.compose.ui.text.Paragraph.getLineEnd
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #1:
-    Attempted to remove default value from parameter color in androidx.compose.ui.text.Paragraph.paint
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #2:
-    Attempted to remove default value from parameter shadow in androidx.compose.ui.text.Paragraph.paint
-DefaultValueChange: androidx.compose.ui.text.Paragraph#paint(androidx.compose.ui.graphics.Canvas, long, androidx.compose.ui.graphics.Shadow, androidx.compose.ui.text.style.TextDecoration) parameter #3:
-    Attempted to remove default value from parameter textDecoration in androidx.compose.ui.text.Paragraph.paint
-
-
-RemovedMethod: androidx.compose.ui.text.ParagraphStyle#equals(Object):
-    Removed method androidx.compose.ui.text.ParagraphStyle.equals(Object)
-RemovedMethod: androidx.compose.ui.text.Placeholder#equals(Object):
-    Removed method androidx.compose.ui.text.Placeholder.equals(Object)
-RemovedMethod: androidx.compose.ui.text.SpanStyle#equals(Object):
-    Removed method androidx.compose.ui.text.SpanStyle.equals(Object)
-RemovedMethod: androidx.compose.ui.text.TextLayoutInput#equals(Object):
-    Removed method androidx.compose.ui.text.TextLayoutInput.equals(Object)
-RemovedMethod: androidx.compose.ui.text.TextLayoutResult#equals(Object):
-    Removed method androidx.compose.ui.text.TextLayoutResult.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextDecoration#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextDecoration.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextGeometricTransform#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextGeometricTransform.equals(Object)
-RemovedMethod: androidx.compose.ui.text.style.TextIndent#equals(Object):
-    Removed method androidx.compose.ui.text.style.TextIndent.equals(Object)
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
index 913cc97..bd58e67 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/AndroidParagraphTest.kt
@@ -31,8 +31,12 @@
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.ShaderBrush
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
 import androidx.compose.ui.text.android.InternalPlatformTextApi
@@ -1270,6 +1274,20 @@
         assertThat(paragraph.textPaint.color).isEqualTo(color.toArgb())
     }
 
+    @OptIn(ExperimentalTextApi::class)
+    @Test
+    fun testSpanStyle_brush_appliedOnTextPaint() {
+        val brush = Brush.horizontalGradient(listOf(Color.Red, Color.Blue)) as ShaderBrush
+        val paragraph = simpleParagraph(
+            text = "",
+            style = TextStyle(brush = brush),
+            width = 0.0f
+        )
+
+        assertThat(paragraph.textPaint.brush).isEqualTo(brush)
+        assertThat(paragraph.textPaint.brushSize).isEqualTo(Size(paragraph.width, paragraph.height))
+    }
+
     @Test
     fun testTextStyle_letterSpacingInEm_appliedOnTextPaint() {
         val letterSpacing = 2
@@ -1646,6 +1664,24 @@
     }
 
     @Test
+    fun testPaint_can_change_drawStyle_to_Stroke() {
+        val paragraph = simpleParagraph(
+            text = "",
+            width = 0.0f
+        )
+        assertThat(paragraph.textPaint.style).isEqualTo(Paint.Style.FILL)
+
+        val stroke = Stroke(width = 4f, miter = 2f, cap = StrokeCap.Square, join = StrokeJoin.Bevel)
+        val canvas = Canvas(android.graphics.Canvas())
+        paragraph.paint(canvas, drawStyle = stroke)
+        assertThat(paragraph.textPaint.style).isEqualTo(Paint.Style.STROKE)
+        assertThat(paragraph.textPaint.strokeWidth).isEqualTo(4f)
+        assertThat(paragraph.textPaint.strokeMiter).isEqualTo(2f)
+        assertThat(paragraph.textPaint.strokeCap).isEqualTo(Paint.Cap.SQUARE)
+        assertThat(paragraph.textPaint.strokeJoin).isEqualTo(Paint.Join.BEVEL)
+    }
+
+    @Test
     fun testSpanStyle_baselineShift_appliedAsSpan() {
         // baselineShift is reset in the Android Layout constructor.
         // therefore we cannot apply them on paint, have to use spans.
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
index 7369bc7..793fa1e 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/ParagraphIntegrationTest.kt
@@ -27,6 +27,7 @@
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.SolidColor
 import androidx.compose.ui.graphics.asAndroidBitmap
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.text.FontTestData.Companion.BASIC_KERN_FONT
 import androidx.compose.ui.text.FontTestData.Companion.BASIC_MEASURE_FONT
 import androidx.compose.ui.text.FontTestData.Companion.FONT_100_REGULAR
@@ -3755,8 +3756,10 @@
                 width = paragraphWidth
             )
 
-            assertThat(paragraphWithoutAlpha.bitmap(brush, 0.5f))
-                .isEqualToBitmap(paragraphWithAlpha.bitmap())
+            val firstBitmap = paragraphWithoutAlpha.bitmap(brush, 0.5f)
+            val secondBitmap = paragraphWithAlpha.bitmap()
+
+            assertThat(firstBitmap).isEqualToBitmap(secondBitmap)
         }
     }
 
@@ -4397,6 +4400,51 @@
         }
     }
 
+    @Test
+    fun paint_withDrawStyle_changesVisual() {
+        with(defaultDensity) {
+            val text = "aaa"
+            // FontSize doesn't matter here, but it should be big enough for bitmap comparison.
+            val fontSize = 100.sp
+            val fontSizeInPx = fontSize.toPx()
+            val paragraphWidth = fontSizeInPx * text.length
+
+            val baseParagraph = simpleParagraph(
+                text = text,
+                style = TextStyle(fontSize = fontSize, color = Color.Red),
+                width = paragraphWidth
+            )
+
+            assertThat(baseParagraph.bitmap(drawStyle = Stroke()))
+                .isNotEqualToBitmap(baseParagraph.bitmap())
+        }
+    }
+
+    @Test
+    fun paint_withDrawStyle_doesNotResetWithNull() {
+        with(defaultDensity) {
+            val text = "aaa"
+            // FontSize doesn't matter here, but it should be big enough for bitmap comparison.
+            val fontSize = 100.sp
+            val fontSizeInPx = fontSize.toPx()
+            val paragraphWidth = fontSizeInPx * text.length
+
+            val baseParagraph = simpleParagraph(
+                text = text,
+                style = TextStyle(
+                    fontSize = fontSize,
+                    color = Color.Red
+                ),
+                width = paragraphWidth
+            )
+
+            val firstBitmap = baseParagraph.bitmap(drawStyle = Stroke(4f))
+            val secondBitmap = baseParagraph.bitmap(drawStyle = null)
+
+            assertThat(firstBitmap).isEqualToBitmap(secondBitmap)
+        }
+    }
+
     private fun simpleParagraph(
         text: String = "",
         style: TextStyle? = null,
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
index 3a0be4f..a476fc4 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/TextTestExtensions.kt
@@ -20,6 +20,8 @@
 import android.graphics.Canvas
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.graphics.Brush
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.text.font.AndroidFontLoader
 import androidx.compose.ui.text.font.AndroidFontResolveInterceptor
 import androidx.compose.ui.text.font.AsyncTypefaceCache
@@ -30,30 +32,54 @@
 import androidx.compose.ui.text.font.PlatformFontLoader
 import androidx.compose.ui.text.font.PlatformResolveInterceptor
 import androidx.compose.ui.text.font.TypefaceRequestCache
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.text.style.TextDecoration
 import kotlin.math.ceil
 import kotlin.math.roundToInt
 
-fun Paragraph.bitmap(): Bitmap {
-    val bitmap = Bitmap.createBitmap(
-        width.toIntPx(),
-        height.toIntPx(),
-        Bitmap.Config.ARGB_8888
-    )
-    this.paint(androidx.compose.ui.graphics.Canvas(Canvas(bitmap)))
-    return bitmap
-}
-
 @OptIn(ExperimentalTextApi::class)
 fun Paragraph.bitmap(
-    brush: Brush,
-    alpha: Float
+    color: Color = Color.Unspecified,
+    shadow: Shadow? = null,
+    textDecoration: TextDecoration? = null,
+    drawStyle: DrawStyle? = null
 ): Bitmap {
     val bitmap = Bitmap.createBitmap(
         width.toIntPx(),
         height.toIntPx(),
         Bitmap.Config.ARGB_8888
     )
-    this.paint(androidx.compose.ui.graphics.Canvas(Canvas(bitmap)), brush, alpha)
+    this.paint(
+        canvas = androidx.compose.ui.graphics.Canvas(Canvas(bitmap)),
+        color = color,
+        shadow = shadow,
+        textDecoration = textDecoration,
+        drawStyle = drawStyle
+    )
+    return bitmap
+}
+
+@OptIn(ExperimentalTextApi::class)
+fun Paragraph.bitmap(
+    brush: Brush,
+    alpha: Float,
+    shadow: Shadow? = null,
+    textDecoration: TextDecoration? = null,
+    drawStyle: DrawStyle? = null
+): Bitmap {
+    val bitmap = Bitmap.createBitmap(
+        width.toIntPx(),
+        height.toIntPx(),
+        Bitmap.Config.ARGB_8888
+    )
+    this.paint(
+        canvas = androidx.compose.ui.graphics.Canvas(Canvas(bitmap)),
+        brush = brush,
+        alpha = alpha,
+        shadow = shadow,
+        textDecoration = textDecoration,
+        drawStyle = drawStyle
+    )
     return bitmap
 }
 
diff --git a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt
index 31fddb9..bf45dc8 100644
--- a/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt
+++ b/compose/ui/ui-text/src/androidAndroidTest/kotlin/androidx/compose/ui/text/platform/AndroidTextPaintTest.kt
@@ -21,10 +21,16 @@
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.graphics.Brush
 import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.PathEffect
 import androidx.compose.ui.graphics.Shader
 import androidx.compose.ui.graphics.ShaderBrush
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.asAndroidPathEffect
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.text.style.TextDecoration
 import androidx.test.ext.junit.runners.AndroidJUnit4
@@ -418,5 +424,78 @@
         assertThat(textPaint.shadowLayerColor).isEqualTo(color.toArgb())
     }
 
+    @Test
+    fun drawStyle_defaultValue() {
+        val textPaint = defaultTextPaint
+        assertThat(textPaint.style).isEqualTo(Paint.Style.FILL)
+    }
+
+    @Test
+    fun setDrawStyle_withNull() {
+        val textPaint = defaultTextPaint
+        textPaint.setDrawStyle(null)
+        assertThat(textPaint.style).isEqualTo(Paint.Style.FILL)
+    }
+
+    @Test
+    fun setDrawStyle_withFill() {
+        val textPaint = defaultTextPaint
+        textPaint.setDrawStyle(Fill)
+        assertThat(textPaint.style).isEqualTo(Paint.Style.FILL)
+    }
+
+    @Test
+    fun setDrawStyle_withStroke() {
+        val textPaint = defaultTextPaint
+        val pathEffect = PathEffect.cornerPathEffect(4f)
+        textPaint.setDrawStyle(
+            Stroke(
+                width = 4f,
+                miter = 2f,
+                join = StrokeJoin.Bevel,
+                cap = StrokeCap.Square,
+                pathEffect = pathEffect
+            )
+        )
+        assertThat(textPaint.style).isEqualTo(Paint.Style.STROKE)
+        assertThat(textPaint.strokeWidth).isEqualTo(4f)
+        assertThat(textPaint.strokeMiter).isEqualTo(2f)
+        assertThat(textPaint.strokeJoin).isEqualTo(Paint.Join.BEVEL)
+        assertThat(textPaint.strokeCap).isEqualTo(Paint.Cap.SQUARE)
+        assertThat(textPaint.pathEffect).isEqualTo(pathEffect.asAndroidPathEffect())
+    }
+
+    @Test
+    fun setDrawStyle_withStrokeThenFill() {
+        val textPaint = defaultTextPaint
+        textPaint.setDrawStyle(
+            Stroke(
+                width = 4f,
+                miter = 2f,
+                join = StrokeJoin.Bevel,
+                cap = StrokeCap.Square
+            )
+        )
+        textPaint.setDrawStyle(Fill)
+        assertThat(textPaint.style).isEqualTo(Paint.Style.FILL)
+    }
+
+    @Test
+    fun setDrawStyle_changeDrawStyleToNull() {
+        val textPaint = defaultTextPaint
+        textPaint.setDrawStyle(
+            Stroke(
+                width = 4f,
+                miter = 2f,
+                join = StrokeJoin.Bevel,
+                cap = StrokeCap.Square
+            )
+        )
+        assertThat(textPaint.style).isEqualTo(Paint.Style.STROKE)
+
+        textPaint.setDrawStyle(null)
+        assertThat(textPaint.style).isEqualTo(Paint.Style.STROKE)
+    }
+
     private val defaultTextPaint get() = AndroidTextPaint(flags = 0, density = 1.0f)
 }
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
index 8638995..94da64b6 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/AndroidParagraph.android.kt
@@ -31,6 +31,7 @@
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.asComposePath
+import androidx.compose.ui.graphics.drawscope.DrawStyle
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.text.android.InternalPlatformTextApi
 import androidx.compose.ui.text.android.LayoutCompat.ALIGN_CENTER
@@ -427,15 +428,25 @@
             setTextDecoration(textDecoration)
         }
 
-        val nativeCanvas = canvas.nativeCanvas
-        if (didExceedMaxLines) {
-            nativeCanvas.save()
-            nativeCanvas.clipRect(0f, 0f, width, height)
+        paint(canvas)
+    }
+
+    @OptIn(ExperimentalTextApi::class)
+    override fun paint(
+        canvas: Canvas,
+        color: Color,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
+    ) {
+        with(textPaint) {
+            setColor(color)
+            setShadow(shadow)
+            setTextDecoration(textDecoration)
+            setDrawStyle(drawStyle)
         }
-        layout.paint(nativeCanvas)
-        if (didExceedMaxLines) {
-            nativeCanvas.restore()
-        }
+
+        paint(canvas)
     }
 
     @OptIn(ExperimentalTextApi::class)
@@ -444,14 +455,20 @@
         brush: Brush,
         alpha: Float,
         shadow: Shadow?,
-        textDecoration: TextDecoration?
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
     ) {
         with(textPaint) {
             setBrush(brush, Size(width, height), alpha)
             setShadow(shadow)
             setTextDecoration(textDecoration)
+            setDrawStyle(drawStyle)
         }
 
+        paint(canvas)
+    }
+
+    private fun paint(canvas: Canvas) {
         val nativeCanvas = canvas.nativeCanvas
         if (didExceedMaxLines) {
             nativeCanvas.save()
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Paragraph.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Paragraph.android.kt
index 7661837..330e76a 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Paragraph.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/Paragraph.android.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.compose.ui.text.style.TextDecoration
 
@@ -59,9 +60,18 @@
     @ExperimentalTextApi
     actual fun paint(
         canvas: Canvas,
+        color: Color,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
+    )
+    @ExperimentalTextApi
+    actual fun paint(
+        canvas: Canvas,
         brush: Brush,
         alpha: Float,
         shadow: Shadow?,
-        textDecoration: TextDecoration?
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
     )
 }
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
index 5a9cc175..ebb94c0 100644
--- a/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
+++ b/compose/ui/ui-text/src/androidMain/kotlin/androidx/compose/ui/text/platform/AndroidTextPaint.android.kt
@@ -16,7 +16,9 @@
 
 package androidx.compose.ui.text.platform
 
+import android.graphics.Paint
 import android.text.TextPaint
+import androidx.annotation.VisibleForTesting
 import androidx.compose.ui.geometry.Size
 import androidx.compose.ui.geometry.isSpecified
 import androidx.compose.ui.graphics.Brush
@@ -24,6 +26,12 @@
 import androidx.compose.ui.graphics.ShaderBrush
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.StrokeCap
+import androidx.compose.ui.graphics.StrokeJoin
+import androidx.compose.ui.graphics.asAndroidPathEffect
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.graphics.drawscope.Fill
+import androidx.compose.ui.graphics.drawscope.Stroke
 import androidx.compose.ui.graphics.isSpecified
 import androidx.compose.ui.graphics.toArgb
 import androidx.compose.ui.text.platform.extensions.correctBlurRadius
@@ -35,10 +43,16 @@
     init {
         this.density = density
     }
+
     private var textDecoration: TextDecoration = TextDecoration.None
     private var shadow: Shadow = Shadow.None
-    private var brush: Brush? = null
-    private var brushSize: Size? = null
+
+    @VisibleForTesting
+    internal var brush: Brush? = null
+
+    @VisibleForTesting
+    internal var brushSize: Size? = null
+    private var drawStyle: DrawStyle? = null
 
     fun setTextDecoration(textDecoration: TextDecoration?) {
         if (textDecoration == null) return
@@ -100,6 +114,47 @@
             }
         }
     }
+
+    fun setDrawStyle(drawStyle: DrawStyle?) {
+        if (drawStyle == null) return
+        if (this.drawStyle != drawStyle) {
+            this.drawStyle = drawStyle
+            when (drawStyle) {
+                Fill -> {
+                    // Stroke properties such as strokeWidth, strokeMiter are not re-set because
+                    // Fill style should make those properties no-op. Next time the style is set
+                    // as Stroke, stroke properties get re-set as well.
+                    style = Style.FILL
+                }
+                is Stroke -> {
+                    style = Style.STROKE
+                    strokeWidth = drawStyle.width
+                    strokeMiter = drawStyle.miter
+                    strokeJoin = drawStyle.join.toAndroidJoin()
+                    strokeCap = drawStyle.cap.toAndroidCap()
+                    pathEffect = drawStyle.pathEffect?.asAndroidPathEffect()
+                }
+            }
+        }
+    }
+}
+
+private fun StrokeJoin.toAndroidJoin(): Paint.Join {
+    return when (this) {
+        StrokeJoin.Miter -> Paint.Join.MITER
+        StrokeJoin.Round -> Paint.Join.ROUND
+        StrokeJoin.Bevel -> Paint.Join.BEVEL
+        else -> Paint.Join.MITER
+    }
+}
+
+private fun StrokeCap.toAndroidCap(): Paint.Cap {
+    return when (this) {
+        StrokeCap.Butt -> Paint.Cap.BUTT
+        StrokeCap.Round -> Paint.Cap.ROUND
+        StrokeCap.Square -> Paint.Cap.SQUARE
+        else -> Paint.Cap.BUTT
+    }
 }
 
 /**
diff --git a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
index 701e2988..7003493 100644
--- a/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
+++ b/compose/ui/ui-text/src/commonMain/kotlin/androidx/compose/ui/text/Paragraph.kt
@@ -21,7 +21,11 @@
 import androidx.compose.ui.graphics.Canvas
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
+import androidx.compose.ui.graphics.ShaderBrush
 import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.SolidColor
+import androidx.compose.ui.graphics.drawscope.DrawStyle
+import androidx.compose.ui.graphics.drawscope.Fill
 import androidx.compose.ui.text.font.Font
 import androidx.compose.ui.text.font.FontFamily
 import androidx.compose.ui.text.platform.ActualParagraph
@@ -234,7 +238,19 @@
     fun getWordBoundary(offset: Int): TextRange
 
     /**
-     * Paint the paragraph to canvas, and also overrides some paint settings.
+     * Draws this paragraph onto given [canvas] while modifying supported draw properties. Any
+     * change caused by overriding parameters are permanent, meaning that they affect the
+     * subsequent paint calls.
+     *
+     * @param canvas Canvas to draw this paragraph on.
+     * @param color Applies to the default text paint color that's used by this paragraph. Text
+     * color spans are not affected. [Color.Unspecified] is treated as no-op.
+     * @param shadow Applies to the default text paint shadow that's used by this paragraph. Text
+     * shadow spans are not affected. Passing this value as [Shadow.None] or `null` removes any
+     * existing shadow on this paragraph.
+     * @param textDecoration Applies to the default text paint that's used by this paragraph. Spans
+     * that specify a TextDecoration are not affected. Passing this value as [TextDecoration.None]
+     * or `null` removes any existing TextDecoration on this paragraph.
      */
     fun paint(
         canvas: Canvas,
@@ -244,9 +260,54 @@
     )
 
     /**
-     * Paint the paragraph to canvas, and also overrides some paint settings.
+     * Draws this paragraph onto given [canvas] while modifying supported draw properties. Any
+     * change caused by overriding parameters are permanent, meaning that they affect the
+     * subsequent paint calls.
      *
-     * If not overridden, this function @throws [UnsupportedOperationException].
+     * @param canvas Canvas to draw this paragraph on.
+     * @param color Applies to the default text paint color that's used by this paragraph. Text
+     * color spans are not affected. [Color.Unspecified] is treated as no-op.
+     * @param shadow Applies to the default text paint shadow that's used by this paragraph. Text
+     * shadow spans are not affected. Passing this value as [Shadow.None] or `null` removes any
+     * existing shadow on this paragraph.
+     * @param textDecoration Applies to the default text paint that's used by this paragraph. Spans
+     * that specify a TextDecoration are not affected. Passing this value as [TextDecoration.None]
+     * or `null` removes any existing TextDecoration on this paragraph.
+     * @param drawStyle Applies to the default text paint style that's used by this paragraph. Spans
+     * that specify a DrawStyle are not affected. Passing this value as `null` is treated equally
+     * to passing it as [Fill].
+     */
+    @ExperimentalTextApi
+    fun paint(
+        canvas: Canvas,
+        color: Color = Color.Unspecified,
+        shadow: Shadow? = null,
+        textDecoration: TextDecoration? = null,
+        drawStyle: DrawStyle? = null
+    )
+
+    /**
+     * Draws this paragraph onto given [canvas] while modifying supported draw properties. Any
+     * change caused by overriding parameters are permanent, meaning that they affect the
+     * subsequent paint calls.
+     *
+     * @param canvas Canvas to draw this paragraph on.
+     * @param brush Applies to the default text paint shader that's used by this paragraph. Text
+     * brush spans are not affected. If brush is type of [SolidColor], color's alpha value is
+     * modulated by [alpha] parameter and gets applied as a color. If brush is type of
+     * [ShaderBrush], its internal shader is created using this paragraph's layout size.
+     * @param alpha Applies to the default text paint alpha that's used by this paragraph. Text
+     * alpha spans are not affected. [Float.NaN] is treated as no-op. All other values are coerced
+     * into [0f, 1f] range.
+     * @param shadow Applies to the default text paint shadow that's used by this paragraph. Text
+     * shadow spans are not affected. Passing this value as [Shadow.None] or `null` removes any
+     * existing shadow on this paragraph.
+     * @param textDecoration Applies to the default text paint that's used by this paragraph. Spans
+     * that specify a TextDecoration are not affected. Passing this value as [TextDecoration.None]
+     * or `null` removes any existing TextDecoration on this paragraph.
+     * @param drawStyle Applies to the default text paint style that's used by this paragraph. Spans
+     * that specify a DrawStyle are not affected. Passing this value as `null` is treated equally
+     * to passing it as [Fill].
      */
     @ExperimentalTextApi
     fun paint(
@@ -254,7 +315,8 @@
         brush: Brush,
         alpha: Float = Float.NaN,
         shadow: Shadow? = null,
-        textDecoration: TextDecoration? = null
+        textDecoration: TextDecoration? = null,
+        drawStyle: DrawStyle? = null
     )
 }
 
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Paragraph.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Paragraph.skiko.kt
index 7661837..330e76a 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Paragraph.skiko.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/Paragraph.skiko.kt
@@ -23,6 +23,7 @@
 import androidx.compose.ui.graphics.Color
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shadow
+import androidx.compose.ui.graphics.drawscope.DrawStyle
 import androidx.compose.ui.text.style.ResolvedTextDirection
 import androidx.compose.ui.text.style.TextDecoration
 
@@ -59,9 +60,18 @@
     @ExperimentalTextApi
     actual fun paint(
         canvas: Canvas,
+        color: Color,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
+    )
+    @ExperimentalTextApi
+    actual fun paint(
+        canvas: Canvas,
         brush: Brush,
         alpha: Float,
         shadow: Shadow?,
-        textDecoration: TextDecoration?
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
     )
 }
\ No newline at end of file
diff --git a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/SkiaParagraph.skiko.kt b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/SkiaParagraph.skiko.kt
index 15ef392..e838890 100644
--- a/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/SkiaParagraph.skiko.kt
+++ b/compose/ui/ui-text/src/skikoMain/kotlin/androidx/compose/ui/text/SkiaParagraph.skiko.kt
@@ -25,6 +25,7 @@
 import androidx.compose.ui.graphics.Path
 import androidx.compose.ui.graphics.Shadow
 import androidx.compose.ui.graphics.asSkiaPath
+import androidx.compose.ui.graphics.drawscope.DrawStyle
 import androidx.compose.ui.graphics.nativeCanvas
 import androidx.compose.ui.graphics.toComposeRect
 import androidx.compose.ui.text.platform.SkiaParagraphIntrinsics
@@ -290,7 +291,6 @@
         }
     }
 
-    // TODO(b/229518449): Implement an alternative to paint function that takes a brush.
     override fun paint(
         canvas: Canvas,
         color: Color,
@@ -312,10 +312,32 @@
     @ExperimentalTextApi
     override fun paint(
         canvas: Canvas,
+        color: Color,
+        shadow: Shadow?,
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
+    ) {
+        para = layouter.layoutParagraph(
+            width = width,
+            maxLines = maxLines,
+            ellipsis = ellipsisChar,
+            color = color,
+            shadow = shadow,
+            textDecoration = textDecoration
+        )
+
+        para.paint(canvas.nativeCanvas, 0.0f, 0.0f)
+    }
+
+    // TODO(b/229518449): Implement this paint function that draws text with a Brush.
+    @ExperimentalTextApi
+    override fun paint(
+        canvas: Canvas,
         brush: Brush,
         alpha: Float,
         shadow: Shadow?,
-        textDecoration: TextDecoration?
+        textDecoration: TextDecoration?,
+        drawStyle: DrawStyle?
     ) {
         throw UnsupportedOperationException(
             "Using brush for painting the paragraph is a separate functionality that " +
diff --git a/compose/ui/ui-unit/api/current.ignore b/compose/ui/ui-unit/api/current.ignore
deleted file mode 100644
index 0817014..0000000
--- a/compose/ui/ui-unit/api/current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.compose.ui.unit.Constraints#Constraints():
-    Removed constructor androidx.compose.ui.unit.Constraints()
-RemovedMethod: androidx.compose.ui.unit.Dp#Dp():
-    Removed constructor androidx.compose.ui.unit.Dp()
-RemovedMethod: androidx.compose.ui.unit.DpOffset#DpOffset():
-    Removed constructor androidx.compose.ui.unit.DpOffset()
-RemovedMethod: androidx.compose.ui.unit.DpSize#DpSize():
-    Removed constructor androidx.compose.ui.unit.DpSize()
-RemovedMethod: androidx.compose.ui.unit.IntOffset#IntOffset():
-    Removed constructor androidx.compose.ui.unit.IntOffset()
-RemovedMethod: androidx.compose.ui.unit.IntSize#IntSize():
-    Removed constructor androidx.compose.ui.unit.IntSize()
-RemovedMethod: androidx.compose.ui.unit.TextUnit#TextUnit():
-    Removed constructor androidx.compose.ui.unit.TextUnit()
-RemovedMethod: androidx.compose.ui.unit.TextUnitType#TextUnitType():
-    Removed constructor androidx.compose.ui.unit.TextUnitType()
-RemovedMethod: androidx.compose.ui.unit.Velocity#Velocity():
-    Removed constructor androidx.compose.ui.unit.Velocity()
diff --git a/compose/ui/ui-unit/api/restricted_current.ignore b/compose/ui/ui-unit/api/restricted_current.ignore
deleted file mode 100644
index 0817014..0000000
--- a/compose/ui/ui-unit/api/restricted_current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-RemovedMethod: androidx.compose.ui.unit.Constraints#Constraints():
-    Removed constructor androidx.compose.ui.unit.Constraints()
-RemovedMethod: androidx.compose.ui.unit.Dp#Dp():
-    Removed constructor androidx.compose.ui.unit.Dp()
-RemovedMethod: androidx.compose.ui.unit.DpOffset#DpOffset():
-    Removed constructor androidx.compose.ui.unit.DpOffset()
-RemovedMethod: androidx.compose.ui.unit.DpSize#DpSize():
-    Removed constructor androidx.compose.ui.unit.DpSize()
-RemovedMethod: androidx.compose.ui.unit.IntOffset#IntOffset():
-    Removed constructor androidx.compose.ui.unit.IntOffset()
-RemovedMethod: androidx.compose.ui.unit.IntSize#IntSize():
-    Removed constructor androidx.compose.ui.unit.IntSize()
-RemovedMethod: androidx.compose.ui.unit.TextUnit#TextUnit():
-    Removed constructor androidx.compose.ui.unit.TextUnit()
-RemovedMethod: androidx.compose.ui.unit.TextUnitType#TextUnitType():
-    Removed constructor androidx.compose.ui.unit.TextUnitType()
-RemovedMethod: androidx.compose.ui.unit.Velocity#Velocity():
-    Removed constructor androidx.compose.ui.unit.Velocity()
diff --git a/compose/ui/ui/api/current.ignore b/compose/ui/ui/api/current.ignore
deleted file mode 100644
index d3f9c1b..0000000
--- a/compose/ui/ui/api/current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.ui.layout.IntrinsicMeasurableKt:
-    Removed class androidx.compose.ui.layout.IntrinsicMeasurableKt
-RemovedClass: androidx.compose.ui.layout.MeasureScopeKt:
-    Removed class androidx.compose.ui.layout.MeasureScopeKt
-
-
-RemovedMethod: androidx.compose.ui.focus.FocusDirection.Companion#getIn():
-    Removed method androidx.compose.ui.focus.FocusDirection.Companion.getIn()
-RemovedMethod: androidx.compose.ui.focus.FocusDirection.Companion#getOut():
-    Removed method androidx.compose.ui.focus.FocusDirection.Companion.getOut()
diff --git a/compose/ui/ui/api/restricted_current.ignore b/compose/ui/ui/api/restricted_current.ignore
deleted file mode 100644
index d3f9c1b..0000000
--- a/compose/ui/ui/api/restricted_current.ignore
+++ /dev/null
@@ -1,11 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.compose.ui.layout.IntrinsicMeasurableKt:
-    Removed class androidx.compose.ui.layout.IntrinsicMeasurableKt
-RemovedClass: androidx.compose.ui.layout.MeasureScopeKt:
-    Removed class androidx.compose.ui.layout.MeasureScopeKt
-
-
-RemovedMethod: androidx.compose.ui.focus.FocusDirection.Companion#getIn():
-    Removed method androidx.compose.ui.focus.FocusDirection.Companion.getIn()
-RemovedMethod: androidx.compose.ui.focus.FocusDirection.Companion#getOut():
-    Removed method androidx.compose.ui.focus.FocusDirection.Companion.getOut()
diff --git a/compose/ui/ui/build.gradle b/compose/ui/ui/build.gradle
index bba3326..c26b7d4 100644
--- a/compose/ui/ui/build.gradle
+++ b/compose/ui/ui/build.gradle
@@ -57,6 +57,7 @@
         implementation("androidx.autofill:autofill:1.0.0")
         implementation(libs.kotlinCoroutinesAndroid)
 
+        implementation("androidx.activity:activity-ktx:1.5.1")
         implementation("androidx.core:core:1.5.0")
         implementation('androidx.collection:collection:1.0.0')
         implementation("androidx.customview:customview-poolingcontainer:1.0.0")
@@ -151,6 +152,9 @@
                 implementation("androidx.autofill:autofill:1.0.0")
                 implementation(libs.kotlinCoroutinesAndroid)
 
+                implementation("androidx.activity:activity-ktx:1.5.1")
+                implementation("androidx.core:core:1.5.0")
+                implementation('androidx.collection:collection:1.0.0')
                 implementation("androidx.customview:customview-poolingcontainer:1.0.0")
                 implementation("androidx.savedstate:savedstate-ktx:1.2.0")
                 implementation("androidx.lifecycle:lifecycle-common-java8:2.3.0")
@@ -220,7 +224,7 @@
                 implementation(project(":test:screenshot:screenshot"))
                 implementation("androidx.recyclerview:recyclerview:1.3.0-alpha02")
                 implementation("androidx.core:core-ktx:1.1.0")
-                implementation("androidx.activity:activity-compose:1.3.1")
+                implementation("androidx.activity:activity-compose:1.5.1")
             }
 
             desktopTest.dependencies {
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
index dd05bf0..5f26bec 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/UiDemos.kt
@@ -28,6 +28,7 @@
 import androidx.compose.ui.demos.focus.ClickableInLazyColumnDemo
 import androidx.compose.ui.demos.focus.ConditionalFocusabilityDemo
 import androidx.compose.ui.demos.focus.CustomFocusOrderDemo
+import androidx.compose.ui.demos.focus.ExplicitEnterExitWithCustomFocusEnterExitDemo
 import androidx.compose.ui.demos.focus.FocusInDialogDemo
 import androidx.compose.ui.demos.focus.FocusInPopupDemo
 import androidx.compose.ui.demos.focus.FocusManagerMoveFocusDemo
@@ -75,6 +76,7 @@
 import androidx.compose.ui.demos.viewinterop.EditTextInteropDemo
 import androidx.compose.ui.demos.viewinterop.FocusTransferDemo
 import androidx.compose.ui.demos.viewinterop.NestedScrollInteropComposeParentWithAndroidChild
+import androidx.compose.ui.demos.viewinterop.ResizeComposeViewDemo
 import androidx.compose.ui.demos.viewinterop.ViewComposeViewNestedScrollInteropDemo
 import androidx.compose.ui.demos.viewinterop.ViewInteropDemo
 import androidx.compose.ui.samples.NestedScrollConnectionSample
@@ -143,6 +145,9 @@
         ComposableDemo("1D Focus Search") { OneDimensionalFocusSearchDemo() },
         ComposableDemo("2D Focus Search") { TwoDimensionalFocusSearchDemo() },
         ComposableDemo("Custom Focus Order") { CustomFocusOrderDemo() },
+        ComposableDemo("Explicit Enter/Exit Focus Group") {
+            ExplicitEnterExitWithCustomFocusEnterExitDemo()
+        },
         ComposableDemo("Cancel Focus Move") { CancelFocusDemo() },
         ComposableDemo("FocusManager.moveFocus()") { FocusManagerMoveFocusDemo() },
         ComposableDemo("Capture/Free Focus") { CaptureFocusDemo() },
@@ -206,7 +211,8 @@
         ComplexTouchInterop,
         ComposableDemo("TextField Interop") { EditTextInteropDemo() },
         ComposableDemo("Focus Transfer") { FocusTransferDemo() },
-        NestedScrollInteropDemos
+        NestedScrollInteropDemos,
+        ComposableDemo("Resize ComposeView") { ResizeComposeViewDemo() },
     )
 )
 
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ExplicitEnterExitWithCustomFocusEnterExitDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ExplicitEnterExitWithCustomFocusEnterExitDemo.kt
new file mode 100644
index 0000000..165c9ad
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/focus/ExplicitEnterExitWithCustomFocusEnterExitDemo.kt
@@ -0,0 +1,100 @@
+/*
+ * 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.
+ */
+
+@file:OptIn(ExperimentalFoundationApi::class)
+
+package androidx.compose.ui.demos.focus
+
+import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.focusGroup
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.material.Button
+import androidx.compose.material.Text
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.remember
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Enter
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
+import androidx.compose.ui.focus.FocusRequester
+import androidx.compose.ui.focus.FocusRequester.Companion.Default
+import androidx.compose.ui.focus.focusProperties
+import androidx.compose.ui.focus.focusRequester
+import androidx.compose.ui.input.InputMode
+import androidx.compose.ui.platform.LocalInputModeManager
+
+@OptIn(ExperimentalComposeUiApi::class, ExperimentalFoundationApi::class)
+@Composable
+fun ExplicitEnterExitWithCustomFocusEnterExitDemo() {
+    val (top, row, item1, item2, item3, bottom) = remember { FocusRequester.createRefs() }
+    val inputModeManager = LocalInputModeManager.current
+    Column {
+        Text("""
+            Click on the top button to request focus on the row, which focuses on item 2.
+            Entering the row from the top focuses on item 1.
+            Entering the row from the bottom focusses on item 3.
+            Exiting the row from the left focuses on the top button.
+            Exiting the row from right focuses on the bottom button.
+            """.trimIndent()
+        )
+        Button(
+            >
+                // If users click on the button instead of using the keyboard
+                // to press the button, we manually switch to Keyboard mode,
+                // since buttons are not focusable in touch mode.
+                if (inputModeManager.inputMode != InputMode.Keyboard) {
+                    inputModeManager.requestInputMode(InputMode.Keyboard)
+                }
+                row.requestFocus()
+            },
+            modifier = Modifier.focusRequester(top)
+        ) {
+            Text("Top Button")
+        }
+
+        Row(
+            Modifier
+                .focusRequester(row)
+                .focusProperties {
+                    enter = {
+                        when (it) {
+                            Down -> item1
+                            Enter -> item2
+                            Up -> item3
+                            else -> Default
+                        }
+                    }
+                    exit = {
+                        when (it) {
+                            Left -> top
+                            Right -> bottom
+                            else -> Default
+                        }
+                    }
+                }
+                .focusGroup()
+        ) {
+            Button({}, Modifier.focusRequester(item1)) { Text("1") }
+            Button({}, Modifier.focusRequester(item2)) { Text("2") }
+            Button({}, Modifier.focusRequester(item3)) { Text("3") }
+        }
+        Button({}, Modifier.focusRequester(bottom)) { Text("Bottom Button") }
+    }
+}
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ResizeComposeViewDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ResizeComposeViewDemo.kt
new file mode 100644
index 0000000..28ae4b0
--- /dev/null
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/viewinterop/ResizeComposeViewDemo.kt
@@ -0,0 +1,67 @@
+/*
+ * 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.compose.ui.demos.viewinterop
+
+import androidx.compose.foundation.background
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.size
+import androidx.compose.material.Text
+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.input.pointer.pointerInput
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.round
+import androidx.compose.ui.viewinterop.AndroidView
+
+@Composable
+fun ResizeComposeViewDemo() {
+    var size by remember { mutableStateOf(IntSize(0, 0)) }
+    Box(
+        Modifier
+            .fillMaxSize()
+            .pointerInput(Unit) {
+                awaitPointerEventScope {
+                    while (true) {
+                        val event = awaitPointerEvent()
+                        event.changes.forEach { it.consume() }
+                        val change = event.changes.firstOrNull()
+                        if (change != null) {
+                            val position = change.position.round()
+                            size = IntSize(position.x, position.y)
+                        }
+                    }
+                }
+            }) {
+        with(LocalDensity.current) {
+            AndroidView(factory = { context ->
+                ComposeView(context).apply {
+                    setContent {
+                        Box(Modifier.fillMaxSize().background(Color.Blue))
+                    }
+                }
+            }, modifier = Modifier.size(size.width.toDp(), size.height.toDp()))
+            Text("Touch the screen to change the size of the child ComposeView")
+        }
+    }
+}
\ No newline at end of file
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
index 3b5ad75..581f8fe7 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/FocusRequesterTest.kt
@@ -19,6 +19,7 @@
 import android.view.View
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.size
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.setValue
@@ -26,6 +27,7 @@
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalView
 import androidx.compose.ui.test.junit4.createComposeRule
+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
@@ -252,6 +254,155 @@
         }
     }
 
+    @Test
+    fun requestFocus_onDeactivatedNode_focusesOnChild() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun requestFocus_onDeactivatedParent_focusesOnChild() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val (focusRequester, initialFocus) = FocusRequester.createRefs()
+        rule.setFocusableContent {
+            Column(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .onFocusChanged { childFocusState = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .size(10.dp)
+                        .focusRequester(initialFocus)
+                        .focusTarget()
+                )
+            }
+        }
+        rule.runOnIdle { initialFocus.requestFocus() }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
+    @Test
+    fun requestFocus_intermediateDisabledParents_focusesOnDistantChild() {
+        // Arrange.
+        lateinit var childFocusState: FocusState
+        val focusRequester = FocusRequester()
+        rule.setFocusableContent {
+            Box(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .focusProperties { canFocus = false }
+                        .focusTarget()
+                ) {
+                    Box(
+                        modifier = Modifier
+                            .focusProperties { canFocus = false }
+                            .focusTarget()
+                    ) {
+                        Box(
+                            modifier = Modifier
+                                .onFocusChanged { childFocusState = it }
+                                .focusTarget()
+                        )
+                    }
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(childFocusState.isFocused).isTrue()
+        }
+    }
+
+    @OptIn(ExperimentalComposeUiApi::class)
+    @Test
+    fun requestFocus_onDeactivatedNode_performsFocusEnter() {
+        // Arrange.
+        lateinit var child1FocusState: FocusState
+        lateinit var child2FocusState: FocusState
+        val focusRequester = FocusRequester()
+        val child2 = FocusRequester()
+        rule.setFocusableContent {
+            Column(
+                modifier = Modifier
+                    .focusRequester(focusRequester)
+                    .focusProperties { enter = { child2 } }
+                    .focusProperties { canFocus = false }
+                    .focusTarget()
+            ) {
+                Box(
+                    modifier = Modifier
+                        .onFocusChanged { child1FocusState = it }
+                        .focusTarget()
+                )
+                Box(
+                    modifier = Modifier
+                        .focusRequester(child2)
+                        .onFocusChanged { child2FocusState = it }
+                        .focusTarget()
+                )
+            }
+        }
+
+        rule.runOnIdle {
+            // Act.
+            focusRequester.requestFocus()
+
+            // Assert.
+            assertThat(child1FocusState.isFocused).isFalse()
+            assertThat(child2FocusState.isFocused).isTrue()
+        }
+    }
+
     // The order in which the children are added to the hierarchy should not change the order
     // in which focus should be resolved.
     @Test
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalExitTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalExitTest.kt
index 701dd79..dc6b92d 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalExitTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalExitTest.kt
@@ -244,7 +244,45 @@
      *     |_________________________|
      */
     @Test
-    fun moveFocusExit_deactivatedParentCanRedirectExit() {
+    fun moveFocusExit_triggersExitPropertyOnFocusedItem() {
+        // Arrange.
+        val (parent, grandparent, other) = List(3) { mutableStateOf(false) }
+        val otherItem = FocusRequester()
+        rule.setContentForTest {
+            FocusableBox(grandparent, 0, 0, 50, 50) {
+                val customExit = Modifier.focusProperties { exit = { otherItem } }
+                FocusableBox(parent, 10, 10, 30, 30, deactivated = true) {
+                    FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus, modifier = customExit) }
+            }
+            FocusableBox(other, x = 0, y = 60, width = 10, height = 10, otherItem)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(Exit) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(grandparent.value).isFalse()
+            assertThat(other.value).isTrue()
+        }
+    }
+
+    /**
+     *      __________________________      __________
+     *     |  grandparent            |     |  other  |
+     *     |   ____________________  |     |_________|
+     *     |  |  parent           |  |
+     *     |  |   ______________  |  |
+     *     |  |  | focusedItem |  |  |
+     *     |  |  |_____________|  |  |
+     *     |  |___________________|  |
+     *     |_________________________|
+     */
+    @Test
+    fun explicitMoveFocusExit_deactivatedParentCanRedirectExit() {
         // Arrange.
         val (parent, grandparent, other) = List(3) { mutableStateOf(false) }
         val otherItem = FocusRequester()
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
new file mode 100644
index 0000000..829bc98
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitEnterTest.kt
@@ -0,0 +1,248 @@
+/*
+ * 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.compose.ui.focus
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.MutableState
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
+import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(Parameterized::class)
+class TwoDimensionalFocusTraversalImplicitEnterTest(param: Param) {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var focusManager: FocusManager
+    private val focusDirection = param.focusDirection
+    private val initialFocus: FocusRequester = FocusRequester()
+
+    /**
+     *                                _________
+     *                               |   Up   |
+     *                               |________|
+     *                 _______________________________________
+     *                |  focusedItem                         |
+     *                |   _________   _________   _________  |
+     *   _________    |  | child0 |  | child1 |  | child2 |  |    _________
+     *  |  Left  |    |  |________|  |________|  |________|  |   |  Right |
+     *  |________|    |   _________   _________   _________  |   |________|
+     *                |  | child3 |  | child4 |  | child5 |  |
+     *                |  |________|  |________|  |________|  |
+     *                |______________________________________|
+     *                                 _________
+     *                                |  Down  |
+     *                                |________|
+     */
+    @Test
+    fun moveFocusEnter_customChildIsFocused() {
+        // Arrange.
+        val (up, down, left, right, parent) = List(5) { mutableStateOf(false) }
+        val children = List(6) { mutableStateOf(false) }
+        var (upItem, downItem, leftItem, rightItem) = FocusRequester.createRefs()
+        val (child1, child2, child3, child4) = FocusRequester.createRefs()
+        val customFocusEnter = Modifier.focusProperties {
+            enter = {
+                when (it) {
+                    Left -> child1
+                    Up -> child2
+                    Down -> child3
+                    Right -> child4
+                    else -> error("Invalid Direction")
+                }
+            }
+        }
+        when (focusDirection) {
+            Left -> rightItem = initialFocus
+            Right -> leftItem = initialFocus
+            Up -> downItem = initialFocus
+            Down -> upItem = initialFocus
+        }
+        rule.setContentForTest {
+            FocusableBox(up, 50, 0, 10, 10, upItem)
+            FocusableBox(left, 0, 35, 10, 10, leftItem)
+            FocusableBox(parent, 20, 20, 70, 50, deactivated = true, modifier = customFocusEnter) {
+                FocusableBox(children[0], 30, 30, 10, 10)
+                FocusableBox(children[1], 50, 30, 10, 10, child1)
+                FocusableBox(children[2], 70, 30, 10, 10, child2)
+                FocusableBox(children[3], 30, 50, 10, 10, child3)
+                FocusableBox(children[4], 50, 50, 10, 10, child4)
+                FocusableBox(children[5], 70, 50, 10, 10)
+            }
+            FocusableBox(right, 100, 35, 10, 10, rightItem)
+            FocusableBox(down, 50, 90, 10, 10, downItem)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            when (focusDirection) {
+                Left -> assertThat(children.values).isExactly(
+                    false, true, false,
+                    false, false, false
+                )
+                Up -> assertThat(children.values).isExactly(
+                    false, false, true,
+                    false, false, false
+                )
+                Down -> assertThat(children.values).isExactly(
+                    false, false, false,
+                    true, false, false
+                )
+                Right -> assertThat(children.values).isExactly(
+                    false, false, false,
+                    false, true, false
+                )
+            }
+        }
+    }
+
+    /**
+     *                    _________
+     *                   |   Up   |
+     *                   |________|
+     *                 ________________
+     *                |  parent       |
+     *   _________    |   _________   |    _________
+     *  |  Left  |    |  | child0 |   |   |  Right |
+     *  |________|    |  |________|   |   |________|
+     *                |_______________|
+     *                    _________
+     *                   |  Down  |
+     *                   |________|
+     */
+    @Test
+    fun moveFocusEnter_blockFocusChange() {
+        // Arrange.
+        val (up, down, left, right, parent) = List(5) { mutableStateOf(false) }
+        val child = mutableStateOf(false)
+        var (upItem, downItem, leftItem, rightItem, childItem) = FocusRequester.createRefs()
+        val customFocusEnter = Modifier.focusProperties { enter = { Cancel } }
+        when (focusDirection) {
+            Left -> rightItem = initialFocus
+            Right -> leftItem = initialFocus
+            Up -> downItem = initialFocus
+            Down -> upItem = initialFocus
+        }
+        rule.setContentForTest {
+            FocusableBox(up, 30, 0, 10, 10, upItem)
+            FocusableBox(left, 0, 30, 10, 10, leftItem)
+            FocusableBox(parent, 20, 20, 70, 50, deactivated = true, modifier = customFocusEnter) {
+                FocusableBox(child, 30, 30, 10, 10, childItem)
+            }
+            FocusableBox(right, 100, 35, 10, 10, rightItem)
+            FocusableBox(down, 30, 90, 10, 10, downItem)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(child.value).isFalse()
+        }
+    }
+
+    /**
+     *                 _________
+     *                |   Up   |
+     *                |________|
+     *
+     *   _________     _________    _________
+     *  |  Left  |    |  item  |   |  Right |
+     *  |________|    |________|   |________|
+     *
+     *                 _________    _________
+     *                |  Down  |   | Other  |
+     *                |________|   |________|
+     */
+    @Test
+    fun focusOnItem_doesNotTriggerEnter() {
+        // Arrange.
+        val (up, down, left, right) = List(4) { mutableStateOf(false) }
+        val (item, other) = List(2) { mutableStateOf(false) }
+        var (upItem, downItem, leftItem, rightItem) = FocusRequester.createRefs()
+        val customFocusEnter = Modifier.focusProperties { enter = { Cancel } }
+        when (focusDirection) {
+            Left -> rightItem = initialFocus
+            Right -> leftItem = initialFocus
+            Up -> downItem = initialFocus
+            Down -> upItem = initialFocus
+        }
+        rule.setContentForTest {
+            FocusableBox(up, 20, 0, 10, 10, upItem)
+            FocusableBox(left, 0, 20, 10, 10, leftItem)
+            FocusableBox(item, 20, 20, 10, 10, modifier = customFocusEnter)
+            FocusableBox(right, 40, 20, 10, 10, rightItem)
+            FocusableBox(down, 20, 60, 10, 10, downItem)
+            FocusableBox(other, 60, 60, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(item.value).isTrue()
+            assertThat(other.value).isFalse()
+        }
+    }
+
+    // We need to wrap the inline class parameter in another class because Java can't instantiate
+    // the inline class.
+    class Param(val focusDirection: FocusDirection) {
+        override fun toString() = focusDirection.toString()
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = listOf(Left, Right, Up, Down).map { Param(it) }
+    }
+
+    private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
+        setContent {
+            focusManager = LocalFocusManager.current
+            composable()
+        }
+        rule.runOnIdle { initialFocus.requestFocus() }
+    }
+}
+
+private val List<MutableState<Boolean>>.values get() = this.map { it.value }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitExitTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitExitTest.kt
new file mode 100644
index 0000000..d40004d
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalImplicitExitTest.kt
@@ -0,0 +1,203 @@
+/*
+ * 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.compose.ui.focus
+
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.focus.FocusDirection.Companion.Down
+import androidx.compose.ui.focus.FocusDirection.Companion.Left
+import androidx.compose.ui.focus.FocusDirection.Companion.Right
+import androidx.compose.ui.focus.FocusDirection.Companion.Up
+import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
+import androidx.compose.ui.platform.LocalFocusManager
+import androidx.compose.ui.test.junit4.ComposeContentTestRule
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@OptIn(ExperimentalComposeUiApi::class)
+@MediumTest
+@RunWith(Parameterized::class)
+class TwoDimensionalFocusTraversalImplicitExitTest(param: Param) {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    private lateinit var focusManager: FocusManager
+    private val focusDirection = param.focusDirection
+    private val initialFocus: FocusRequester = FocusRequester()
+    private val focusedItem = mutableStateOf(false)
+
+    /**
+     *                    ________________
+     *                   |      top      |
+     *                   |_______________|
+     *     __________     ________________      __________
+     *    |   left  |    |  focusedItem  |     |  right  |
+     *    |_________|    |_______________|     |_________|
+     *                    ________________      __________
+     *                   |    bottom     |     |  other  |
+     *                   |_______________|     |_________|
+     */
+    @Test
+    fun implicitExit_notTriggeredWhenFocusLeavesItem() {
+        // Arrange.
+        val (focusedItem, other) = List(2) { mutableStateOf(false) }
+        val (left, right, top, bottom) = List(4) { mutableStateOf(false) }
+        val otherItem = FocusRequester()
+        rule.setContentForTest {
+            val customExit = Modifier.focusProperties { exit = { otherItem } }
+            FocusableBox(top, x = 20, y = 0, width = 10, height = 10, otherItem)
+            FocusableBox(left, x = 0, y = 20, width = 10, height = 10, otherItem)
+            FocusableBox(focusedItem, 20, 20, 10, 10, initialFocus, modifier = customExit)
+            FocusableBox(right, x = 40, y = 20, width = 10, height = 10, otherItem)
+            FocusableBox(bottom, x = 20, y = 40, width = 10, height = 10, otherItem)
+            FocusableBox(other, x = 20, y = 40, width = 10, height = 10, otherItem)
+        }
+
+            // Act.
+            val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+            // Assert.
+            rule.runOnIdle {
+                assertThat(movedFocusSuccessfully).isTrue()
+                assertThat(focusedItem.value).isFalse()
+                assertThat(other.value).isFalse()
+                when (focusDirection) {
+                    Left -> assertThat(left.value).isTrue()
+                    Right -> assertThat(right.value).isTrue()
+                    Up -> assertThat(top.value).isTrue()
+                    Down -> assertThat(bottom.value).isTrue()
+                    else -> error("Invalid FocusDirection")
+                }
+            }
+    }
+
+    /**
+     *      __________________________      __________
+     *     |  grandparent            |     |  other  |
+     *     |   ____________________  |     |_________|
+     *     |  |  parent           |  |
+     *     |  |   ______________  |  |
+     *     |  |  | focusedItem |  |  |
+     *     |  |  |_____________|  |  |
+     *     |  |___________________|  |
+     *     |_________________________|
+     */
+    @Test
+    fun implicitExit_deactivatedParentCanRedirectExit() {
+        // Arrange.
+        val (parent, grandparent, other) = List(3) { mutableStateOf(false) }
+        val otherItem = FocusRequester()
+        var receivedFocusDirection: FocusDirection? = null
+        rule.setContentForTest {
+            FocusableBox(grandparent, 0, 0, 50, 50) {
+                val customExit = Modifier.focusProperties {
+                    exit = {
+                        receivedFocusDirection = it
+                        otherItem
+                    }
+                }
+                FocusableBox(parent, 10, 10, 30, 30, deactivated = true, modifier = customExit) {
+                    FocusableBox(focusedItem, 10, 10, 10, 10, initialFocus) }
+            }
+            FocusableBox(other, x = 0, y = 60, width = 10, height = 10, otherItem)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(receivedFocusDirection).isEqualTo(focusDirection)
+            assertThat(movedFocusSuccessfully).isTrue()
+            assertThat(focusedItem.value).isFalse()
+            assertThat(parent.value).isFalse()
+            assertThat(grandparent.value).isFalse()
+            assertThat(other.value).isTrue()
+        }
+    }
+
+    /**
+     *                    _________
+     *                   |   Up   |
+     *                   |________|
+     *                 ________________
+     *                |  focusedItem  |
+     *   _________    |   _________   |    _________
+     *  |  Left  |    |  | child0 |   |   |  Right |
+     *  |________|    |  |________|   |   |________|
+     *                |_______________|
+     *                    _________
+     *                   |  Down  |
+     *                   |________|
+     */
+    @Test
+    fun moveFocusExit_blockFocusChange() {
+        // Arrange.
+        val (up, down, left, right, parent) = List(5) { mutableStateOf(false) }
+        val customFocusEnter = Modifier.focusProperties { exit = { Cancel } }
+        rule.setContentForTest {
+            FocusableBox(up, 30, 0, 10, 10)
+            FocusableBox(left, 0, 30, 10, 10)
+            FocusableBox(parent, 20, 20, 70, 50, deactivated = true, modifier = customFocusEnter) {
+                FocusableBox(focusedItem, 30, 30, 10, 10, initialFocus)
+            }
+            FocusableBox(right, 100, 35, 10, 10)
+            FocusableBox(down, 30, 90, 10, 10)
+        }
+
+        // Act.
+        val movedFocusSuccessfully = rule.runOnIdle { focusManager.moveFocus(focusDirection) }
+
+        // Assert.
+        rule.runOnIdle {
+            assertThat(movedFocusSuccessfully).isFalse()
+            assertThat(up.value).isFalse()
+            assertThat(left.value).isFalse()
+            assertThat(right.value).isFalse()
+            assertThat(down.value).isFalse()
+            assertThat(focusedItem.value).isTrue()
+        }
+    }
+
+    // We need to wrap the inline class parameter in another class because Java can't instantiate
+    // the inline class.
+    class Param(val focusDirection: FocusDirection) {
+        override fun toString() = focusDirection.toString()
+    }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "{0}")
+        fun initParameters() = listOf(Left, Right, Up, Down).map { Param(it) }
+    }
+
+    private fun ComposeContentTestRule.setContentForTest(composable: @Composable () -> Unit) {
+        setContent {
+            focusManager = LocalFocusManager.current
+            composable()
+        }
+        rule.runOnIdle { initialFocus.requestFocus() }
+    }
+}
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
index 5786317..e85483e 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusTraversalTest.kt
@@ -60,7 +60,7 @@
     companion object {
         @JvmStatic
         @Parameterized.Parameters(name = "{0}")
-        fun initParameters() = listOf(Param(Left), Param(Right), Param(Up), Param(Down))
+        fun initParameters() = listOf(Left, Right, Up, Down).map { Param(it) }
     }
 
     @FlakyTest(bugId = 233373546)
@@ -428,4 +428,4 @@
     FocusableBox(isFocused, x, y, width, height, focusRequester, deactivated, Modifier, content)
 }
 
-private val MutableList<MutableState<Boolean>>.values get() = this.map { it.value }
\ No newline at end of file
+private val MutableList<MutableState<Boolean>>.values get() = this.map { it.value }
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt
new file mode 100644
index 0000000..0fd5694
--- /dev/null
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/layout/MeasureOnlyTest.kt
@@ -0,0 +1,247 @@
+/*
+ * 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.compose.ui.layout
+
+import android.view.View
+import android.view.View.MeasureSpec
+import androidx.activity.ComponentActivity
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.setValue
+import androidx.compose.runtime.snapshots.Snapshot
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.background
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.layout.RootMeasurePolicy.measure
+import androidx.compose.ui.platform.LocalView
+import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.unit.IntSize
+import androidx.compose.ui.unit.constrainHeight
+import androidx.compose.ui.unit.constrainWidth
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth.assertThat
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+class MeasureOnlyTest {
+    @get:Rule
+    val rule = createAndroidComposeRule<ComponentActivity>()
+
+    /**
+     * onMeasure() shouldn't call placement or onPlace() or onGloballyPositioned()
+     */
+    @Test
+    fun onMeasureDoesNotPlace() {
+        var onPlacedCalled: Boolean
+        var onGloballyPositionedCalled: Boolean
+        var placementCalled: Boolean
+        lateinit var view: View
+
+        rule.setContent {
+            view = LocalView.current
+            Layout(modifier = Modifier
+                .fillMaxSize()
+                .background(Color.Blue)
+                .onPlaced {  }
+                .onGloballyPositioned {  }
+            ) { _, constraints ->
+                val width = constraints.constrainWidth(10000)
+                val height = constraints.constrainHeight(10000)
+                layout(width, height) {
+                    placementCalled = true
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            >
+            >
+            placementCalled = false
+            val widthSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            val heightSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            view.measure(widthSpec, heightSpec)
+            assertThat(onPlacedCalled).isFalse()
+            assertThat(onGloballyPositionedCalled).isFalse()
+            assertThat(placementCalled).isFalse()
+        }
+    }
+
+    @Test
+    fun childrenRequiredForMeasurementRemeasured() {
+        lateinit var view: View
+        var childMeasured: Boolean
+
+        rule.setContent {
+            view = LocalView.current
+            val child = @Composable {
+                Layout { _, _ ->
+                    childMeasured = true
+                    layout(10, 10) { }
+                }
+            }
+            Layout(
+                content = child,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(Color.Blue)
+            ) { measurables, constraints ->
+                val p = measurables[0].measure(constraints)
+                layout(p.width, p.height) {
+                    p.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            childMeasured = false
+            val widthSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            val heightSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            view.measure(widthSpec, heightSpec)
+            assertThat(childMeasured).isTrue()
+        }
+    }
+
+    @Test
+    fun childrenNotRequiredForMeasurementNotRemeasured() {
+        lateinit var view: View
+        var childMeasured: Boolean
+
+        rule.setContent {
+            view = LocalView.current
+            val child = @Composable {
+                Layout { _, _ ->
+                    childMeasured = true
+                    layout(10, 10) { }
+                }
+            }
+            Layout(
+                content = child,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(Color.Blue)
+            ) { measurables, constraints ->
+                layout(100, 100) {
+                    val p = measurables[0].measure(constraints)
+                    p.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            childMeasured = false
+            val widthSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            val heightSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.EXACTLY)
+            view.measure(widthSpec, heightSpec)
+            assertThat(childMeasured).isFalse()
+        }
+    }
+
+    @Test
+    fun invalidatedChildRequiredForMeasurementRemeasured() {
+        lateinit var view: View
+        var childMeasured: Boolean
+        var childSize by mutableStateOf(IntSize(10, 10))
+
+        rule.setContent {
+            view = LocalView.current
+            val child = @Composable {
+                Layout { _, _ ->
+                    childMeasured = true
+                    layout(childSize.width, childSize.height) { }
+                }
+            }
+            Layout(
+                content = child,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(Color.Blue)
+            ) { measurables, constraints ->
+                val p = measurables[0].measure(constraints)
+                layout(p.width, p.height) {
+                    p.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            val widthSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST)
+            val heightSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST)
+            view.measure(widthSpec, heightSpec)
+            view.layout(0, 0, view.width, view.height)
+        }
+        rule.runOnIdle {
+            // Must change the MeasureSpec or measure() won't call onMeasure()
+            val widthSpec = MeasureSpec.makeMeasureSpec(101, MeasureSpec.AT_MOST)
+            val heightSpec = MeasureSpec.makeMeasureSpec(101, MeasureSpec.AT_MOST)
+            childMeasured = false
+            childSize = IntSize(12, 12)
+            Snapshot.sendApplyNotifications()
+            view.measure(widthSpec, heightSpec)
+            assertThat(childMeasured).isTrue()
+        }
+    }
+
+    @Test
+    fun invalidatedChildNotRequiredForMeasurementNotRemeasured() {
+        lateinit var view: View
+        var childMeasured: Boolean
+        var childSize by mutableStateOf(IntSize(10, 10))
+
+        rule.setContent {
+            view = LocalView.current
+            val child = @Composable {
+                Layout { _, _ ->
+                    childMeasured = true
+                    layout(childSize.width, childSize.height) { }
+                }
+            }
+            Layout(
+                content = child,
+                modifier = Modifier
+                    .fillMaxSize()
+                    .background(Color.Blue)
+            ) { measurables, constraints ->
+                layout(10, 10) {
+                    val p = measurables[0].measure(constraints)
+                    p.place(0, 0)
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            val widthSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST)
+            val heightSpec = MeasureSpec.makeMeasureSpec(100, MeasureSpec.AT_MOST)
+            view.measure(widthSpec, heightSpec)
+            view.layout(0, 0, view.width, view.height)
+        }
+        rule.runOnIdle {
+            // Must change the MeasureSpec or measure() won't call onMeasure()
+            val widthSpec = MeasureSpec.makeMeasureSpec(101, MeasureSpec.AT_MOST)
+            val heightSpec = MeasureSpec.makeMeasureSpec(101, MeasureSpec.AT_MOST)
+            childMeasured = false
+            childSize = IntSize(12, 12)
+            Snapshot.sendApplyNotifications()
+            view.measure(widthSpec, heightSpec)
+            assertThat(childMeasured).isFalse()
+        }
+    }
+}
\ No newline at end of file
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 d5ceaf8..31b3907 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
@@ -15,6 +15,7 @@
  */
 package androidx.compose.ui.window
 
+import androidx.activity.compose.BackHandler
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.fillMaxSize
@@ -265,6 +266,32 @@
     }
 
     @Test
+    fun dialogTest_backHandler_isCalled_backButtonPressed() {
+        val clickCountPrefix = "Click: "
+
+        rule.setContent {
+            val showDialog = remember { mutableStateOf(true) }
+
+            if (showDialog.value) {
+                Dialog( {
+                    val clickCount = remember { mutableStateOf(0) }
+                    BasicText(clickCountPrefix + clickCount.value)
+                    BackHandler {
+                        clickCount.value++
+                    }
+                }
+            }
+        }
+
+        rule.onNodeWithText(clickCountPrefix + "0").assertIsDisplayed()
+
+        // Click the back button to trigger the BackHandler
+        Espresso.pressBack()
+
+        rule.onNodeWithText(clickCountPrefix + "1").assertIsDisplayed()
+    }
+
+    @Test
     fun dialog_preservesCompositionLocals() {
         val compositionLocal = compositionLocalOf<Float> { error("unset") }
         var value = 0f
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
index dddf0be..a29e428 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/AndroidComposeView.android.kt
@@ -841,7 +841,7 @@
                 wasMeasuredWithMultipleConstraints = true
             }
             measureAndLayoutDelegate.updateRootConstraints(constraints)
-            measureAndLayoutDelegate.measureAndLayout(resendMotionEventOnLayout)
+            measureAndLayoutDelegate.measureOnly()
             setMeasuredDimension(root.width, root.height)
             if (_androidViewsHandler != null) {
                 androidViewsHandler.measure(
@@ -864,6 +864,7 @@
     }
 
     override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
+        measureAndLayoutDelegate.measureAndLayout(resendMotionEventOnLayout)
         >
         // we postpone onPositioned callbacks until onLayout as LayoutCoordinates
         // are currently wrong if you try to get the global(activity) coordinates -
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.android.kt
index c793093..8d4bdfa 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/platform/ViewCompositionStrategy.android.kt
@@ -112,9 +112,6 @@
      * [AbstractComposeView.createComposition] is called while the view is detached from a window,
      * [AbstractComposeView.disposeComposition] must be called manually if the view is not later
      * attached to a window.)
-     *
-     * [DisposeOnDetachedFromWindow] is the default strategy for [AbstractComposeView] and
-     * [ComposeView].
      */
     object DisposeOnDetachedFromWindow : ViewCompositionStrategy {
         override fun installFor(view: AbstractComposeView): () -> Unit {
diff --git a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
index f7a7972..4f87447 100644
--- a/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
+++ b/compose/ui/ui/src/androidMain/kotlin/androidx/compose/ui/window/AndroidDialog.android.kt
@@ -16,7 +16,6 @@
 
 package androidx.compose.ui.window
 
-import android.app.Dialog
 import android.content.Context
 import android.graphics.Outline
 import android.os.Build
@@ -27,6 +26,8 @@
 import android.view.ViewOutlineProvider
 import android.view.Window
 import android.view.WindowManager
+import androidx.activity.ComponentDialog
+import androidx.activity.addCallback
 import androidx.compose.runtime.Composable
 import androidx.compose.runtime.CompositionContext
 import androidx.compose.runtime.DisposableEffect
@@ -280,7 +281,7 @@
     layoutDirection: LayoutDirection,
     density: Density,
     dialogId: UUID
-) : Dialog(
+) : ComponentDialog(
     /**
      * [Window.setClipToOutline] is only available from 22+, but the style attribute exists on 21.
      * So use a wrapped context that sets this attribute for compatibility back to 21.
@@ -359,6 +360,17 @@
 
         // Initial setup
         updateParameters(onDismissRequest, properties, layoutDirection)
+
+        // Due to how the onDismissRequest callback works
+        // (it enforces a just-in-time decision on whether to update the state to hide the dialog)
+        // we need to unconditionally add a callback here that is always enabled,
+        // meaning we'll never get a system UI controlled predictive back animation
+        // for these dialogs
+        onBackPressedDispatcher.addCallback(this) {
+            if (properties.dismissOnBackPress) {
+                onDismissRequest()
+            }
+        }
     }
 
     private fun setLayoutDirection(layoutDirection: LayoutDirection) {
@@ -425,12 +437,6 @@
         // Prevents the dialog from dismissing itself
         return
     }
-
-    override fun onBackPressed() {
-        if (properties.dismissOnBackPress) {
-            onDismissRequest()
-        }
-    }
 }
 
 @Composable
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
index c7a4d3d..3d819cd 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusRequester.kt
@@ -52,7 +52,33 @@
      */
     fun requestFocus() {
         check(focusRequesterModifierLocals.isNotEmpty()) { focusRequesterNotInitialized }
-        focusRequesterModifierLocals.forEach { it.findFocusNode()?.requestFocus() }
+        performRequestFocus {
+            it.requestFocus()
+            // TODO(b/245755256): Make focusModifier.requestFocus() return a Boolean.
+            true
+        }
+    }
+
+    /**
+     * This function searches down the hierarchy and calls [onFound] for all focus nodes associated
+     * with this [FocusRequester].
+     * @param onFound the callback that is run when the child is found.
+     * @return false if no focus nodes were found or if the FocusRequester is
+     * [FocusRequester.Cancel]. Returns null if the FocusRequester is [FocusRequester.Default].
+     * Otherwise returns a logical or of the result of calling [onFound] for each focus node
+     * associated with this [FocusRequester].
+     */
+    @OptIn(ExperimentalComposeUiApi::class)
+    internal fun performRequestFocus(onFound: (FocusModifier) -> Boolean): Boolean? = when (this) {
+        Cancel -> false
+        Default -> null
+        else -> {
+            var success = false
+            focusRequesterModifierLocals.forEach {
+                it.findFocusNode()?.let { success = onFound.invoke(it) || success }
+            }
+            success
+        }
     }
 
     /**
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
index 99e9627..2eea72e 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/FocusTransactions.kt
@@ -16,6 +16,7 @@
 
 package androidx.compose.ui.focus
 
+import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
@@ -37,13 +38,20 @@
         return
     }
     when (focusState) {
-        Active, Captured, Deactivated, DeactivatedParent -> {
+        Active, Captured -> {
             // There is no change in focus state, but we send a focus event to notify the user
             // that the focus request is completed.
             sendOnFocusEvent()
         }
         ActiveParent -> if (clearChildFocus()) grantFocus()
-
+        Deactivated, DeactivatedParent -> {
+            // If the node is deactivated, we perform a moveFocus(Enter).
+            @OptIn(ExperimentalComposeUiApi::class)
+            findChildCorrespondingToFocusEnter(FocusDirection.Enter) {
+                it.requestFocus()
+                true
+            }
+        }
         Inactive -> {
             val focusParent = parent
             if (focusParent != null) {
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
index f90c773..5ba0114 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/focus/TwoDimensionalFocusSearch.kt
@@ -19,12 +19,10 @@
 import androidx.compose.runtime.collection.MutableVector
 import androidx.compose.ui.ExperimentalComposeUiApi
 import androidx.compose.ui.focus.FocusDirection.Companion.Down
-import androidx.compose.ui.focus.FocusDirection.Companion.Exit
+import androidx.compose.ui.focus.FocusDirection.Companion.Enter
 import androidx.compose.ui.focus.FocusDirection.Companion.Left
 import androidx.compose.ui.focus.FocusDirection.Companion.Right
 import androidx.compose.ui.focus.FocusDirection.Companion.Up
-import androidx.compose.ui.focus.FocusRequester.Companion.Cancel
-import androidx.compose.ui.focus.FocusRequester.Companion.Default
 import androidx.compose.ui.focus.FocusStateImpl.Active
 import androidx.compose.ui.focus.FocusStateImpl.ActiveParent
 import androidx.compose.ui.focus.FocusStateImpl.Captured
@@ -66,29 +64,14 @@
                     // we continue searching among its children.
                     if (focusedChild.twoDimensionalFocusSearch(direction, onFound)) return true
 
+                    // If we don't find a match, we exit this Parent.
+                    // First check if this node has a custom focus exit.
                     @OptIn(ExperimentalComposeUiApi::class)
-                    if (direction == Exit) {
-                        when (val exit = focusedChild.focusProperties.exit(direction)) {
-                            Cancel -> return false
-                            Default -> {
-                                // If we don't have a custom destination,
-                                // we search among the siblings of the parent.
-                                val activeNode = focusedChild.activeNode()
-                                return generateAndSearchChildren(activeNode, direction, onFound)
-                            }
-                            else -> {
-                                var success = false
-                                exit.focusRequesterModifierLocals.forEach {
-                                    it.findFocusNode()?.let {
-                                        success = onFound.invoke(it) || success
-                                    }
-                                }
-                                return success
-                            }
-                        }
-                    }
+                    focusedChild.focusProperties.exit(direction).performRequestFocus(onFound)
+                        ?.let { return it }
 
-                    // If we don't find a match, we search among the siblings of the parent.
+                    // If we don't have a custom exit property,
+                    // we search among the siblings of the parent.
                     return generateAndSearchChildren(focusedChild.activeNode(), direction, onFound)
                 }
                 // Search for the next eligible sibling.
@@ -98,29 +81,59 @@
             }
         }
         Active, Captured -> {
-            // The 2-D focus search starts form the root. If we reached here, it means that there
+            // The 2-D focus search starts from the root. If we reached here, it means that there
             // was no intermediate node that was ActiveParent. This is an initial focus scenario.
             // We need to search among this node's children to find the best focus candidate.
-            val activated = activatedChildren()
-
-            // If there are aren't multiple children to choose from, return the first child.
-            if (activated.size <= 1) {
-                return activated.firstOrNull()?.let { onFound.invoke(it) } ?: false
-            }
-
-            // To start the search, we pick one of the four corners of this node as the initially
-            // focused rectangle.
-            val initialFocusRect = when (direction) {
-                Right, Down -> focusRect().topLeft()
-                Left, Up -> focusRect().bottomRight()
-                else -> error(InvalidFocusDirection)
-            }
-            val nextCandidate = activated.findBestCandidate(initialFocusRect, direction)
-            return nextCandidate?.let { onFound.invoke(it) } ?: false
+            return findChildCorrespondingToFocusEnter(direction, onFound)
         }
     }
 }
 
+/**
+ * Search through the children and find a child that corresponds to a moveFocus(Enter).
+ * An enter can be triggered explicitly by using the DPadCenter or can be triggered
+ * implicitly when we encounter a focus group during focus search.
+ * @param direction The [direction][FocusDirection] that triggered Focus Enter.
+ * @param onFound the callback that is run when the child is found.
+ * @return true if we find a suitable child, false otherwise.
+ */
+internal fun FocusModifier.findChildCorrespondingToFocusEnter(
+    direction: FocusDirection,
+    onFound: (FocusModifier) -> Boolean
+): Boolean {
+
+    // Check if a custom FocusEnter is specified.
+    @OptIn(ExperimentalComposeUiApi::class)
+    focusProperties.enter(direction).performRequestFocus(onFound)?.let { return it }
+
+    val focusableChildren = activatedChildren()
+
+    // If there are aren't multiple children to choose from, return the first child.
+    if (focusableChildren.size <= 1) {
+        return focusableChildren.firstOrNull()?.let { onFound.invoke(it) } ?: false
+    }
+
+    // For the purpose of choosing an appropriate child, we convert moveFocus(Enter)
+    // to Left or Right based on LayoutDirection. If this was an implicit enter, we use the
+    // direction that triggered the implicit enter.
+    val requestedDirection = when (direction) {
+        // TODO(b/244528858) choose different items for moveFocus(Enter) based on LayoutDirection.
+        @OptIn(ExperimentalComposeUiApi::class)
+        Enter -> Left
+        else -> direction
+    }
+
+    // To start the search, we pick one of the four corners of this node as the initially
+    // focused rectangle.
+    val initialFocusRect = when (requestedDirection) {
+        Right, Down -> focusRect().topLeft()
+        Left, Up -> focusRect().bottomRight()
+        else -> error(InvalidFocusDirection)
+    }
+    val nextCandidate = focusableChildren.findBestCandidate(initialFocusRect, requestedDirection)
+    return nextCandidate?.let { onFound.invoke(it) } ?: false
+}
+
 // Search among your children for the next child.
 // If the next child is not found, generate more children by requesting a beyondBoundsLayout.
 private fun FocusModifier.generateAndSearchChildren(
@@ -157,7 +170,11 @@
         // If the result is not deactivated, this is a valid next item.
         if (!nextItem.focusState.isDeactivated) return onFound.invoke(nextItem)
 
-        // If the result is deactivated, we search among its children.
+        // If the result is deactivated, and the deactivated node has a custom Enter, we use it.
+        @OptIn(ExperimentalComposeUiApi::class)
+        nextItem.focusProperties.enter(direction).performRequestFocus(onFound)?.let { return it }
+
+        // If the result is deactivated, and there is no custom ehter, we search among its children.
         if (nextItem.generateAndSearchChildren(focusedItem, direction, onFound)) return true
 
         // If there are no results among the children of the deactivated node,
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
index 5a4a960..e8c82f9 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/LayoutNode.kt
@@ -410,6 +410,7 @@
         if (parent != null) {
             parent.invalidateLayer()
             parent.invalidateMeasurements()
+            measuredByParent = UsageByParent.NotUsed
         }
         layoutDelegate.resetAlignmentLines()
         onDetach?.invoke(owner)
diff --git a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
index d9d13d07..dfb1463 100644
--- a/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
+++ b/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/MeasureAndLayoutDelegate.kt
@@ -338,6 +338,34 @@
         return rootNodeResized
     }
 
+    /**
+     * Only does measurement from the root without doing any placement. This is intended
+     * to be called to determine only how large the root is with minimal effort.
+     */
+    fun measureOnly() {
+        performMeasureAndLayout {
+            recurseRemeasure(root)
+        }
+    }
+
+    /**
+     * Walks the hierarchy from [layoutNode] and remeasures [layoutNode] and any
+     * descendants that affect its size.
+     */
+    private fun recurseRemeasure(layoutNode: LayoutNode) {
+        remeasureOnly(layoutNode)
+
+        layoutNode._children.forEach { child ->
+            if (child.canAffectParent) {
+                if (relayoutNodes.contains(child)) {
+                    recurseRemeasure(child)
+                }
+            }
+        }
+        // The child measurement may have invalidated layoutNode's measurement
+        remeasureOnly(layoutNode)
+    }
+
     fun measureAndLayout(layoutNode: LayoutNode, constraints: Constraints) {
         require(layoutNode != root)
         performMeasureAndLayout {
@@ -442,6 +470,21 @@
     }
 
     /**
+     * Remeasures [layoutNode] if it has [LayoutNode.measurePending] or
+     * [LayoutNode.lookaheadMeasurePending].
+     */
+    private fun remeasureOnly(layoutNode: LayoutNode) {
+        if (!layoutNode.measurePending && !layoutNode.lookaheadMeasurePending) {
+            return // nothing needs to be remeasured
+        }
+        val constraints = if (layoutNode === root) rootConstraints!! else null
+        if (layoutNode.lookaheadMeasurePending) {
+            doLookaheadRemeasure(layoutNode, constraints)
+        }
+        doRemeasure(layoutNode, constraints)
+    }
+
+    /**
      * Makes sure the passed [layoutNode] and its subtree is remeasured and has the final sizes.
      *
      * The node or some of the nodes in its subtree can still be kept unmeasured if they are
diff --git a/core/core-ktx/api/current.ignore b/core/core-ktx/api/current.ignore
deleted file mode 100644
index 150db83..0000000
--- a/core/core-ktx/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.core.view.ViewKt#setGone(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setGone
-ParameterNameChange: androidx.core.view.ViewKt#setInvisible(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setInvisible
-ParameterNameChange: androidx.core.view.ViewKt#setVisible(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setVisible
diff --git a/core/core-ktx/api/restricted_current.ignore b/core/core-ktx/api/restricted_current.ignore
deleted file mode 100644
index 150db83..0000000
--- a/core/core-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.core.view.ViewKt#setGone(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setGone
-ParameterNameChange: androidx.core.view.ViewKt#setInvisible(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setInvisible
-ParameterNameChange: androidx.core.view.ViewKt#setVisible(android.view.View, boolean) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.core.view.ViewKt.setVisible
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index 4919591..30803b3 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -800,6 +800,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index e539396..7aa1a18 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -800,6 +800,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
     method public androidx.core.graphics.drawable.IconCompat? getIcon();
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 86dafe9..cc32575 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -888,6 +888,16 @@
     method public void removeOnPictureInPictureModeChangedListener(androidx.core.util.Consumer<androidx.core.app.PictureInPictureModeChangedInfo!>);
   }
 
+  public final class PendingIntentCompat {
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getActivities(android.content.Context, int, android.content.Intent![], int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getActivity(android.content.Context, int, android.content.Intent, int, android.os.Bundle, boolean);
+    method public static android.app.PendingIntent getBroadcast(android.content.Context, int, android.content.Intent, int, boolean);
+    method @RequiresApi(26) public static android.app.PendingIntent getForegroundService(android.content.Context, int, android.content.Intent, int, boolean);
+    method public static android.app.PendingIntent getService(android.content.Context, int, android.content.Intent, int, boolean);
+  }
+
   public class Person {
     method @RequiresApi(28) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static androidx.core.app.Person fromAndroidPerson(android.app.Person);
     method public static androidx.core.app.Person fromBundle(android.os.Bundle);
diff --git a/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java b/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java
new file mode 100644
index 0000000..db89748
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/app/PendingIntentCompat.java
@@ -0,0 +1,234 @@
+/*

+ * Copyright (C) 2022 The Android Open Source Project

+ *

+ * Licensed under the Apache License, Version 2.0 (the "License");

+ * you may not use this file except in compliance with the License.

+ * You may obtain a copy of the License at

+ *

+ *      http://www.apache.org/licenses/LICENSE-2.0

+ *

+ * Unless required by applicable law or agreed to in writing, software

+ * distributed under the License is distributed on an "AS IS" BASIS,

+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.

+ * See the License for the specific language governing permissions and

+ * limitations under the License.

+ */

+

+package androidx.core.app;

+

+import static android.app.PendingIntent.FLAG_IMMUTABLE;

+import static android.app.PendingIntent.FLAG_MUTABLE;

+

+import android.annotation.SuppressLint;

+import android.app.PendingIntent;

+import android.content.Context;

+import android.content.Intent;

+import android.os.Build;

+import android.os.Bundle;

+

+import androidx.annotation.DoNotInline;

+import androidx.annotation.IntDef;

+import androidx.annotation.NonNull;

+import androidx.annotation.RequiresApi;

+

+import java.lang.annotation.Retention;

+import java.lang.annotation.RetentionPolicy;

+

+/** Helper for accessing features in {@link PendingIntent}. */

+public final class PendingIntentCompat {

+

+    /** @hide */

+    @IntDef(

+            flag = true,

+            value = {

+                PendingIntent.FLAG_ONE_SHOT,

+                PendingIntent.FLAG_NO_CREATE,

+                PendingIntent.FLAG_CANCEL_CURRENT,

+                PendingIntent.FLAG_UPDATE_CURRENT,

+                Intent.FILL_IN_ACTION,

+                Intent.FILL_IN_DATA,

+                Intent.FILL_IN_CATEGORIES,

+                Intent.FILL_IN_COMPONENT,

+                Intent.FILL_IN_PACKAGE,

+                Intent.FILL_IN_SOURCE_BOUNDS,

+                Intent.FILL_IN_SELECTOR,

+                Intent.FILL_IN_CLIP_DATA

+            })

+    @Retention(RetentionPolicy.SOURCE)

+    public @interface Flags {}

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivities(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+            @Flags int flags,

+            @NonNull Bundle options,

+            boolean isMutable) {

+        if (Build.VERSION.SDK_INT >= 16) {

+            return Api16Impl.getActivities(

+                    context, requestCode, intents, addMutabilityFlags(isMutable, flags), options);

+        } else {

+            return PendingIntent.getActivities(context, requestCode, intents, flags);

+        }

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivities(Context, int, Intent[], int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivities(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getActivities(

+                context, requestCode, intents, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivity(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getActivity(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getActivity(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getActivity(Context, int, Intent, int, Bundle)}.

+     */

+    public static @NonNull PendingIntent getActivity(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            @NonNull Bundle options,

+            boolean isMutable) {

+        if (Build.VERSION.SDK_INT >= 16) {

+            return Api16Impl.getActivity(

+                    context, requestCode, intent, addMutabilityFlags(isMutable, flags), options);

+        } else {

+            return PendingIntent.getActivity(context, requestCode, intent, flags);

+        }

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getBroadcast(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getBroadcast(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getBroadcast(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getForegroundService(Context, int, Intent, int)} .

+     */

+    @RequiresApi(26)

+    public static @NonNull PendingIntent getForegroundService(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return Api26Impl.getForegroundService(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    /**

+     * Retrieves a {@link PendingIntent} with mandatory mutability flag set on supported platform

+     * versions. The caller provides the flag as combination of all the other values except

+     * mutability flag. This method combines mutability flag when necessary. See {@link

+     * PendingIntent#getService(Context, int, Intent, int)}.

+     */

+    public static @NonNull PendingIntent getService(

+            @NonNull Context context,

+            int requestCode,

+            @NonNull Intent intent,

+            @Flags int flags,

+            boolean isMutable) {

+        return PendingIntent.getService(

+                context, requestCode, intent, addMutabilityFlags(isMutable, flags));

+    }

+

+    private static int addMutabilityFlags(boolean isMutable, int flags) {

+        if (isMutable) {

+            if (Build.VERSION.SDK_INT >= 31) {

+                flags |= FLAG_MUTABLE;

+            }

+        } else {

+            if (Build.VERSION.SDK_INT >= 23) {

+                flags |= FLAG_IMMUTABLE;

+            }

+        }

+

+        return flags;

+    }

+

+    private PendingIntentCompat() {}

+

+    @RequiresApi(16)

+    private static class Api16Impl {

+        private Api16Impl() {}

+

+        @DoNotInline

+        public static @NonNull PendingIntent getActivities(

+                @NonNull Context context,

+                int requestCode,

+                @NonNull @SuppressLint("ArrayReturn") Intent[] intents,

+                @Flags int flags,

+                @NonNull Bundle options) {

+            return PendingIntent.getActivities(context, requestCode, intents, flags, options);

+        }

+

+        @DoNotInline

+        public static @NonNull PendingIntent getActivity(

+                @NonNull Context context,

+                int requestCode,

+                @NonNull Intent intent,

+                @Flags int flags,

+                @NonNull Bundle options) {

+            return PendingIntent.getActivity(context, requestCode, intent, flags, options);

+        }

+    }

+

+    @RequiresApi(26)

+    private static class Api26Impl {

+        private Api26Impl() {}

+

+        @DoNotInline

+        public static PendingIntent getForegroundService(

+                Context context, int requestCode, Intent intent, int flags) {

+            return PendingIntent.getForegroundService(context, requestCode, intent, flags);

+        }

+    }

+}

diff --git a/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java b/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java
new file mode 100644
index 0000000..22eed6b
--- /dev/null
+++ b/core/core/src/test/java/androidx/core/app/PendingIntentCompatTest.java
@@ -0,0 +1,238 @@
+/*

+ * 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.core.app;

+

+import static com.google.common.truth.Truth.assertThat;

+

+import static org.robolectric.Shadows.shadowOf;

+

+import android.annotation.TargetApi;

+import android.app.PendingIntent;

+import android.content.Context;

+import android.content.Intent;

+import android.os.Bundle;

+

+import androidx.test.core.app.ApplicationProvider;

+

+import org.junit.Test;

+import org.junit.runner.RunWith;

+import org.robolectric.RobolectricTestRunner;

+import org.robolectric.annotation.Config;

+import org.robolectric.shadows.ShadowPendingIntent;

+

+/** Unit test for {@link PendingIntentCompat}. */

+@RunWith(RobolectricTestRunner.class)

+public class PendingIntentCompatTest {

+    private final Context context = ApplicationProvider.getApplicationContext();

+

+    @Config(maxSdk = 22)

+    @Test

+    public void addMutabilityFlags_immutableOnPreM() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags()).isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(minSdk = 23)

+    @Test

+    public void addMutabilityFlags_immutableOnMPlus() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags())

+                .isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_IMMUTABLE);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(maxSdk = 30)

+    @Test

+    public void addMutabilityFlags_mutableOnPreS() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ true));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags()).isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Config(minSdk = 31)

+    @Test

+    public void addMutabilityFlags_mutableOnSPlus() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ true));

+        assertThat(shadow.isActivityIntent()).isTrue();

+        assertThat(shadow.getFlags())

+                .isEqualTo(PendingIntent.FLAG_UPDATE_CURRENT | PendingIntent.FLAG_MUTABLE);

+        assertThat(shadow.getRequestCode()).isEqualTo(requestCode);

+    }

+

+    @Test

+    public void getActivities_withBundle() {

+        int requestCode = 7465;

+        Intent[] intents = new Intent[] {};

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivities(

+                                context,

+                                requestCode,

+                                intents,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivities() {

+        int requestCode = 7465;

+        Intent[] intents = new Intent[] {};

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivities(

+                                context,

+                                requestCode,

+                                intents,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivity_withBundle() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                options,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getActivity() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getActivity(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isActivityIntent()).isTrue();

+    }

+

+    @Test

+    public void getService() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getService(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isService()).isTrue();

+    }

+

+    @Test

+    public void getBroadcast() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getBroadcast(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isBroadcast()).isTrue();

+    }

+

+    @TargetApi(26)

+    @Config(minSdk = 26)

+    @Test

+    public void getForegroundService() {

+        int requestCode = 7465;

+        Intent intent = new Intent();

+        Bundle options = new Bundle();

+        ShadowPendingIntent shadow =

+                shadowOf(

+                        PendingIntentCompat.getForegroundService(

+                                context,

+                                requestCode,

+                                intent,

+                                PendingIntent.FLAG_UPDATE_CURRENT,

+                                /* isMutable= */ false));

+        assertThat(shadow.isForegroundService()).isTrue();

+    }

+}

diff --git a/credentials/credentials-play-services-auth/api/current.txt b/credentials/credentials-play-services-auth/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-auth/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-auth/api/public_plus_experimental_current.txt b/credentials/credentials-play-services-auth/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-auth/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-auth/api/res-current.txt b/credentials/credentials-play-services-auth/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/credentials/credentials-play-services-auth/api/res-current.txt
diff --git a/credentials/credentials-play-services-auth/api/restricted_current.txt b/credentials/credentials-play-services-auth/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/credentials/credentials-play-services-auth/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/credentials/credentials-play-services-auth/build.gradle b/credentials/credentials-play-services-auth/build.gradle
new file mode 100644
index 0000000..2125f1d
--- /dev/null
+++ b/credentials/credentials-play-services-auth/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    // Add dependencies here
+}
+
+android {
+    namespace "androidx.credentials.play.services.auth"
+}
+
+androidx {
+    name = "Credentials Play Services Auth Library"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenGroup = LibraryGroups.CREDENTIALS
+    inceptionYear = "2022"
+    description = "sign into apps using play-services-auth library"
+}
diff --git a/credentials/credentials-play-services-auth/src/main/androidx/credentials/androidx-credentials-credentials-play-services-auth-documentation.md b/credentials/credentials-play-services-auth/src/main/androidx/credentials/androidx-credentials-credentials-play-services-auth-documentation.md
new file mode 100644
index 0000000..1ddfeaa
--- /dev/null
+++ b/credentials/credentials-play-services-auth/src/main/androidx/credentials/androidx-credentials-credentials-play-services-auth-documentation.md
@@ -0,0 +1,7 @@
+# Module root
+
+CREDENTIALS CREDENTIALS PLAY SERVICES AUTH
+
+# Package androidx.credentials.play.services.auth
+
+This package allows developers to retrieve credentials from play-services-auth library.
diff --git a/datastore/datastore-rxjava2/src/test/java/androidx/datastore/rxjava2/RxDataStoreTest.java b/datastore/datastore-rxjava2/src/test/java/androidx/datastore/rxjava2/RxDataStoreTest.java
index 36471e8..40077e0 100644
--- a/datastore/datastore-rxjava2/src/test/java/androidx/datastore/rxjava2/RxDataStoreTest.java
+++ b/datastore/datastore-rxjava2/src/test/java/androidx/datastore/rxjava2/RxDataStoreTest.java
@@ -20,6 +20,7 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import org.junit.Ignore;
 import org.junit.Rule;
 import org.junit.Test;
 import org.junit.rules.TemporaryFolder;
@@ -61,6 +62,7 @@
         assertThat(firstByte).isEqualTo(1);
     }
 
+    @Ignore // b/214040264
     @Test
     public void testTake3() throws Exception {
         File newFile = tempFolder.newFile();
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 906fb0d..e943f94 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -430,10 +430,7 @@
 \(KonanProperies\) Downloading dependency: file:\.\./\.\./.*
 Please wait while Kotlin/Native compiler .* is being installed\.
 Unpack Kotlin/Native compiler to .*
-# Even though this log shows an http url, it is actually coming from konan prebuilts directory
-# In kotlin 1.7, we will be able to modify this URL hence this message will change to point
-# to the right location.
-Download https://download\.jetbrains\.com/kotlin/native/builds/.*
+Download file:\.\./\.\./.*
 Downloading native dependencies \(LLVM, sysroot etc\)\. This is a one\-time action performed only on the first run of the compiler\.
 Extracting dependency: .*\.konan/cache.*
 # New memory model does not work with compiler caches yet:
@@ -491,4 +488,6 @@
 To see more detail about a task, run gradlew help \-\-task <task>
 To see a list of command\-line options, run gradlew \-\-help
 For more detail on using Gradle, see https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/command_line_interface\.html
-For troubleshooting, visit https://help\.gradle\.org
\ No newline at end of file
+For troubleshooting, visit https://help\.gradle\.org
+# > Task :graphics:graphics-path:compileDebugKotlin
+w\: \$SUPPORT\/graphics\/graphics\-path\/src\/main\/java\/androidx\/graphics\/path\/Paths\.kt\: \([0-9]+\, [0-9]+\)\: Extension is shadowed by a member\: public open fun iterator\(\)\: PathIterator
diff --git a/development/referenceDocs/stageReferenceDocsWithDackka.sh b/development/referenceDocs/stageReferenceDocsWithDackka.sh
index dc141d8..dad52ee 100755
--- a/development/referenceDocs/stageReferenceDocsWithDackka.sh
+++ b/development/referenceDocs/stageReferenceDocsWithDackka.sh
@@ -49,16 +49,10 @@
 # Each directory's spelling must match the library's directory in
 # frameworks/support.
 readonly javaLibraryDirsThatDontUseDackka=(
-  "android/support/v4"
-  "androidx/ads"
-  "androidx/appcompat"
-  "androidx/biometric"
-  "androidx/browser"
   "androidx/camera"
   "androidx/car"
   "androidx/concurrent"
   "androidx/contentpager"
-  "androidx/customview"
   "androidx/datastore"
   "androidx/documentfile"
   "androidx/draganddrop"
@@ -72,38 +66,22 @@
   "androidx/media"
   "androidx/media2"
   "androidx/mediarouter"
-  "androidx/percentlayout"
   "androidx/preference"
   "androidx/print"
   "androidx/profileinstaller"
   "androidx/recommendation"
   "androidx/recyclerview"
-  "androidx/remotecallback"
-  "androidx/resourceinspection"
-  "androidx/room"
-  "androidx/security"
-  "androidx/slice"
-  "androidx/sqlite"
   "androidx/startup"
   "androidx/swiperefreshlayout"
   "androidx/textclassifier"
-  "androidx/tracing"
-  "androidx/transition"
   "androidx/tvprovider"
-  "androidx/webkit"
 )
 readonly kotlinLibraryDirsThatDontUseDackka=(
-  "android/support/v4"
-  "androidx/ads"
-  "androidx/appcompat"
   "androidx/benchmark"
-  "androidx/biometric"
-  "androidx/browser"
   "androidx/camera"
   "androidx/car"
   "androidx/concurrent"
   "androidx/contentpager"
-  "androidx/customview"
   "androidx/datastore"
   "androidx/documentfile"
   "androidx/dynamicanimation"
@@ -118,25 +96,15 @@
   "androidx/media"
   "androidx/media2"
   "androidx/mediarouter"
-  "androidx/percentlayout"
   "androidx/preference"
   "androidx/print"
   "androidx/profileinstaller"
   "androidx/recommendation"
   "androidx/recyclerview"
-  "androidx/remotecallback"
-  "androidx/resourceinspection"
-  "androidx/room"
-  "androidx/security"
-  "androidx/slice"
-  "androidx/sqlite"
   "androidx/startup"
   "androidx/swiperefreshlayout"
   "androidx/textclassifier"
-  "androidx/tracing"
-  "androidx/transition"
   "androidx/tvprovider"
-  "androidx/webkit"
 )
 
 # Change directory to this script's location and store the directory
@@ -269,7 +237,7 @@
 printf "== Create (if needed) and sync g4 workspace \n"
 printf "=================================================================== \n"
 
-client="$(p4 g4d -f androidx-ref-docs-stage)"
+client="$(p4 g4d -f androidx-ref-docs-stage --multichange)"
 cd "$client"
 
 # Revert all local changes to prevent merge conflicts when syncing.
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 702e574..05189c3 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -8,10 +8,10 @@
 }
 
 dependencies {
-    docs("androidx.activity:activity:1.6.0-rc02")
-    docs("androidx.activity:activity-compose:1.6.0-rc02")
-    samples("androidx.activity:activity-compose-samples:1.6.0-rc02")
-    docs("androidx.activity:activity-ktx:1.6.0-rc02")
+    docs("androidx.activity:activity:1.6.0")
+    docs("androidx.activity:activity-compose:1.6.0")
+    samples("androidx.activity:activity-compose-samples:1.6.0")
+    docs("androidx.activity:activity-ktx:1.6.0")
     docs("androidx.ads:ads-identifier:1.0.0-alpha04")
     docs("androidx.ads:ads-identifier-provider:1.0.0-alpha04")
     docs("androidx.annotation:annotation:1.5.0-rc01")
@@ -52,55 +52,55 @@
     docs("androidx.cardview:cardview:1.0.0")
     docs("androidx.collection:collection:1.3.0-alpha02")
     docs("androidx.collection:collection-ktx:1.3.0-alpha02")
-    docs("androidx.compose.animation:animation:1.3.0-beta02")
-    docs("androidx.compose.animation:animation-core:1.3.0-beta02")
-    docs("androidx.compose.animation:animation-graphics:1.3.0-beta02")
-    samples("androidx.compose.animation:animation-samples:1.3.0-beta02")
-    samples("androidx.compose.animation:animation-core-samples:1.3.0-beta02")
-    samples("androidx.compose.animation:animation-graphics-samples:1.3.0-beta02")
-    docs("androidx.compose.foundation:foundation:1.3.0-beta02")
-    docs("androidx.compose.foundation:foundation-layout:1.3.0-beta02")
-    samples("androidx.compose.foundation:foundation-layout-samples:1.3.0-beta02")
-    samples("androidx.compose.foundation:foundation-samples:1.3.0-beta02")
-    docs("androidx.compose.material3:material3:1.0.0-beta02")
-    samples("androidx.compose.material3:material3-samples:1.0.0-beta02")
-    docs("androidx.compose.material3:material3-window-size-class:1.0.0-beta02")
-    samples("androidx.compose.material3:material3-window-size-class-samples:1.0.0-beta02")
-    docs("androidx.compose.material:material:1.3.0-beta02")
-    docs("androidx.compose.material:material-icons-core:1.3.0-beta02")
-    samples("androidx.compose.material:material-icons-core-samples:1.3.0-beta02")
-    docs("androidx.compose.material:material-ripple:1.3.0-beta02")
-    samples("androidx.compose.material:material-samples:1.3.0-beta02")
-    docs("androidx.compose.runtime:runtime:1.3.0-beta02")
-    docs("androidx.compose.runtime:runtime-livedata:1.3.0-beta02")
-    samples("androidx.compose.runtime:runtime-livedata-samples:1.3.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava2:1.3.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.3.0-beta02")
-    docs("androidx.compose.runtime:runtime-rxjava3:1.3.0-beta02")
-    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.3.0-beta02")
-    docs("androidx.compose.runtime:runtime-saveable:1.3.0-beta02")
-    samples("androidx.compose.runtime:runtime-saveable-samples:1.3.0-beta02")
-    samples("androidx.compose.runtime:runtime-samples:1.3.0-beta02")
+    docs("androidx.compose.animation:animation:1.3.0-rc01")
+    docs("androidx.compose.animation:animation-core:1.3.0-rc01")
+    docs("androidx.compose.animation:animation-graphics:1.3.0-rc01")
+    samples("androidx.compose.animation:animation-samples:1.3.0-rc01")
+    samples("androidx.compose.animation:animation-core-samples:1.3.0-rc01")
+    samples("androidx.compose.animation:animation-graphics-samples:1.3.0-rc01")
+    docs("androidx.compose.foundation:foundation:1.3.0-rc01")
+    docs("androidx.compose.foundation:foundation-layout:1.3.0-rc01")
+    samples("androidx.compose.foundation:foundation-layout-samples:1.3.0-rc01")
+    samples("androidx.compose.foundation:foundation-samples:1.3.0-rc01")
+    docs("androidx.compose.material3:material3:1.0.0-rc01")
+    samples("androidx.compose.material3:material3-samples:1.0.0-rc01")
+    docs("androidx.compose.material3:material3-window-size-class:1.0.0-rc01")
+    samples("androidx.compose.material3:material3-window-size-class-samples:1.0.0-rc01")
+    docs("androidx.compose.material:material:1.3.0-rc01")
+    docs("androidx.compose.material:material-icons-core:1.3.0-rc01")
+    samples("androidx.compose.material:material-icons-core-samples:1.3.0-rc01")
+    docs("androidx.compose.material:material-ripple:1.3.0-rc01")
+    samples("androidx.compose.material:material-samples:1.3.0-rc01")
+    docs("androidx.compose.runtime:runtime:1.3.0-rc01")
+    docs("androidx.compose.runtime:runtime-livedata:1.3.0-rc01")
+    samples("androidx.compose.runtime:runtime-livedata-samples:1.3.0-rc01")
+    docs("androidx.compose.runtime:runtime-rxjava2:1.3.0-rc01")
+    samples("androidx.compose.runtime:runtime-rxjava2-samples:1.3.0-rc01")
+    docs("androidx.compose.runtime:runtime-rxjava3:1.3.0-rc01")
+    samples("androidx.compose.runtime:runtime-rxjava3-samples:1.3.0-rc01")
+    docs("androidx.compose.runtime:runtime-saveable:1.3.0-rc01")
+    samples("androidx.compose.runtime:runtime-saveable-samples:1.3.0-rc01")
+    samples("androidx.compose.runtime:runtime-samples:1.3.0-rc01")
     docs("androidx.compose.runtime:runtime-tracing:1.0.0-alpha01")
-    docs("androidx.compose.ui:ui:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-geometry:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-graphics:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-graphics-samples:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-test:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-test-junit4:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-test-samples:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-text:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-text-google-fonts:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-text-samples:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-tooling:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-tooling-data:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-tooling-preview:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-unit:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-unit-samples:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-util:1.3.0-beta02")
-    docs("androidx.compose.ui:ui-viewbinding:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-viewbinding-samples:1.3.0-beta02")
-    samples("androidx.compose.ui:ui-samples:1.3.0-beta02")
+    docs("androidx.compose.ui:ui:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-geometry:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-graphics:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-graphics-samples:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-test:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-test-junit4:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-test-samples:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-text:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-text-google-fonts:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-text-samples:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-tooling:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-tooling-data:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-tooling-preview:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-unit:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-unit-samples:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-util:1.3.0-rc01")
+    docs("androidx.compose.ui:ui-viewbinding:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-viewbinding-samples:1.3.0-rc01")
+    samples("androidx.compose.ui:ui-samples:1.3.0-rc01")
     docs("androidx.concurrent:concurrent-futures:1.1.0")
     docs("androidx.concurrent:concurrent-futures-ktx:1.1.0")
     docs("androidx.contentpager:contentpager:1.0.0")
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 02b3201..ecdf5cc 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -122,6 +122,7 @@
     docs(project(":core:uwb:uwb"))
     docs(project(":core:uwb:uwb-rxjava3"))
     docs(project(":credentials:credentials"))
+    docs(project(":credentials:credentials-play-services-auth"))
     docs(project(":cursoradapter:cursoradapter"))
     docs(project(":customview:customview"))
     docs(project(":customview:customview-poolingcontainer"))
@@ -141,6 +142,7 @@
     docs(project(":drawerlayout:drawerlayout"))
     docs(project(":dynamicanimation:dynamicanimation"))
     docs(project(":dynamicanimation:dynamicanimation-ktx"))
+    docs(project(":emoji2:emoji2-emojipicker"))
     docs(project(":emoji:emoji"))
     docs(project(":emoji:emoji-appcompat"))
     docs(project(":emoji:emoji-bundled"))
@@ -230,6 +232,7 @@
     docs(project(":paging:paging-rxjava2-ktx"))
     docs(project(":paging:paging-rxjava3"))
     samples(project(":paging:paging-samples"))
+    docs(project(":paging:paging-testing"))
     docs(project(":palette:palette"))
     docs(project(":palette:palette-ktx"))
     docs(project(":percentlayout:percentlayout"))
diff --git a/docs/testing.md b/docs/testing.md
index 2829821..50f5928 100644
--- a/docs/testing.md
+++ b/docs/testing.md
@@ -127,7 +127,7 @@
 pre-release version of the Android SDK then you may see an error like
 
 ```
-java.lang.IllegalArgumentException: Package targetSdkVersion=31 > maxSdkVersion=30
+java.lang.IllegalArgumentException: Package targetSdkVersion=33 > maxSdkVersion=32
 at org.robolectric.plugins.DefaultSdkPicker.configuredSdks(DefaultSdkPicker.java:118)
 at org.robolectric.plugins.DefaultSdkPicker.selectSdks(DefaultSdkPicker.java:69)
 ```
@@ -137,9 +137,9 @@
 following contents:
 
 ```
-# Robolectric currently doesn't support API 31, so we have to explicitly specify 30 as the target
+# Robolectric currently doesn't support API 33, so we have to explicitly specify 32 as the target
 # sdk for now. Remove when no longer necessary.
-sdk=30
+sdk=32
 ```
 
 ## Using the emulator {#emulator}
diff --git a/emoji2/emoji2-emojipicker/OWNERS b/emoji2/emoji2-emojipicker/OWNERS
new file mode 100644
index 0000000..be95047
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/OWNERS
@@ -0,0 +1,4 @@
+chelseahao@google.com
+linguo@google.com
+scduan@google.com
+wangqi@google.com
diff --git a/emoji2/emoji2-emojipicker/api/current.txt b/emoji2/emoji2-emojipicker/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/emoji2/emoji2-emojipicker/api/public_plus_experimental_current.txt b/emoji2/emoji2-emojipicker/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/emoji2/emoji2-emojipicker/api/res-current.txt b/emoji2/emoji2-emojipicker/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/api/res-current.txt
diff --git a/emoji2/emoji2-emojipicker/api/restricted_current.txt b/emoji2/emoji2-emojipicker/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/emoji2/emoji2-emojipicker/build.gradle b/emoji2/emoji2-emojipicker/build.gradle
new file mode 100644
index 0000000..e28cb79
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/build.gradle
@@ -0,0 +1,41 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    // Add dependencies here
+}
+
+android {
+    namespace "androidx.emoji2.emojipicker"
+}
+
+androidx {
+    name = "androidx.emoji2:emoji2-emojipicker"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenGroup = LibraryGroups.EMOJI2
+    inceptionYear = "2022"
+    description = "This library provides the latest emoji support and emoji picker UI to input " +
+            "emoji in current and older devices"
+}
diff --git a/emoji2/emoji2-emojipicker/src/main/androidx/emoji2/androidx-emoji2-emoji2-emojipicker-documentation.md b/emoji2/emoji2-emojipicker/src/main/androidx/emoji2/androidx-emoji2-emoji2-emojipicker-documentation.md
new file mode 100644
index 0000000..f751ab7
--- /dev/null
+++ b/emoji2/emoji2-emojipicker/src/main/androidx/emoji2/androidx-emoji2-emoji2-emojipicker-documentation.md
@@ -0,0 +1,8 @@
+# Module root
+
+androidx.emoji2 emoji2-emojipicker
+
+# Package androidx.emoji2.emojipicker
+
+This library provides the latest emoji support and emoji picker UI including
+skin-tone variants and emoji compat support.
diff --git a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
index 5690ae1..42ea047 100644
--- a/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
+++ b/exifinterface/exifinterface/src/androidTest/java/androidx/exifinterface/media/ExifInterfaceTest.java
@@ -430,7 +430,6 @@
     @LargeTest
     public void testDngFiles() throws Throwable {
         readFromFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
-        writeToFilesWithExif(DNG_WITH_EXIF_WITH_XMP, R.array.dng_with_exif_with_xmp);
     }
 
     @Test
diff --git a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
index b91b710..38759cf 100644
--- a/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
+++ b/exifinterface/exifinterface/src/main/java/androidx/exifinterface/media/ExifInterface.java
@@ -88,7 +88,7 @@
  * <p>
  * Supported for reading: JPEG, PNG, WebP, HEIF, DNG, CR2, NEF, NRW, ARW, RW2, ORF, PEF, SRW, RAF.
  * <p>
- * Supported for writing: JPEG, PNG, WebP, DNG.
+ * Supported for writing: JPEG, PNG, WebP.
  * <p>
  * Note: JPEG and HEIF files may contain XMP data either inside the Exif data chunk or outside of
  * it. This class will search both locations for XMP data, but if XMP data exist both inside and
@@ -3719,7 +3719,6 @@
             new ExifTag(TAG_Y_CB_CR_SUB_SAMPLING, 530, IFD_FORMAT_USHORT),
             new ExifTag(TAG_Y_CB_CR_POSITIONING, 531, IFD_FORMAT_USHORT),
             new ExifTag(TAG_REFERENCE_BLACK_WHITE, 532, IFD_FORMAT_URATIONAL),
-            new ExifTag(TAG_XMP, 700, IFD_FORMAT_BYTE),
             new ExifTag(TAG_COPYRIGHT, 33432, IFD_FORMAT_STRING),
             new ExifTag(TAG_EXIF_IFD_POINTER, 34665, IFD_FORMAT_ULONG),
             new ExifTag(TAG_GPS_INFO_IFD_POINTER, 34853, IFD_FORMAT_ULONG),
@@ -4653,7 +4652,7 @@
      * other. It's best to use {@link #setAttribute(String,String)} to set all attributes to write
      * and make a single call rather than multiple calls for each attribute.
      * <p>
-     * This method is supported for JPEG, PNG, WebP, and DNG formats.
+     * This method is supported for JPEG, PNG, and WebP formats.
      * <p class="note">
      * Note: after calling this method, any attempts to obtain range information
      * from {@link #getAttributeRange(String)} or {@link #getThumbnailRange()}
@@ -4669,7 +4668,7 @@
     public void saveAttributes() throws IOException {
         if (!isSupportedFormatForSavingAttributes(mMimeType)) {
             throw new IOException("ExifInterface only supports saving attributes for JPEG, PNG, "
-                    + "WebP, and DNG formats.");
+                    + "and WebP formats.");
         }
         if (mSeekableFileDescriptor == null && mFilename == null) {
             throw new IOException(
@@ -4738,10 +4737,6 @@
                 savePngAttributes(bufferedIn, bufferedOut);
             } else if (mMimeType == IMAGE_TYPE_WEBP) {
                 saveWebpAttributes(bufferedIn, bufferedOut);
-            } else if (mMimeType == IMAGE_TYPE_DNG || mMimeType == IMAGE_TYPE_UNKNOWN) {
-                ByteOrderedDataOutputStream dataOutputStream =
-                        new ByteOrderedDataOutputStream(bufferedOut, BIG_ENDIAN);
-                writeExifSegment(dataOutputStream);
             }
         } catch (Exception e) {
             try {
@@ -8076,8 +8071,7 @@
 
     private static boolean isSupportedFormatForSavingAttributes(int mimeType) {
         if (mimeType == IMAGE_TYPE_JPEG || mimeType == IMAGE_TYPE_PNG
-                || mimeType == IMAGE_TYPE_WEBP || mimeType == IMAGE_TYPE_DNG
-                || mimeType == IMAGE_TYPE_UNKNOWN) {
+                || mimeType == IMAGE_TYPE_WEBP) {
             return true;
         }
         return false;
diff --git a/fragment/fragment-ktx/api/current.ignore b/fragment/fragment-ktx/api/current.ignore
deleted file mode 100644
index a75bd4a..0000000
--- a/fragment/fragment-ktx/api/current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
-    Method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy has changed return type from kotlin.Lazy<VM> to kotlin.Lazy<? extends VM>
-
-
-InvalidNullConversion: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
-    Attempted to remove @NonNull annotation from method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy(androidx.fragment.app.Fragment,kotlin.reflect.KClass<VM>,kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>,kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>)
diff --git a/fragment/fragment-ktx/api/restricted_current.ignore b/fragment/fragment-ktx/api/restricted_current.ignore
deleted file mode 100644
index a75bd4a..0000000
--- a/fragment/fragment-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,7 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
-    Method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy has changed return type from kotlin.Lazy<VM> to kotlin.Lazy<? extends VM>
-
-
-InvalidNullConversion: androidx.fragment.app.FragmentViewModelLazyKt#createViewModelLazy(androidx.fragment.app.Fragment, kotlin.reflect.KClass<VM>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>, kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>):
-    Attempted to remove @NonNull annotation from method androidx.fragment.app.FragmentViewModelLazyKt.createViewModelLazy(androidx.fragment.app.Fragment,kotlin.reflect.KClass<VM>,kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelStore>,kotlin.jvm.functions.Function0<? extends androidx.lifecycle.ViewModelProvider.Factory>)
diff --git a/fragment/fragment/api/current.ignore b/fragment/fragment/api/current.ignore
deleted file mode 100644
index 653547a..0000000
--- a/fragment/fragment/api/current.ignore
+++ /dev/null
@@ -1,9 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.fragment.app.strictmode.FragmentStrictMode#setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.fragment.app.strictmode.FragmentStrictMode.setDefaultPolicy
-
-
-RemovedMethod: androidx.fragment.app.FragmentActivity#onMultiWindowModeChanged(boolean):
-    Removed method androidx.fragment.app.FragmentActivity.onMultiWindowModeChanged(boolean)
-RemovedMethod: androidx.fragment.app.FragmentActivity#onPictureInPictureModeChanged(boolean):
-    Removed method androidx.fragment.app.FragmentActivity.onPictureInPictureModeChanged(boolean)
diff --git a/fragment/fragment/api/current.txt b/fragment/fragment/api/current.txt
index 32377c9..d10b6de 100644
--- a/fragment/fragment/api/current.txt
+++ b/fragment/fragment/api/current.txt
@@ -13,7 +13,7 @@
     method public boolean isCancelable();
     method public void onCancel(android.content.DialogInterface);
     method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
-    method public void onDismiss(android.content.DialogInterface);
+    method @CallSuper public void onDismiss(android.content.DialogInterface);
     method public final android.app.Dialog requireDialog();
     method public void setCancelable(boolean);
     method public void setShowsDialog(boolean);
diff --git a/fragment/fragment/api/public_plus_experimental_current.txt b/fragment/fragment/api/public_plus_experimental_current.txt
index 32377c9..d10b6de 100644
--- a/fragment/fragment/api/public_plus_experimental_current.txt
+++ b/fragment/fragment/api/public_plus_experimental_current.txt
@@ -13,7 +13,7 @@
     method public boolean isCancelable();
     method public void onCancel(android.content.DialogInterface);
     method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
-    method public void onDismiss(android.content.DialogInterface);
+    method @CallSuper public void onDismiss(android.content.DialogInterface);
     method public final android.app.Dialog requireDialog();
     method public void setCancelable(boolean);
     method public void setShowsDialog(boolean);
diff --git a/fragment/fragment/api/restricted_current.ignore b/fragment/fragment/api/restricted_current.ignore
deleted file mode 100644
index 995cd20..0000000
--- a/fragment/fragment/api/restricted_current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.fragment.app.strictmode.FragmentStrictMode#setDefaultPolicy(androidx.fragment.app.strictmode.FragmentStrictMode.Policy) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.fragment.app.strictmode.FragmentStrictMode.setDefaultPolicy
-
-
-RemovedDeprecatedMethod: androidx.fragment.app.FragmentActivity#onPrepareOptionsPanel(android.view.View, android.view.Menu):
-    Removed deprecated method androidx.fragment.app.FragmentActivity.onPrepareOptionsPanel(android.view.View,android.view.Menu)
-
-
-RemovedMethod: androidx.fragment.app.FragmentActivity#onMultiWindowModeChanged(boolean):
-    Removed method androidx.fragment.app.FragmentActivity.onMultiWindowModeChanged(boolean)
-RemovedMethod: androidx.fragment.app.FragmentActivity#onPictureInPictureModeChanged(boolean):
-    Removed method androidx.fragment.app.FragmentActivity.onPictureInPictureModeChanged(boolean)
diff --git a/fragment/fragment/api/restricted_current.txt b/fragment/fragment/api/restricted_current.txt
index 3c1054e..44528a7 100644
--- a/fragment/fragment/api/restricted_current.txt
+++ b/fragment/fragment/api/restricted_current.txt
@@ -13,7 +13,7 @@
     method public boolean isCancelable();
     method public void onCancel(android.content.DialogInterface);
     method @MainThread public android.app.Dialog onCreateDialog(android.os.Bundle?);
-    method public void onDismiss(android.content.DialogInterface);
+    method @CallSuper public void onDismiss(android.content.DialogInterface);
     method public final android.app.Dialog requireDialog();
     method public void setCancelable(boolean);
     method public void setShowsDialog(boolean);
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
index c50be0d..dfd3fbc 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/DialogFragmentTest.kt
@@ -202,32 +202,6 @@
         fc.dispatchDestroy()
     }
 
-    @Test
-    fun testDismissDialogFragmentNoSuperCall() {
-        with(ActivityScenario.launch(EmptyFragmentTestActivity::class.java)) {
-            val fm = withActivity { supportFragmentManager }
-            val dialogFragment = TestDialogFragment()
-
-            dialogFragment.show(fm, null)
-            executePendingTransactions()
-
-            val dialog = dialogFragment.dialog
-            assertWithMessage("Dialog should be shown")
-                .that(dialog?.isShowing)
-                .isTrue()
-
-            dialogFragment.dismiss()
-            executePendingTransactions()
-
-            assertWithMessage("Ensure onDismiss was actually called")
-                .that(dialogFragment.onDismissedCalled)
-                .isTrue()
-            assertWithMessage("Dialog should be removed")
-                .that(dialog?.isShowing)
-                .isFalse()
-        }
-    }
-
     @UiThreadTest
     @Test
     fun testDialogFragmentInLayout() {
@@ -551,7 +525,6 @@
 
     class TestDialogFragment(val setShowsDialog: Boolean = false) : DialogFragment() {
         var >
-        var >
 
         override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
             if (setShowsDialog) {
@@ -570,10 +543,6 @@
             super.onCancel(dialog)
             >
         }
-
-        override fun onDismiss(dialog: DialogInterface) {
-            >
-        }
     }
 
     class TestDialogFragmentWithChild(
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ProviderCallbackTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ProviderCallbackTest.kt
new file mode 100644
index 0000000..5e750b6
--- /dev/null
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ProviderCallbackTest.kt
@@ -0,0 +1,227 @@
+/*
+ * 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.fragment.app
+
+import android.content.ComponentCallbacks2
+import android.content.res.Configuration
+import androidx.fragment.app.test.FragmentTestActivity
+import androidx.fragment.test.R
+import androidx.test.core.app.ActivityScenario
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.testutils.withActivity
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@LargeTest
+@RunWith(AndroidJUnit4::class)
+class ProviderCallbackTest {
+
+    @Test
+    fun onConfigurationChanged() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity { supportFragmentManager }
+            val fragment = CallbackFragment()
+
+            withActivity {
+                fm.beginTransaction()
+                    .replace(R.id.content, fragment)
+                    .commitNow()
+            }
+
+            withActivity {
+                val newConfig = Configuration(resources.configuration)
+                onConfigurationChanged(newConfig)
+            }
+            assertThat(fragment.configChangedCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun onConfigurationChangedNestedFragments() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fm = withActivity { supportFragmentManager }
+            val parent = StrictViewFragment(R.layout.fragment_container_view)
+            val child = CallbackFragment()
+
+            withActivity {
+                fm.beginTransaction()
+                    .replace(R.id.content, parent)
+                    .commitNow()
+
+                parent.childFragmentManager.beginTransaction()
+                    .replace(R.id.fragment_container_view, child)
+                    .commitNow()
+            }
+
+            withActivity {
+                val newConfig = Configuration(resources.configuration)
+                onConfigurationChanged(newConfig)
+            }
+            assertThat(child.configChangedCount).isEqualTo(1)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun onMultiWindowModeChanged() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fragment = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, fragment)
+                    .commitNow()
+
+                val newConfig = Configuration(resources.configuration)
+                onMultiWindowModeChanged(true, newConfig)
+            }
+            assertThat(fragment.multiWindowChangedCount).isEqualTo(1)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun onMultiWindowModeChangedNestedFragments() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val parent = StrictViewFragment(R.layout.fragment_container_view)
+            val child = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, parent)
+                    .commitNow()
+
+                parent.childFragmentManager.beginTransaction()
+                    .replace(R.id.fragment_container_view, child)
+                    .commitNow()
+
+                val newConfig = Configuration(resources.configuration)
+                onMultiWindowModeChanged(true, newConfig)
+            }
+
+            assertThat(child.multiWindowChangedCount).isEqualTo(1)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Suppress("DEPRECATION")
+    @Test
+    fun onPictureInPictureModeChanged() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fragment = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, fragment)
+                    .commitNow()
+
+                val newConfig = Configuration(resources.configuration)
+                onPictureInPictureModeChanged(true, newConfig)
+            }
+            assertThat(fragment.pictureModeChangedCount).isEqualTo(1)
+        }
+    }
+
+    @SdkSuppress(minSdkVersion = 26)
+    @Test
+    fun onPictureInPictureModeChangedNestedFragments() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val parent = StrictViewFragment(R.layout.fragment_container_view)
+            val child = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, parent)
+                    .commitNow()
+
+                parent.childFragmentManager.beginTransaction()
+                    .replace(R.id.fragment_container_view, child)
+                    .commitNow()
+
+                val newConfig = Configuration(resources.configuration)
+                onPictureInPictureModeChanged(true, newConfig)
+            }
+
+            assertThat(child.pictureModeChangedCount).isEqualTo(1)
+        }
+    }
+
+    @Suppress("DEPRECATION")
+    @Test
+    fun onLowMemory() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val fragment = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, fragment)
+                    .commitNow()
+
+                onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
+            }
+            assertThat(fragment.onLowMemoryCount).isEqualTo(1)
+        }
+    }
+
+    @Test
+    fun onLowMemoryNestedFragments() {
+        with(ActivityScenario.launch(FragmentTestActivity::class.java)) {
+            val parent = StrictViewFragment(R.layout.fragment_container_view)
+            val child = CallbackFragment()
+
+            withActivity {
+                supportFragmentManager.beginTransaction()
+                    .replace(R.id.content, parent)
+                    .commitNow()
+
+                parent.childFragmentManager.beginTransaction()
+                    .replace(R.id.fragment_container_view, child)
+                    .commitNow()
+
+                onTrimMemory(ComponentCallbacks2.TRIM_MEMORY_COMPLETE)
+            }
+
+            assertThat(child.onLowMemoryCount).isEqualTo(1)
+        }
+    }
+}
+
+class CallbackFragment : StrictViewFragment() {
+    var configChangedCount = 0
+    var multiWindowChangedCount = 0
+    var pictureModeChangedCount = 0
+    var >
+
+    override fun onConfigurationChanged(newConfig: Configuration) {
+        configChangedCount++
+    }
+
+    override fun onMultiWindowModeChanged(isInMultiWindowMode: Boolean) {
+        multiWindowChangedCount++
+    }
+
+    override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean) {
+        pictureModeChangedCount++
+    }
+
+    override fun onLowMemory() {
+        onLowMemoryCount++
+    }
+}
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
index c560c80..5ab086a 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/DialogFragment.java
@@ -35,6 +35,7 @@
 import android.view.WindowManager;
 
 import androidx.activity.ComponentDialog;
+import androidx.annotation.CallSuper;
 import androidx.annotation.IntDef;
 import androidx.annotation.LayoutRes;
 import androidx.annotation.MainThread;
@@ -130,16 +131,6 @@
         @Override
         public void onDismiss(@Nullable DialogInterface dialog) {
             if (mDialog != null) {
-                if (!mViewDestroyed) {
-                    // Note: we need to use allowStateLoss, because the dialog
-                    // dispatches this asynchronously so we can receive the call
-                    // after the activity is paused.  Worst case, when the user comes
-                    // back to the activity they see the dialog again.
-                    if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
-                        Log.d(TAG, "onDismiss called for DialogFragment " + this);
-                    }
-                    dismissInternal(true, true, false);
-                }
                 DialogFragment.this.onDismiss(mDialog);
             }
         }
@@ -650,8 +641,19 @@
     public void onCancel(@NonNull DialogInterface dialog) {
     }
 
+    @CallSuper
     @Override
     public void onDismiss(@NonNull DialogInterface dialog) {
+        if (!mViewDestroyed) {
+            // Note: we need to use allowStateLoss, because the dialog
+            // dispatches this asynchronously so we can receive the call
+            // after the activity is paused.  Worst case, when the user comes
+            // back to the activity they see the dialog again.
+            if (FragmentManager.isLoggingEnabled(Log.DEBUG)) {
+                Log.d(TAG, "onDismiss called for DialogFragment " + this);
+            }
+            dismissInternal(true, true, false);
+        }
     }
 
     private void prepareDialog(@Nullable Bundle savedInstanceState) {
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index 2a5a8e4..0901d48 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -3217,22 +3217,18 @@
 
     void performMultiWindowModeChanged(boolean isInMultiWindowMode) {
         onMultiWindowModeChanged(isInMultiWindowMode);
-        mChildFragmentManager.dispatchMultiWindowModeChanged(isInMultiWindowMode);
     }
 
     void performPictureInPictureModeChanged(boolean isInPictureInPictureMode) {
         onPictureInPictureModeChanged(isInPictureInPictureMode);
-        mChildFragmentManager.dispatchPictureInPictureModeChanged(isInPictureInPictureMode);
     }
 
     void performConfigurationChanged(@NonNull Configuration newConfig) {
         onConfigurationChanged(newConfig);
-        mChildFragmentManager.dispatchConfigurationChanged(newConfig);
     }
 
     void performLowMemory() {
         onLowMemory();
-        mChildFragmentManager.dispatchLowMemory();
     }
 
     /*
diff --git a/glance/glance-appwidget/api/current.txt b/glance/glance-appwidget/api/current.txt
index baf72d7..94ab9a6 100644
--- a/glance/glance-appwidget/api/current.txt
+++ b/glance/glance-appwidget/api/current.txt
@@ -82,6 +82,11 @@
     ctor public GlanceAppWidgetReceiver();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
+    field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
+    field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
+  }
+
+  public static final class GlanceAppWidgetReceiver.Companion {
   }
 
   public final class GlanceAppWidgetReceiverKt {
diff --git a/glance/glance-appwidget/api/public_plus_experimental_current.txt b/glance/glance-appwidget/api/public_plus_experimental_current.txt
index 77c2f0f..e16ae856 100644
--- a/glance/glance-appwidget/api/public_plus_experimental_current.txt
+++ b/glance/glance-appwidget/api/public_plus_experimental_current.txt
@@ -85,6 +85,11 @@
     ctor public GlanceAppWidgetReceiver();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
+    field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
+    field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
+  }
+
+  public static final class GlanceAppWidgetReceiver.Companion {
   }
 
   public final class GlanceAppWidgetReceiverKt {
diff --git a/glance/glance-appwidget/api/restricted_current.txt b/glance/glance-appwidget/api/restricted_current.txt
index baf72d7..94ab9a6 100644
--- a/glance/glance-appwidget/api/restricted_current.txt
+++ b/glance/glance-appwidget/api/restricted_current.txt
@@ -82,6 +82,11 @@
     ctor public GlanceAppWidgetReceiver();
     method public abstract androidx.glance.appwidget.GlanceAppWidget getGlanceAppWidget();
     property public abstract androidx.glance.appwidget.GlanceAppWidget glanceAppWidget;
+    field public static final String ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE";
+    field public static final androidx.glance.appwidget.GlanceAppWidgetReceiver.Companion Companion;
+  }
+
+  public static final class GlanceAppWidgetReceiver.Companion {
   }
 
   public final class GlanceAppWidgetReceiverKt {
diff --git a/glance/glance-appwidget/integration-tests/template-demos/src/main/java/androidx/glance/appwidget/template/demos/ListDemoWidget.kt b/glance/glance-appwidget/integration-tests/template-demos/src/main/java/androidx/glance/appwidget/template/demos/ListDemoWidget.kt
index 23bcab3..811e8f3 100644
--- a/glance/glance-appwidget/integration-tests/template-demos/src/main/java/androidx/glance/appwidget/template/demos/ListDemoWidget.kt
+++ b/glance/glance-appwidget/integration-tests/template-demos/src/main/java/androidx/glance/appwidget/template/demos/ListDemoWidget.kt
@@ -20,7 +20,6 @@
 
 import android.content.Context
 import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.Color
 import androidx.datastore.preferences.core.Preferences
 import androidx.datastore.preferences.core.intPreferencesKey
 import androidx.glance.GlanceComposable
@@ -37,14 +36,17 @@
 import androidx.glance.appwidget.template.GlanceTemplateAppWidget
 import androidx.glance.appwidget.template.ListTemplate
 import androidx.glance.currentState
+import androidx.glance.template.ActionBlock
+import androidx.glance.template.HeaderBlock
+import androidx.glance.template.ImageBlock
 import androidx.glance.template.ListStyle
 import androidx.glance.template.ListTemplateData
 import androidx.glance.template.ListTemplateItem
 import androidx.glance.template.TemplateImageButton
 import androidx.glance.template.TemplateImageWithDescription
 import androidx.glance.template.TemplateText
+import androidx.glance.template.TextBlock
 import androidx.glance.template.TextType
-import androidx.glance.unit.ColorProvider
 
 /**
  * List demo with list items in full details and list header with action button using data and list
@@ -145,42 +147,64 @@
             }
             content.add(
                 ListTemplateItem(
-                    title = TemplateText("Title Medium", TextType.Title),
-                    body = TemplateText(
-                        "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
-                        TextType.Body
+                    textBlock = TextBlock(
+                        text1 = TemplateText("Title Medium", TextType.Title),
+                        text2 = if (listStyle == ListStyle.Full) TemplateText(
+                            "Lorem ipsum dolor sit amet, consectetur adipiscing elit",
+                            TextType.Body
+                        ) else null,
+                        text3 = if (listStyle == ListStyle.Full) TemplateText(
+                            label,
+                            TextType.Label
+                        ) else null,
+                        priority = 1,
                     ),
-                    label = TemplateText(label, TextType.Label),
-                    image = TemplateImageWithDescription(ImageProvider(R.drawable.compose), "$i"),
-                    button = TemplateImageButton(
-                        itemSelectAction(
-                            actionParametersOf(ClickedKey to i)
+                    imageBlock = ImageBlock(
+                        images = listOf(
+                            TemplateImageWithDescription(
+                                ImageProvider(R.drawable.compose),
+                                "$i"
+                            )
                         ),
-                        TemplateImageWithDescription(
-                            ImageProvider(R.drawable.ic_favorite),
-                            "button"
-                        )
+                        priority = 0, // ahead of textBlock
                     ),
-                    action = itemSelectAction(actionParametersOf(ClickedKey to i)),
+                    actionBlock = ActionBlock(
+                        actionButtons = listOf(
+                            TemplateImageButton(
+                                itemSelectAction(
+                                    actionParametersOf(ClickedKey to i)
+                                ),
+                                TemplateImageWithDescription(
+                                    ImageProvider(R.drawable.ic_favorite),
+                                    "button"
+                                )
+                            ),
+                        ),
+                    ),
                 )
             )
         }
         ListTemplate(
             ListTemplateData(
-                header = if (showHeader) TemplateText(
-                    "List Demo",
-                    TextType.Title
-                ) else null,
-                headerIcon = if (showHeader) TemplateImageWithDescription(
-                    ImageProvider(R.drawable.ic_widget),
-                    "Logo"
-                ) else null,
-                button = if (showHeader && showHeaderAction) TemplateImageButton(
-                    headerButtonAction(),
-                    TemplateImageWithDescription(ImageProvider(R.drawable.ic_add), "Add item")
+                headerBlock = if (showHeader) HeaderBlock(
+                    text = TemplateText("List Demo", TextType.Title),
+                    icon = TemplateImageWithDescription(
+                        ImageProvider(R.drawable.ic_widget),
+                        "Logo"
+                    ),
+                    actionBlock = if (showHeaderAction) ActionBlock(
+                        actionButtons = listOf(
+                            TemplateImageButton(
+                                headerButtonAction(),
+                                TemplateImageWithDescription(
+                                    ImageProvider(R.drawable.ic_add),
+                                    "Add item"
+                                )
+                            ),
+                        ),
+                    ) else null,
                 ) else null,
                 listContent = content,
-                backgroundColor = ColorProvider(Color(0xDDD7E8CD)),
                 listStyle = listStyle
             )
         )
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
index a276ec9..461eace 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/GlanceAppWidgetReceiver.kt
@@ -50,8 +50,21 @@
  */
 abstract class GlanceAppWidgetReceiver : AppWidgetProvider() {
 
-    private companion object {
+    companion object {
         private const val TAG = "GlanceAppWidgetReceiver"
+
+        /**
+         * Action for a broadcast intent that will try to update all instances of a Glance App
+         * Widget for debugging.
+         * <pre>
+         * adb shell am broadcast -a androidx.glance.appwidget.action.DEBUG_UPDATE -n APP/COMPONENT
+         * </pre>
+         * where APP/COMPONENT is the manifest component for the GlanceAppWidgetReceiver subclass.
+         * This only works if the Receiver is exported (or the target device has adb running as
+         * root), and has androidx.glance.appwidget.DEBUG_UPDATE in its intent-filter.
+         * This should only be done for debug builds and disabled for release.
+         */
+        const val ACTION_DEBUG_UPDATE = "androidx.glance.appwidget.action.DEBUG_UPDATE"
     }
 
     /**
@@ -110,8 +123,11 @@
     }
 
     override fun onReceive(context: Context, intent: Intent) {
+        val forceUpdateAllWidgets = intent.action == Intent.ACTION_LOCALE_CHANGED ||
+            intent.action == ACTION_DEBUG_UPDATE
+
         runAndLogExceptions {
-            if (intent.action == Intent.ACTION_LOCALE_CHANGED) {
+            if (forceUpdateAllWidgets) {
                 val appWidgetManager = AppWidgetManager.getInstance(context)
                 val componentName =
                     ComponentName(context.packageName, checkNotNull(javaClass.canonicalName))
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/GlanceAppWidgetTemplates.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/GlanceAppWidgetTemplates.kt
index 4a3c354..09fed4a 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/GlanceAppWidgetTemplates.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/GlanceAppWidgetTemplates.kt
@@ -239,6 +239,47 @@
     }
 }
 
+/**
+ * Displays an entity of a block of texts from a [TextBlock] and an image from an [ImageBlock]
+ * ordered by priority of the blocks with the default to the [TextBlock] being ahead of the
+ * [ImageBlock] if they have the same priority.
+ *
+ * @param textBlock The [TextBlock]  for an entity.
+ * @param imageBlock The [ImageBlock] for an entity.
+ * @param modifier The modifier for the textBlock in relation to the imageBlock.
+ */
+@Composable
+internal fun TextAndImageBlockTemplate(
+    textBlock: TextBlock,
+    imageBlock: ImageBlock? = null,
+    modifier: GlanceModifier = GlanceModifier
+) {
+    if (imageBlock == null || imageBlock.images.isEmpty()) {
+        TextBlockTemplate(textBlock)
+    } else {
+        // Show first block by lower numbered priority
+        if (textBlock.priority <= imageBlock.priority) {
+            Column(
+                modifier = modifier,
+                verticalAlignment = Alignment.Vertical.CenterVertically
+            ) {
+                TextBlockTemplate(textBlock)
+            }
+            Spacer(modifier = GlanceModifier.width(16.dp))
+            SingleImageBlockTemplate(imageBlock)
+        } else {
+            SingleImageBlockTemplate(imageBlock)
+            Spacer(modifier = GlanceModifier.width(16.dp))
+            Column(
+                modifier = modifier,
+                verticalAlignment = Alignment.Vertical.CenterVertically
+            ) {
+                TextBlockTemplate(textBlock)
+            }
+        }
+    }
+}
+
 private enum class DisplaySize {
     Small,
     Medium,
diff --git a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/ListTemplateLayouts.kt b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/ListTemplateLayouts.kt
index deaa5f2..0c4ef2f 100644
--- a/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/ListTemplateLayouts.kt
+++ b/glance/glance-appwidget/src/androidMain/kotlin/androidx/glance/appwidget/template/ListTemplateLayouts.kt
@@ -20,11 +20,8 @@
 
 import androidx.compose.runtime.Composable
 import androidx.compose.ui.unit.dp
-import androidx.compose.ui.unit.sp
 import androidx.glance.GlanceComposable
 import androidx.glance.GlanceModifier
-import androidx.glance.Image
-import androidx.glance.action.clickable
 import androidx.glance.appwidget.lazy.LazyColumn
 import androidx.glance.appwidget.lazy.itemsIndexed
 import androidx.glance.background
@@ -35,14 +32,11 @@
 import androidx.glance.layout.fillMaxSize
 import androidx.glance.layout.height
 import androidx.glance.layout.padding
-import androidx.glance.layout.width
 import androidx.glance.template.ListStyle
 import androidx.glance.template.ListTemplateData
+import androidx.glance.template.LocalTemplateColors
 import androidx.glance.template.LocalTemplateMode
 import androidx.glance.template.TemplateMode
-import androidx.glance.text.FontWeight
-import androidx.glance.text.Text
-import androidx.glance.text.TextStyle
 
 /**
  * Composable layout for a list template app widget. The template is optimized to display a list of
@@ -63,24 +57,17 @@
 
 @Composable
 private fun WidgetLayoutCollapsed(data: ListTemplateData) {
-    Column(modifier = createTopLevelModifier(data)) {
+    Column(modifier = createTopLevelModifier()) {
         if (data.listStyle == ListStyle.Full) {
-            AppWidgetTemplateHeader(data.headerIcon, data.header, data.button)
+            HeaderBlockTemplate(data.headerBlock)
             Spacer(modifier = GlanceModifier.height(4.dp))
         }
         data.listContent.firstOrNull()?.let { item ->
-            var itemModifier = GlanceModifier.fillMaxSize().padding(vertical = 8.dp)
-            item.action?.let { action -> itemModifier = itemModifier.clickable(action) }
+            val itemModifier = GlanceModifier.fillMaxSize().padding(vertical = 8.dp)
             Row(modifier = itemModifier) {
                 Column(modifier = GlanceModifier.defaultWeight()) {
                     Spacer(modifier = GlanceModifier.height(4.dp))
-                    Text(item.title.text, style = TextStyle(fontSize = 18.sp), maxLines = 1)
-                    if (data.listStyle == ListStyle.Full) {
-                        item.body?.let { body ->
-                            Spacer(modifier = GlanceModifier.height(4.dp))
-                            Text(body.text, style = TextStyle(fontSize = 16.sp), maxLines = 2)
-                        }
-                    }
+                    TextBlockTemplate(item.textBlock)
                 }
             }
         }
@@ -89,68 +76,33 @@
 
 @Composable
 private fun WidgetLayoutExpanded(data: ListTemplateData) {
-    Column(modifier = createTopLevelModifier(data)) {
+    Column(modifier = createTopLevelModifier()) {
         if (data.listStyle == ListStyle.Full) {
-            AppWidgetTemplateHeader(data.headerIcon, data.header, data.button)
-            Spacer(modifier = GlanceModifier.height(16.dp))
-        }
-        data.title?.let { title ->
-            Text(title.text, style = TextStyle(fontSize = 20.sp))
+            HeaderBlockTemplate(data.headerBlock)
             Spacer(modifier = GlanceModifier.height(16.dp))
         }
         LazyColumn {
             itemsIndexed(data.listContent) { _, item ->
-                // TODO: Extract and allow override
                 val itemSpacer = if (data.listStyle == ListStyle.Full) 8.dp else 0.dp
-                var itemModifier = GlanceModifier.fillMaxSize().padding(vertical = itemSpacer)
-                item.action?.let { action -> itemModifier = itemModifier.clickable(action) }
+                val itemModifier = GlanceModifier.fillMaxSize().padding(vertical = itemSpacer)
                 Row(
                     modifier = itemModifier,
                     verticalAlignment = Alignment.Vertical.CenterVertically,
                 ) {
-                    if (data.listStyle == ListStyle.Full) {
-                        item.image?.let { image ->
-                            Image(
-                                provider = image.image,
-                                contentDescription = image.description,
-                            )
-                            Spacer(modifier = GlanceModifier.width(16.dp))
-                        }
-                    }
-                    Column(
-                        modifier = GlanceModifier.defaultWeight(),
-                        verticalAlignment = Alignment.Vertical.CenterVertically
-                    ) {
-                        Text(
-                            item.title.text,
-                            style = TextStyle(fontWeight = FontWeight.Medium, fontSize = 18.sp),
-                            maxLines = 1
-                        )
-                        if (data.listStyle == ListStyle.Full) {
-                            item.body?.let { body ->
-                                Text(body.text, style = TextStyle(fontSize = 16.sp), maxLines = 2)
-                            }
-                        }
-                        if (data.listStyle == ListStyle.Full) {
-                            item.label?.let { label ->
-                                Spacer(modifier = GlanceModifier.height(4.dp))
-                                Text(label.text)
-                            }
-                        }
-                    }
-                    item.button?.let { button ->
-                        Spacer(modifier = GlanceModifier.width(16.dp))
-                        AppWidgetTemplateButton(button)
-                    }
+                    TextAndImageBlockTemplate(
+                        item.textBlock,
+                        item.imageBlock,
+                        GlanceModifier.defaultWeight()
+                    )
+                    ActionBlockTemplate(item.actionBlock)
                 }
             }
         }
     }
 }
 
-private fun createTopLevelModifier(data: ListTemplateData): GlanceModifier {
-    var modifier = GlanceModifier.fillMaxSize().padding(16.dp)
-    data.backgroundColor?.let { color -> modifier = modifier.background(color) }
-
-    return modifier
+@Composable
+private fun createTopLevelModifier(): GlanceModifier {
+    return GlanceModifier.fillMaxSize().padding(16.dp)
+        .background(LocalTemplateColors.current.primaryContainer)
 }
diff --git a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/GlanceTileServiceTest.kt b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/GlanceTileServiceTest.kt
index f086c17..6be7404 100644
--- a/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/GlanceTileServiceTest.kt
+++ b/glance/glance-wear-tiles/src/test/kotlin/androidx/glance/wear/tiles/GlanceTileServiceTest.kt
@@ -57,6 +57,7 @@
 import java.util.Arrays
 import kotlin.test.assertIs
 import kotlinx.coroutines.test.UnconfinedTestDispatcher
+import org.junit.Ignore
 
 @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class)
 @RunWith(RobolectricTestRunner::class)
@@ -138,6 +139,7 @@
         assertThat(text.text!!.value).isEqualTo("Hello World!")
     }
 
+    @Ignore("resourcesVersion is not matching - b/246239580")
     @Test
     fun tileProviderReturnsTimelineTile() = fakeCoroutineScope.runTest {
         // Request is currently un-used, provide an empty one.
diff --git a/glance/glance/api/current.txt b/glance/glance/api/current.txt
index ef98ce0..2c9aae5 100644
--- a/glance/glance/api/current.txt
+++ b/glance/glance/api/current.txt
@@ -532,37 +532,23 @@
   }
 
   public final class ListTemplateData {
-    ctor public ListTemplateData(optional androidx.glance.template.TemplateImageWithDescription? headerIcon, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional androidx.glance.template.TemplateText? header, optional androidx.glance.template.TemplateText? title, optional androidx.glance.template.TemplateButton? button, optional androidx.glance.unit.ColorProvider? backgroundColor, optional int listStyle);
-    method public androidx.glance.unit.ColorProvider? getBackgroundColor();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateText? getHeader();
-    method public androidx.glance.template.TemplateImageWithDescription? getHeaderIcon();
+    ctor public ListTemplateData(optional androidx.glance.template.HeaderBlock? headerBlock, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional int listStyle);
+    method public androidx.glance.template.HeaderBlock? getHeaderBlock();
     method public java.util.List<androidx.glance.template.ListTemplateItem> getListContent();
     method public int getListStyle();
-    method public androidx.glance.template.TemplateText? getTitle();
-    property public final androidx.glance.unit.ColorProvider? backgroundColor;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateText? header;
-    property public final androidx.glance.template.TemplateImageWithDescription? headerIcon;
+    property public final androidx.glance.template.HeaderBlock? headerBlock;
     property public final java.util.List<androidx.glance.template.ListTemplateItem> listContent;
     property public final int listStyle;
-    property public final androidx.glance.template.TemplateText? title;
   }
 
   public final class ListTemplateItem {
-    ctor public ListTemplateItem(androidx.glance.template.TemplateText title, optional androidx.glance.template.TemplateText? body, optional androidx.glance.template.TemplateText? label, optional androidx.glance.action.Action? action, optional androidx.glance.template.TemplateImageWithDescription? image, optional androidx.glance.template.TemplateButton? button);
-    method public androidx.glance.action.Action? getAction();
-    method public androidx.glance.template.TemplateText? getBody();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateImageWithDescription? getImage();
-    method public androidx.glance.template.TemplateText? getLabel();
-    method public androidx.glance.template.TemplateText getTitle();
-    property public final androidx.glance.action.Action? action;
-    property public final androidx.glance.template.TemplateText? body;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateImageWithDescription? image;
-    property public final androidx.glance.template.TemplateText? label;
-    property public final androidx.glance.template.TemplateText title;
+    ctor public ListTemplateItem(androidx.glance.template.TextBlock textBlock, optional androidx.glance.template.ImageBlock? imageBlock, optional androidx.glance.template.ActionBlock? actionBlock);
+    method public androidx.glance.template.ActionBlock? getActionBlock();
+    method public androidx.glance.template.ImageBlock? getImageBlock();
+    method public androidx.glance.template.TextBlock getTextBlock();
+    property public final androidx.glance.template.ActionBlock? actionBlock;
+    property public final androidx.glance.template.ImageBlock? imageBlock;
+    property public final androidx.glance.template.TextBlock textBlock;
   }
 
   public final class SingleEntityTemplateData {
diff --git a/glance/glance/api/public_plus_experimental_current.txt b/glance/glance/api/public_plus_experimental_current.txt
index ef98ce0..2c9aae5 100644
--- a/glance/glance/api/public_plus_experimental_current.txt
+++ b/glance/glance/api/public_plus_experimental_current.txt
@@ -532,37 +532,23 @@
   }
 
   public final class ListTemplateData {
-    ctor public ListTemplateData(optional androidx.glance.template.TemplateImageWithDescription? headerIcon, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional androidx.glance.template.TemplateText? header, optional androidx.glance.template.TemplateText? title, optional androidx.glance.template.TemplateButton? button, optional androidx.glance.unit.ColorProvider? backgroundColor, optional int listStyle);
-    method public androidx.glance.unit.ColorProvider? getBackgroundColor();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateText? getHeader();
-    method public androidx.glance.template.TemplateImageWithDescription? getHeaderIcon();
+    ctor public ListTemplateData(optional androidx.glance.template.HeaderBlock? headerBlock, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional int listStyle);
+    method public androidx.glance.template.HeaderBlock? getHeaderBlock();
     method public java.util.List<androidx.glance.template.ListTemplateItem> getListContent();
     method public int getListStyle();
-    method public androidx.glance.template.TemplateText? getTitle();
-    property public final androidx.glance.unit.ColorProvider? backgroundColor;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateText? header;
-    property public final androidx.glance.template.TemplateImageWithDescription? headerIcon;
+    property public final androidx.glance.template.HeaderBlock? headerBlock;
     property public final java.util.List<androidx.glance.template.ListTemplateItem> listContent;
     property public final int listStyle;
-    property public final androidx.glance.template.TemplateText? title;
   }
 
   public final class ListTemplateItem {
-    ctor public ListTemplateItem(androidx.glance.template.TemplateText title, optional androidx.glance.template.TemplateText? body, optional androidx.glance.template.TemplateText? label, optional androidx.glance.action.Action? action, optional androidx.glance.template.TemplateImageWithDescription? image, optional androidx.glance.template.TemplateButton? button);
-    method public androidx.glance.action.Action? getAction();
-    method public androidx.glance.template.TemplateText? getBody();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateImageWithDescription? getImage();
-    method public androidx.glance.template.TemplateText? getLabel();
-    method public androidx.glance.template.TemplateText getTitle();
-    property public final androidx.glance.action.Action? action;
-    property public final androidx.glance.template.TemplateText? body;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateImageWithDescription? image;
-    property public final androidx.glance.template.TemplateText? label;
-    property public final androidx.glance.template.TemplateText title;
+    ctor public ListTemplateItem(androidx.glance.template.TextBlock textBlock, optional androidx.glance.template.ImageBlock? imageBlock, optional androidx.glance.template.ActionBlock? actionBlock);
+    method public androidx.glance.template.ActionBlock? getActionBlock();
+    method public androidx.glance.template.ImageBlock? getImageBlock();
+    method public androidx.glance.template.TextBlock getTextBlock();
+    property public final androidx.glance.template.ActionBlock? actionBlock;
+    property public final androidx.glance.template.ImageBlock? imageBlock;
+    property public final androidx.glance.template.TextBlock textBlock;
   }
 
   public final class SingleEntityTemplateData {
diff --git a/glance/glance/api/restricted_current.txt b/glance/glance/api/restricted_current.txt
index ef98ce0..2c9aae5 100644
--- a/glance/glance/api/restricted_current.txt
+++ b/glance/glance/api/restricted_current.txt
@@ -532,37 +532,23 @@
   }
 
   public final class ListTemplateData {
-    ctor public ListTemplateData(optional androidx.glance.template.TemplateImageWithDescription? headerIcon, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional androidx.glance.template.TemplateText? header, optional androidx.glance.template.TemplateText? title, optional androidx.glance.template.TemplateButton? button, optional androidx.glance.unit.ColorProvider? backgroundColor, optional int listStyle);
-    method public androidx.glance.unit.ColorProvider? getBackgroundColor();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateText? getHeader();
-    method public androidx.glance.template.TemplateImageWithDescription? getHeaderIcon();
+    ctor public ListTemplateData(optional androidx.glance.template.HeaderBlock? headerBlock, optional java.util.List<androidx.glance.template.ListTemplateItem> listContent, optional int listStyle);
+    method public androidx.glance.template.HeaderBlock? getHeaderBlock();
     method public java.util.List<androidx.glance.template.ListTemplateItem> getListContent();
     method public int getListStyle();
-    method public androidx.glance.template.TemplateText? getTitle();
-    property public final androidx.glance.unit.ColorProvider? backgroundColor;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateText? header;
-    property public final androidx.glance.template.TemplateImageWithDescription? headerIcon;
+    property public final androidx.glance.template.HeaderBlock? headerBlock;
     property public final java.util.List<androidx.glance.template.ListTemplateItem> listContent;
     property public final int listStyle;
-    property public final androidx.glance.template.TemplateText? title;
   }
 
   public final class ListTemplateItem {
-    ctor public ListTemplateItem(androidx.glance.template.TemplateText title, optional androidx.glance.template.TemplateText? body, optional androidx.glance.template.TemplateText? label, optional androidx.glance.action.Action? action, optional androidx.glance.template.TemplateImageWithDescription? image, optional androidx.glance.template.TemplateButton? button);
-    method public androidx.glance.action.Action? getAction();
-    method public androidx.glance.template.TemplateText? getBody();
-    method public androidx.glance.template.TemplateButton? getButton();
-    method public androidx.glance.template.TemplateImageWithDescription? getImage();
-    method public androidx.glance.template.TemplateText? getLabel();
-    method public androidx.glance.template.TemplateText getTitle();
-    property public final androidx.glance.action.Action? action;
-    property public final androidx.glance.template.TemplateText? body;
-    property public final androidx.glance.template.TemplateButton? button;
-    property public final androidx.glance.template.TemplateImageWithDescription? image;
-    property public final androidx.glance.template.TemplateText? label;
-    property public final androidx.glance.template.TemplateText title;
+    ctor public ListTemplateItem(androidx.glance.template.TextBlock textBlock, optional androidx.glance.template.ImageBlock? imageBlock, optional androidx.glance.template.ActionBlock? actionBlock);
+    method public androidx.glance.template.ActionBlock? getActionBlock();
+    method public androidx.glance.template.ImageBlock? getImageBlock();
+    method public androidx.glance.template.TextBlock getTextBlock();
+    property public final androidx.glance.template.ActionBlock? actionBlock;
+    property public final androidx.glance.template.ImageBlock? imageBlock;
+    property public final androidx.glance.template.TextBlock textBlock;
   }
 
   public final class SingleEntityTemplateData {
diff --git a/glance/glance/build.gradle b/glance/glance/build.gradle
index 6b5a6fd..182bfb3 100644
--- a/glance/glance/build.gradle
+++ b/glance/glance/build.gradle
@@ -38,7 +38,6 @@
     api("androidx.datastore:datastore-preferences:1.0.0")
 
     implementation("androidx.annotation:annotation:1.1.0")
-    implementation("com.google.android.material:material:1.6.0")
     implementation(libs.kotlinStdlib)
     implementation(project(":compose:runtime:runtime"))
 
@@ -56,6 +55,7 @@
     testImplementation("androidx.datastore:datastore-core:1.0.0")
     testImplementation("androidx.datastore:datastore-preferences-core:1.0.0")
     testImplementation("androidx.datastore:datastore-preferences:1.0.0-rc02")
+    testImplementation("com.google.android.material:material:1.6.0")
 }
 
 android {
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/template/GlanceTemplate.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/template/GlanceTemplate.kt
index 57eeb11..a3df630 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/template/GlanceTemplate.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/template/GlanceTemplate.kt
@@ -152,7 +152,9 @@
  * as an ordering, some may only use it to define which elements are most important when showing
  * smaller layouts. Priority number is zero based with smaller numbers being higher priority.
  * If two blocks has the same priority number, the default order (e.g. text before image)
- * is used. Currently only [TextBlock] and [ImageBlock] comparison are supported in the design.
+ * is used. Currently only [TextBlock] and [ImageBlock] comparison are supported in the design. For
+ * example, the Gallery Template layout determines the ordering of mainTextBlock and mainImageBlock
+ * in [GalleryTemplateData] by their corresponding priority number.
  *
  * @param text1 The text displayed first within the block.
  * @param text2 The text displayed second within the block.
@@ -191,7 +193,7 @@
 
 /**
  * A block of image sequence by certain size and aspect ratio preferences and display priority
- * relative to other blocks such as a [TextBlock].
+ * relative to other blocks such as a [TextBlock]. Priority is the same as defined in [TextBlock].
  *
  * @param images The sequence of images or just one image for display. Default to empty list.
  * @param aspectRatio The preferred aspect ratio of the images. Default to [AspectRatio.Ratio1x1].
diff --git a/glance/glance/src/androidMain/kotlin/androidx/glance/template/ListTemplateData.kt b/glance/glance/src/androidMain/kotlin/androidx/glance/template/ListTemplateData.kt
index 9beb71d..e5659b8 100644
--- a/glance/glance/src/androidMain/kotlin/androidx/glance/template/ListTemplateData.kt
+++ b/glance/glance/src/androidMain/kotlin/androidx/glance/template/ListTemplateData.kt
@@ -16,9 +16,6 @@
 
 package androidx.glance.template
 
-import androidx.glance.action.Action
-import androidx.glance.unit.ColorProvider
-
 /**
  * The semantic data required to build List Template layouts.
  *
@@ -30,35 +27,19 @@
  * AppWidget can put an action button in the header while Glance Tile might layout the button
  * without a header. Only the level of data details can be indicated for use in the list style.
  *
- * @param headerIcon Logo icon displayed in the list header by [TemplateImageWithDescription].
- * Default to no display when null valued.
+ * @param headerBlock The header of the template by [HeaderBlock].
  * @param listContent List of items by [ListTemplateItem]. Default to empty list.
- * @param header Main header text by [TemplateText]. Default to no display when null valued.
- * @param title Text section title by [TemplateText] independent of header. Default to no display
- * when null valued.
- * @param button List action button by [TemplateButton] that can be a part of the header with its
- * own icon image. Default to no display when null valued.
- * @param backgroundColor Glanceable background color by [ColorProvider] for the whole list.
- * Default to the system background color.
  * @param listStyle The level of data details by [ListStyle]. Default to the [ListStyle.Full] level.
  */
 class ListTemplateData(
-    val headerIcon: TemplateImageWithDescription? = null,
+    val headerBlock: HeaderBlock? = null,
     val listContent: List<ListTemplateItem> = listOf(),
-    val header: TemplateText? = null,
-    val title: TemplateText? = null,
-    val button: TemplateButton? = null,
-    val backgroundColor: ColorProvider? = null,
     val listStyle: ListStyle = ListStyle.Full
 ) {
 
     override fun hashCode(): Int {
-        var result = header.hashCode()
-        result = 31 * result + headerIcon.hashCode()
-        result = 31 * result + title.hashCode()
-        result = 31 * result + button.hashCode()
+        var result = headerBlock.hashCode()
         result = 31 * result + listContent.hashCode()
-        result = 31 * result + backgroundColor.hashCode()
         result = 31 * result + listStyle.hashCode()
         return result
     }
@@ -69,12 +50,8 @@
 
         other as ListTemplateData
 
-        if (header != other.header) return false
-        if (headerIcon != other.headerIcon) return false
-        if (title != other.title) return false
-        if (button != other.button) return false
+        if (headerBlock != other.headerBlock) return false
         if (listContent != other.listContent) return false
-        if (backgroundColor != other.backgroundColor) return false
         if (listStyle != other.listStyle) return false
 
         return true
@@ -91,33 +68,20 @@
  * by developers. For example, Glance AppWidget developer might only show item icon when list header
  * is displayed.
  *
- * @param title The list item title text by [TemplateText] with brief information.
- * @param body The list item body text by [TemplateText] with detailed information. Default to no
- * display when null valued.
- * @param label The list item label text by [TemplateText]. Default to no display when null valued.
- * @param action The list item onClick action by [Action]. Default to no action when null valued.
- * @param image The list item icon image by [TemplateImageWithDescription]. Default to no display
- * when null valued.
- * @param button The item action button by [TemplateButton] that can have its own icon and action
- * independent of item icon and item action. Default to no display when null valued.
+ * @param textBlock The text block for title, body, and other texts of the item.
+ * @param imageBlock The image block for a list item defined by [ImageBlock].
+ * @param actionBlock The item onClick action buttons defined by [ActionBlock].
  */
-// TODO: Allow users to define a custom list item
 class ListTemplateItem(
-    val title: TemplateText,
-    val body: TemplateText? = null,
-    val label: TemplateText? = null,
-    val action: Action? = null,
-    val image: TemplateImageWithDescription? = null,
-    val button: TemplateButton? = null,
+    val textBlock: TextBlock,
+    val imageBlock: ImageBlock? = null,
+    val actionBlock: ActionBlock? = null,
 ) {
 
     override fun hashCode(): Int {
-        var result = title.hashCode()
-        result = 31 * result + (body?.hashCode() ?: 0)
-        result = 31 * result + (label?.hashCode() ?: 0)
-        result = 31 * result + (action?.hashCode() ?: 0)
-        result = 31 * result + (image?.hashCode() ?: 0)
-        result = 31 * result + (button?.hashCode() ?: 0)
+        var result = textBlock.hashCode()
+        result = 31 * result + (imageBlock?.hashCode() ?: 0)
+        result = 31 * result + (actionBlock?.hashCode() ?: 0)
         return result
     }
 
@@ -127,12 +91,9 @@
 
         other as ListTemplateItem
 
-        if (title != other.title) return false
-        if (body != other.body) return false
-        if (label != other.label) return false
-        if (action != other.action) return false
-        if (image != other.image) return false
-        if (button != other.button) return false
+        if (textBlock != other.textBlock) return false
+        if (imageBlock != other.imageBlock) return false
+        if (actionBlock != other.actionBlock) return false
 
         return true
     }
@@ -148,16 +109,16 @@
  * the Brief style shows no header.
  */
 @JvmInline
-public value class ListStyle private constructor(private val value: Int) {
-    public companion object {
+value class ListStyle private constructor(private val value: Int) {
+    companion object {
         /**
          * Show list data in full details relative to the platform.
          */
-        public val Full: ListStyle = ListStyle(0)
+        val Full: ListStyle = ListStyle(0)
 
         /**
          * Show list data in minimal details relative to the platform.
          */
-        public val Brief: ListStyle = ListStyle(1)
+        val Brief: ListStyle = ListStyle(1)
     }
 }
\ No newline at end of file
diff --git a/glance/glance/src/androidMain/res/values-night-v31/colors.xml b/glance/glance/src/androidMain/res/values-night-v31/colors.xml
index 36c885b..bbf8abd 100644
--- a/glance/glance/src/androidMain/res/values-night-v31/colors.xml
+++ b/glance/glance/src/androidMain/res/values-night-v31/colors.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,35 +15,30 @@
   -->
 
 <resources>
-    <color name="glance_colorPrimary">@color/m3_sys_color_dynamic_dark_primary</color>
-    <color name="glance_colorOnPrimary">@color/m3_sys_color_dynamic_dark_on_primary</color>
-    <color name="glance_colorPrimaryInverse">@color/m3_sys_color_dynamic_dark_inverse_primary</color>
-    <color name="glance_colorPrimaryContainer">@color/m3_sys_color_dynamic_dark_primary_container</color>
-    <color name="glance_colorOnPrimaryContainer">@color/m3_sys_color_dynamic_dark_on_primary_container</color>
-    <color name="glance_colorSecondary">@color/m3_sys_color_dynamic_dark_secondary</color>
-    <color name="glance_colorOnSecondary">@color/m3_sys_color_dynamic_dark_on_secondary</color>
-    <color name="glance_colorSecondaryContainer">@color/m3_sys_color_dynamic_dark_secondary_container</color>
-    <color name="glance_colorOnSecondaryContainer">@color/m3_sys_color_dynamic_dark_on_secondary_container</color>
-    <color name="glance_colorTertiary">@color/m3_sys_color_dynamic_dark_tertiary</color>
-    <color name="glance_colorOnTertiary">@color/m3_sys_color_dynamic_dark_on_tertiary</color>
-    <color name="glance_colorTertiaryContainer">@color/m3_sys_color_dynamic_dark_tertiary_container</color>
-    <color name="glance_colorOnTertiaryContainer">@color/m3_sys_color_dynamic_dark_on_tertiary_container</color>
-    <color name="glance_colorBackground">@color/m3_sys_color_dynamic_dark_background</color>
-    <color name="glance_colorOnBackground">@color/m3_sys_color_dynamic_dark_on_background</color>
-    <color name="glance_colorSurface">@color/m3_sys_color_dynamic_dark_surface</color>
-    <color name="glance_colorOnSurface">@color/m3_sys_color_dynamic_dark_on_surface</color>
-    <color name="glance_colorSurfaceVariant">@color/m3_sys_color_dynamic_dark_surface_variant</color>
-    <color name="glance_colorOnSurfaceVariant">@color/m3_sys_color_dynamic_dark_on_surface_variant</color>
-    <color name="glance_colorSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_surface</color>
-    <color name="glance_colorOnSurfaceInverse">@color/m3_sys_color_dynamic_dark_inverse_on_surface</color>
-    <color name="glance_colorOutline">@color/m3_sys_color_dynamic_dark_outline</color>
-    <color name="glance_colorError">@color/m3_sys_color_dark_error</color>
-    <color name="glance_colorOnError">@color/m3_sys_color_dark_on_error</color>
-    <color name="glance_colorErrorContainer">@color/m3_sys_color_dark_error_container</color>
-    <color name="glance_colorOnErrorContainer">@color/m3_sys_color_dark_on_error_container</color>
-
-    <!--<color name="textColorPrimary">@color/m3_dynamic_dark_default_color_primary_text</color>
-    <color name="textColorPrimaryInverse">@color/m3_dynamic_default_color_primary_text</color>
-    <color name="textColorSecondary">@color/m3_dynamic_dark_default_color_secondary_text</color>
-    <color name="textColorSecondaryInverse">@color/m3_dynamic_default_color_secondary_text</color>-->
+    <color name="glance_colorPrimary">@android:color/system_accent1_200</color>
+    <color name="glance_colorOnPrimary">@android:color/system_accent1_800</color>
+    <color name="glance_colorPrimaryInverse">@android:color/system_accent1_600</color>
+    <color name="glance_colorPrimaryContainer">@android:color/system_accent1_700</color>
+    <color name="glance_colorOnPrimaryContainer">@android:color/system_accent1_100</color>
+    <color name="glance_colorSecondary">@android:color/system_accent2_200</color>
+    <color name="glance_colorOnSecondary">@android:color/system_accent2_800</color>
+    <color name="glance_colorSecondaryContainer">@android:color/system_accent2_700</color>
+    <color name="glance_colorOnSecondaryContainer">@android:color/system_accent2_100</color>
+    <color name="glance_colorTertiary">@android:color/system_accent3_200</color>
+    <color name="glance_colorOnTertiary">@android:color/system_accent3_800</color>
+    <color name="glance_colorTertiaryContainer">@android:color/system_accent3_700</color>
+    <color name="glance_colorOnTertiaryContainer">@android:color/system_accent3_100</color>
+    <color name="glance_colorBackground">@android:color/system_neutral1_900</color>
+    <color name="glance_colorOnBackground">@android:color/system_neutral1_100</color>
+    <color name="glance_colorSurface">@android:color/system_neutral1_900</color>
+    <color name="glance_colorOnSurface">@android:color/system_neutral1_100</color>
+    <color name="glance_colorSurfaceVariant">@android:color/system_neutral2_700</color>
+    <color name="glance_colorOnSurfaceVariant">@android:color/system_neutral2_200</color>
+    <color name="glance_colorSurfaceInverse">@android:color/system_neutral1_100</color>
+    <color name="glance_colorOnSurfaceInverse">@android:color/system_neutral1_800</color>
+    <color name="glance_colorOutline">@android:color/system_neutral2_400</color>
+    <color name="glance_colorError">#fff2b8b5</color>
+    <color name="glance_colorOnError">#ff601410</color>
+    <color name="glance_colorErrorContainer">#ff8c1d18</color>
+    <color name="glance_colorOnErrorContainer">#fff2b8b5</color>
 </resources>
\ No newline at end of file
diff --git a/glance/glance/src/androidMain/res/values-night/colors.xml b/glance/glance/src/androidMain/res/values-night/colors.xml
index 5f432cb..990cfd3 100644
--- a/glance/glance/src/androidMain/res/values-night/colors.xml
+++ b/glance/glance/src/androidMain/res/values-night/colors.xml
@@ -16,35 +16,30 @@
   -->
 
 <resources>
-    <color name="glance_colorPrimary">@color/m3_sys_color_dark_primary</color>
-    <color name="glance_colorOnPrimary">@color/m3_sys_color_dark_on_primary</color>
-    <color name="glance_colorPrimaryInverse">@color/m3_sys_color_dark_inverse_primary</color>
-    <color name="glance_colorPrimaryContainer">@color/m3_sys_color_dark_primary_container</color>
-    <color name="glance_colorOnPrimaryContainer">@color/m3_sys_color_dark_on_primary_container</color>
-    <color name="glance_colorSecondary">@color/m3_sys_color_dark_secondary</color>
-    <color name="glance_colorOnSecondary">@color/m3_sys_color_dark_on_secondary</color>
-    <color name="glance_colorSecondaryContainer">@color/m3_sys_color_dark_secondary_container</color>
-    <color name="glance_colorOnSecondaryContainer">@color/m3_sys_color_dark_on_secondary_container</color>
-    <color name="glance_colorTertiary">@color/m3_sys_color_dark_tertiary</color>
-    <color name="glance_colorOnTertiary">@color/m3_sys_color_dark_on_tertiary</color>
-    <color name="glance_colorTertiaryContainer">@color/m3_sys_color_dark_tertiary_container</color>
-    <color name="glance_colorOnTertiaryContainer">@color/m3_sys_color_dark_on_tertiary_container</color>
-    <color name="glance_colorBackground">@color/m3_sys_color_dark_background</color>
-    <color name="glance_colorOnBackground">@color/m3_sys_color_dark_on_background</color>
-    <color name="glance_colorSurface">@color/m3_sys_color_dark_surface</color>
-    <color name="glance_colorOnSurface">@color/m3_sys_color_dark_on_surface</color>
-    <color name="glance_colorSurfaceVariant">@color/m3_sys_color_dark_surface_variant</color>
-    <color name="glance_colorOnSurfaceVariant">@color/m3_sys_color_dark_on_surface_variant</color>
-    <color name="glance_colorSurfaceInverse">@color/m3_sys_color_dark_inverse_surface</color>
-    <color name="glance_colorOnSurfaceInverse">@color/m3_sys_color_dark_inverse_on_surface</color>
-    <color name="glance_colorOutline">@color/m3_sys_color_dark_outline</color>
-    <color name="glance_colorError">@color/m3_sys_color_dark_error</color>
-    <color name="glance_colorOnError">@color/m3_sys_color_dark_on_error</color>
-    <color name="glance_colorErrorContainer">@color/m3_sys_color_dark_error_container</color>
-    <color name="glance_colorOnErrorContainer">@color/m3_sys_color_dark_on_error_container</color>
-
-    <!--<color name="textColorPrimary">@color/m3_dark_default_color_primary_text</color>
-    <color name="textColorPrimaryInverse">@color/m3_default_color_primary_text</color>
-    <color name="textColorSecondary">@color/m3_dark_default_color_secondary_text</color>
-    <color name="textColorSecondaryInverse">@color/m3_default_color_secondary_text</color>-->
+    <color name="glance_colorPrimary">#ffd0bcff</color>
+    <color name="glance_colorOnPrimary">#ff381e72</color>
+    <color name="glance_colorPrimaryInverse">#ff6750a4</color>
+    <color name="glance_colorPrimaryContainer">#ff4f378b</color>
+    <color name="glance_colorOnPrimaryContainer">#ffeaddff</color>
+    <color name="glance_colorSecondary">#ffccc2dc</color>
+    <color name="glance_colorOnSecondary">#ff332d41</color>
+    <color name="glance_colorSecondaryContainer">#ff4a4458</color>
+    <color name="glance_colorOnSecondaryContainer">#ffe8def8</color>
+    <color name="glance_colorTertiary">#ffefb8c8</color>
+    <color name="glance_colorOnTertiary">#ff492532</color>
+    <color name="glance_colorTertiaryContainer">#ff633b48</color>
+    <color name="glance_colorOnTertiaryContainer">#ffffd8e4</color>
+    <color name="glance_colorBackground">#ff1c1b1f</color>
+    <color name="glance_colorOnBackground">#ffe6e1e5</color>
+    <color name="glance_colorSurface">#ff1c1b1f</color>
+    <color name="glance_colorOnSurface">#ffe6e1e5</color>
+    <color name="glance_colorSurfaceVariant">#ff49454f</color>
+    <color name="glance_colorOnSurfaceVariant">#ffcac4d0</color>
+    <color name="glance_colorSurfaceInverse">#ffe6e1e5</color>
+    <color name="glance_colorOnSurfaceInverse">#ff313033</color>
+    <color name="glance_colorOutline">#ff938f99</color>
+    <color name="glance_colorError">#fff2b8b5</color>
+    <color name="glance_colorOnError">#ff601410</color>
+    <color name="glance_colorErrorContainer">#ff8c1d18</color>
+    <color name="glance_colorOnErrorContainer">#fff2b8b5</color>
 </resources>
\ No newline at end of file
diff --git a/glance/glance/src/androidMain/res/values-v31/colors.xml b/glance/glance/src/androidMain/res/values-v31/colors.xml
index e6cac06..fc1a525 100644
--- a/glance/glance/src/androidMain/res/values-v31/colors.xml
+++ b/glance/glance/src/androidMain/res/values-v31/colors.xml
@@ -1,5 +1,4 @@
-<?xml version="1.0" encoding="utf-8"?>
-<!--
+<?xml version="1.0" encoding="utf-8"?><!--
   ~ Copyright (C) 2022 The Android Open Source Project
   ~
   ~ Licensed under the Apache License, Version 2.0 (the "License");
@@ -16,36 +15,30 @@
   -->
 
 <resources>
-    <color name="glance_colorPrimary">@color/m3_sys_color_dynamic_light_primary</color>
-    <color name="glance_colorOnPrimary">@color/m3_sys_color_dynamic_light_on_primary</color>
-    <color name="glance_colorPrimaryInverse">@color/m3_sys_color_dynamic_light_inverse_primary</color>
-    <color name="glance_colorPrimaryContainer">@color/m3_sys_color_dynamic_light_primary_container</color>
-    <color name="glance_colorOnPrimaryContainer">@color/m3_sys_color_dynamic_light_on_primary_container</color>
-    <color name="glance_colorSecondary">@color/m3_sys_color_dynamic_light_secondary</color>
-    <color name="glance_colorOnSecondary">@color/m3_sys_color_dynamic_light_on_secondary</color>
-    <color name="glance_colorSecondaryContainer">@color/m3_sys_color_dynamic_light_secondary_container</color>
-    <color name="glance_colorOnSecondaryContainer">@color/m3_sys_color_dynamic_light_on_secondary_container</color>
-    <color name="glance_colorTertiary">@color/m3_sys_color_dynamic_light_tertiary</color>
-    <color name="glance_colorOnTertiary">@color/m3_sys_color_dynamic_light_on_tertiary</color>
-    <color name="glance_colorTertiaryContainer">@color/m3_sys_color_dynamic_light_tertiary_container</color>
-    <color name="glance_colorOnTertiaryContainer">@color/m3_sys_color_dynamic_light_on_tertiary_container</color>
-    <color name="glance_colorBackground">@color/m3_sys_color_dynamic_light_background</color>
-    <color name="glance_colorOnBackground">@color/m3_sys_color_dynamic_light_on_background</color>
-    <color name="glance_colorSurface">@color/m3_sys_color_dynamic_light_surface</color>
-    <color name="glance_colorOnSurface">@color/m3_sys_color_dynamic_light_on_surface</color>
-    <color name="glance_colorSurfaceVariant">@color/m3_sys_color_dynamic_light_surface_variant</color>
-    <color name="glance_colorOnSurfaceVariant">@color/m3_sys_color_dynamic_light_on_surface_variant</color>
-    <color name="glance_colorSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_surface</color>
-    <color name="glance_colorOnSurfaceInverse">@color/m3_sys_color_dynamic_light_inverse_on_surface</color>
-    <color name="glance_colorOutline">@color/m3_sys_color_dynamic_light_outline</color>
-    <color name="glance_colorError">@color/m3_sys_color_light_error</color>
-    <color name="glance_colorOnError">@color/m3_sys_color_light_on_error</color>
-    <color name="glance_colorErrorContainer">@color/m3_sys_color_light_error_container</color>
-    <color name="glance_colorOnErrorContainer">@color/m3_sys_color_light_on_error_container</color>
-
-    <!--<color name="textColorPrimary">@color/m3_dynamic_default_color_primary_text</color>
-    <color name="textColorPrimaryInverse">@color/m3_dynamic_dark_default_color_primary_text</color>
-    <color name="textColorSecondary">@color/m3_dynamic_default_color_secondary_text</color>
-    <color name="textColorSecondaryInverse">@color/m3_dynamic_dark_default_color_secondary_text
-    </color>-->
+    <color name="glance_colorPrimary">@android:color/system_accent1_600</color>
+    <color name="glance_colorOnPrimary">@android:color/system_accent1_0</color>
+    <color name="glance_colorPrimaryInverse">@android:color/system_accent1_200</color>
+    <color name="glance_colorPrimaryContainer">@android:color/system_accent1_100</color>
+    <color name="glance_colorOnPrimaryContainer">@android:color/system_accent1_900</color>
+    <color name="glance_colorSecondary">@android:color/system_accent2_600</color>
+    <color name="glance_colorOnSecondary">@android:color/system_accent2_0</color>
+    <color name="glance_colorSecondaryContainer">@android:color/system_accent2_100</color>
+    <color name="glance_colorOnSecondaryContainer">@android:color/system_accent2_900</color>
+    <color name="glance_colorTertiary">@android:color/system_accent3_600</color>
+    <color name="glance_colorOnTertiary">@android:color/system_accent3_0</color>
+    <color name="glance_colorTertiaryContainer">@android:color/system_accent3_100</color>
+    <color name="glance_colorOnTertiaryContainer">@android:color/system_accent3_900</color>
+    <color name="glance_colorBackground">@android:color/system_neutral1_10</color>
+    <color name="glance_colorOnBackground">@android:color/system_neutral1_900</color>
+    <color name="glance_colorSurface">@android:color/system_neutral1_10</color>
+    <color name="glance_colorOnSurface">@android:color/system_neutral1_900</color>
+    <color name="glance_colorSurfaceVariant">@android:color/system_neutral2_100</color>
+    <color name="glance_colorOnSurfaceVariant">@android:color/system_neutral2_700</color>
+    <color name="glance_colorSurfaceInverse">@android:color/system_neutral1_800</color>
+    <color name="glance_colorOnSurfaceInverse">@android:color/system_neutral1_50</color>
+    <color name="glance_colorOutline">@android:color/system_neutral2_500</color>
+    <color name="glance_colorError">#ffb3261e</color>
+    <color name="glance_colorOnError">#ffffffff</color>
+    <color name="glance_colorErrorContainer">#fff9dedc</color>
+    <color name="glance_colorOnErrorContainer">#ff410e0b</color>
 </resources>
\ No newline at end of file
diff --git a/glance/glance/src/androidMain/res/values/colors.xml b/glance/glance/src/androidMain/res/values/colors.xml
index 8fd372f..bfc0020 100644
--- a/glance/glance/src/androidMain/res/values/colors.xml
+++ b/glance/glance/src/androidMain/res/values/colors.xml
@@ -16,35 +16,30 @@
   -->
 
 <resources>
-    <color name="glance_colorPrimary">@color/m3_sys_color_light_primary</color>
-    <color name="glance_colorOnPrimary">@color/m3_sys_color_light_on_primary</color>
-    <color name="glance_colorPrimaryInverse">@color/m3_sys_color_light_inverse_primary</color>
-    <color name="glance_colorPrimaryContainer">@color/m3_sys_color_light_primary_container</color>
-    <color name="glance_colorOnPrimaryContainer">@color/m3_sys_color_light_on_primary_container</color>
-    <color name="glance_colorSecondary">@color/m3_sys_color_light_secondary</color>
-    <color name="glance_colorOnSecondary">@color/m3_sys_color_light_on_secondary</color>
-    <color name="glance_colorSecondaryContainer">@color/m3_sys_color_light_secondary_container</color>
-    <color name="glance_colorOnSecondaryContainer">@color/m3_sys_color_light_on_secondary_container</color>
-    <color name="glance_colorTertiary">@color/m3_sys_color_light_tertiary</color>
-    <color name="glance_colorOnTertiary">@color/m3_sys_color_light_on_tertiary</color>
-    <color name="glance_colorTertiaryContainer">@color/m3_sys_color_light_tertiary_container</color>
-    <color name="glance_colorOnTertiaryContainer">@color/m3_sys_color_light_on_tertiary_container</color>
-    <color name="glance_colorBackground">@color/m3_sys_color_light_background</color>
-    <color name="glance_colorOnBackground">@color/m3_sys_color_light_on_background</color>
-    <color name="glance_colorSurface">@color/m3_sys_color_light_surface</color>
-    <color name="glance_colorOnSurface">@color/m3_sys_color_light_on_surface</color>
-    <color name="glance_colorSurfaceVariant">@color/m3_sys_color_light_surface_variant</color>
-    <color name="glance_colorOnSurfaceVariant">@color/m3_sys_color_light_on_surface_variant</color>
-    <color name="glance_colorSurfaceInverse">@color/m3_sys_color_light_inverse_surface</color>
-    <color name="glance_colorOnSurfaceInverse">@color/m3_sys_color_light_inverse_on_surface</color>
-    <color name="glance_colorOutline">@color/m3_sys_color_light_outline</color>
-    <color name="glance_colorError">@color/m3_sys_color_light_error</color>
-    <color name="glance_colorOnError">@color/m3_sys_color_light_on_error</color>
-    <color name="glance_colorErrorContainer">@color/m3_sys_color_light_error_container</color>
-    <color name="glance_colorOnErrorContainer">@color/m3_sys_color_light_on_error_container</color>
-
-    <!--<color name="textColorPrimary">@color/m3_default_color_primary_text</color>
-    <color name="textColorPrimaryInverse">@color/m3_dark_default_color_primary_text</color>
-    <color name="textColorSecondary">@color/m3_default_color_secondary_text</color>
-    <color name="textColorSecondaryInverse">@color/m3_dark_default_color_secondary_text</color>-->
+    <color name="glance_colorPrimary">#ff6750a4</color>
+    <color name="glance_colorOnPrimary">#ffffffff</color>
+    <color name="glance_colorPrimaryInverse">#ffd0bcff</color>
+    <color name="glance_colorPrimaryContainer">#ffeaddff</color>
+    <color name="glance_colorOnPrimaryContainer">#ff21005d</color>
+    <color name="glance_colorSecondary">#ff625b71</color>
+    <color name="glance_colorOnSecondary">#ffffffff</color>
+    <color name="glance_colorSecondaryContainer">#ffe8def8</color>
+    <color name="glance_colorOnSecondaryContainer">#ff1d192b</color>
+    <color name="glance_colorTertiary">#ff7d5260</color>
+    <color name="glance_colorOnTertiary">#ffffffff</color>
+    <color name="glance_colorTertiaryContainer">#ffffd8e4</color>
+    <color name="glance_colorOnTertiaryContainer">#ff31111d</color>
+    <color name="glance_colorBackground">#fffffbfe</color>
+    <color name="glance_colorOnBackground">#ff1c1b1f</color>
+    <color name="glance_colorSurface">#fffffbfe</color>
+    <color name="glance_colorOnSurface">#ff1c1b1f</color>
+    <color name="glance_colorSurfaceVariant">#ffe7e0ec</color>
+    <color name="glance_colorOnSurfaceVariant">#ff49454f</color>
+    <color name="glance_colorSurfaceInverse">#ff313033</color>
+    <color name="glance_colorOnSurfaceInverse">#fff4eff4</color>
+    <color name="glance_colorOutline">#ff79747e</color>
+    <color name="glance_colorError">#ffb3261e</color>
+    <color name="glance_colorOnError">#ffffffff</color>
+    <color name="glance_colorErrorContainer">#fff9dedc</color>
+    <color name="glance_colorOnErrorContainer">#ff410e0b</color>
 </resources>
\ No newline at end of file
diff --git a/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt b/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt
new file mode 100644
index 0000000..370949f
--- /dev/null
+++ b/glance/glance/src/test/kotlin/androidx/glance/color/ColorProvidersTest.kt
@@ -0,0 +1,287 @@
+/*
+ * 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.glance.color
+
+import android.content.Context
+import android.os.Build
+import androidx.annotation.ColorRes
+import androidx.core.content.ContextCompat
+import androidx.test.core.app.ApplicationProvider
+import com.google.common.truth.Truth.assertThat
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.annotation.Config
+
+@RunWith(RobolectricTestRunner::class)
+class ColorProvidersTest {
+
+    private val context = ApplicationProvider.getApplicationContext<Context>()
+
+    @Test
+    fun testGlanceMatchMaterial3Colors() {
+        val lightColors = mapOf(
+            androidx.glance.R.color.glance_colorPrimary to
+                com.google.android.material.R.color.m3_sys_color_light_primary,
+            androidx.glance.R.color.glance_colorOnPrimary to
+                com.google.android.material.R.color.m3_sys_color_light_on_primary,
+            androidx.glance.R.color.glance_colorPrimaryInverse to
+                com.google.android.material.R.color.m3_sys_color_light_inverse_primary,
+            androidx.glance.R.color.glance_colorPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_primary_container,
+            androidx.glance.R.color.glance_colorOnPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_on_primary_container,
+            androidx.glance.R.color.glance_colorSecondary to
+                com.google.android.material.R.color.m3_sys_color_light_secondary,
+            androidx.glance.R.color.glance_colorOnSecondary to
+                com.google.android.material.R.color.m3_sys_color_light_on_secondary,
+            androidx.glance.R.color.glance_colorSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_secondary_container,
+            androidx.glance.R.color.glance_colorOnSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_on_secondary_container,
+            androidx.glance.R.color.glance_colorTertiary to
+                com.google.android.material.R.color.m3_sys_color_light_tertiary,
+            androidx.glance.R.color.glance_colorOnTertiary to
+                com.google.android.material.R.color.m3_sys_color_light_on_tertiary,
+            androidx.glance.R.color.glance_colorTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_tertiary_container,
+            androidx.glance.R.color.glance_colorOnTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_light_on_tertiary_container,
+            androidx.glance.R.color.glance_colorBackground to
+                com.google.android.material.R.color.m3_sys_color_light_background,
+            androidx.glance.R.color.glance_colorOnBackground to
+                com.google.android.material.R.color.m3_sys_color_light_on_background,
+            androidx.glance.R.color.glance_colorSurface to
+                com.google.android.material.R.color.m3_sys_color_light_surface,
+            androidx.glance.R.color.glance_colorOnSurface to
+                com.google.android.material.R.color.m3_sys_color_light_on_surface,
+            androidx.glance.R.color.glance_colorSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_light_surface_variant,
+            androidx.glance.R.color.glance_colorOnSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_light_on_surface_variant,
+            androidx.glance.R.color.glance_colorSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_light_inverse_surface,
+            androidx.glance.R.color.glance_colorOnSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_light_inverse_on_surface,
+            androidx.glance.R.color.glance_colorOutline to
+                com.google.android.material.R.color.m3_sys_color_light_outline,
+            androidx.glance.R.color.glance_colorError to
+                com.google.android.material.R.color.m3_sys_color_light_error,
+            androidx.glance.R.color.glance_colorOnError to
+                com.google.android.material.R.color.m3_sys_color_light_on_error,
+            androidx.glance.R.color.glance_colorErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_light_error_container,
+            androidx.glance.R.color.glance_colorOnErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_light_on_error_container,
+        )
+        lightColors.forEach {
+            assertColor(it.key, it.value)
+        }
+    }
+
+    @Test
+    @Config(qualifiers = "night")
+    fun testGlanceMatchMaterial3NightColors() {
+        val darkColors = mapOf(
+            androidx.glance.R.color.glance_colorPrimary to
+                com.google.android.material.R.color.m3_sys_color_dark_primary,
+            androidx.glance.R.color.glance_colorOnPrimary to
+                com.google.android.material.R.color.m3_sys_color_dark_on_primary,
+            androidx.glance.R.color.glance_colorPrimaryInverse to
+                com.google.android.material.R.color.m3_sys_color_dark_inverse_primary,
+            androidx.glance.R.color.glance_colorPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_primary_container,
+            androidx.glance.R.color.glance_colorOnPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_on_primary_container,
+            androidx.glance.R.color.glance_colorSecondary to
+                com.google.android.material.R.color.m3_sys_color_dark_secondary,
+            androidx.glance.R.color.glance_colorOnSecondary to
+                com.google.android.material.R.color.m3_sys_color_dark_on_secondary,
+            androidx.glance.R.color.glance_colorSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_secondary_container,
+            androidx.glance.R.color.glance_colorOnSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_on_secondary_container,
+            androidx.glance.R.color.glance_colorTertiary to
+                com.google.android.material.R.color.m3_sys_color_dark_tertiary,
+            androidx.glance.R.color.glance_colorOnTertiary to
+                com.google.android.material.R.color.m3_sys_color_dark_on_tertiary,
+            androidx.glance.R.color.glance_colorTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_tertiary_container,
+            androidx.glance.R.color.glance_colorOnTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_on_tertiary_container,
+            androidx.glance.R.color.glance_colorBackground to
+                com.google.android.material.R.color.m3_sys_color_dark_background,
+            androidx.glance.R.color.glance_colorOnBackground to
+                com.google.android.material.R.color.m3_sys_color_dark_on_background,
+            androidx.glance.R.color.glance_colorSurface to
+                com.google.android.material.R.color.m3_sys_color_dark_surface,
+            androidx.glance.R.color.glance_colorOnSurface to
+                com.google.android.material.R.color.m3_sys_color_dark_on_surface,
+            androidx.glance.R.color.glance_colorSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dark_surface_variant,
+            androidx.glance.R.color.glance_colorOnSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dark_on_surface_variant,
+            androidx.glance.R.color.glance_colorSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dark_inverse_surface,
+            androidx.glance.R.color.glance_colorOnSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dark_inverse_on_surface,
+            androidx.glance.R.color.glance_colorOutline to
+                com.google.android.material.R.color.m3_sys_color_dark_outline,
+            androidx.glance.R.color.glance_colorError to
+                com.google.android.material.R.color.m3_sys_color_dark_error,
+            androidx.glance.R.color.glance_colorOnError to
+                com.google.android.material.R.color.m3_sys_color_dark_on_error,
+            androidx.glance.R.color.glance_colorErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_error_container,
+            androidx.glance.R.color.glance_colorOnErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_on_error_container,
+        )
+        darkColors.forEach {
+            assertColor(it.key, it.value)
+        }
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.S])
+    fun testGlanceMatchMaterial3v31Colors() {
+        val v31Colors = mapOf(
+            androidx.glance.R.color.glance_colorPrimary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_primary,
+            androidx.glance.R.color.glance_colorOnPrimary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_primary,
+            androidx.glance.R.color.glance_colorPrimaryInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_inverse_primary,
+            androidx.glance.R.color.glance_colorPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_primary_container,
+            androidx.glance.R.color.glance_colorOnPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_primary_container,
+            androidx.glance.R.color.glance_colorSecondary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_secondary,
+            androidx.glance.R.color.glance_colorOnSecondary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_secondary,
+            androidx.glance.R.color.glance_colorSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_secondary_container,
+            androidx.glance.R.color.glance_colorOnSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_secondary_container, // ktlint-disable max-line-length
+            androidx.glance.R.color.glance_colorTertiary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_tertiary,
+            androidx.glance.R.color.glance_colorOnTertiary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_tertiary,
+            androidx.glance.R.color.glance_colorTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_tertiary_container,
+            androidx.glance.R.color.glance_colorOnTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_tertiary_container, // ktlint-disable max-line-length
+            androidx.glance.R.color.glance_colorBackground to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_background,
+            androidx.glance.R.color.glance_colorOnBackground to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_background,
+            androidx.glance.R.color.glance_colorSurface to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_surface,
+            androidx.glance.R.color.glance_colorOnSurface to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_surface,
+            androidx.glance.R.color.glance_colorSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_surface_variant,
+            androidx.glance.R.color.glance_colorOnSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_on_surface_variant,
+            androidx.glance.R.color.glance_colorSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_inverse_surface,
+            androidx.glance.R.color.glance_colorOnSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_inverse_on_surface,
+            androidx.glance.R.color.glance_colorOutline to
+                com.google.android.material.R.color.m3_sys_color_dynamic_light_outline,
+            androidx.glance.R.color.glance_colorError to
+                com.google.android.material.R.color.m3_sys_color_light_error,
+            androidx.glance.R.color.glance_colorOnError to
+                com.google.android.material.R.color.m3_sys_color_light_on_error,
+            androidx.glance.R.color.glance_colorErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_light_error_container,
+            androidx.glance.R.color.glance_colorOnErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_light_on_error_container,
+        )
+        v31Colors.forEach {
+            assertColor(it.key, it.value)
+        }
+    }
+
+    @Test
+    @Config(sdk = [Build.VERSION_CODES.S], qualifiers = "night")
+    fun testGlanceMatchMaterial3v31NightColors() {
+        val v31NightColors = mapOf(
+            androidx.glance.R.color.glance_colorPrimary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_primary,
+            androidx.glance.R.color.glance_colorOnPrimary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_primary,
+            androidx.glance.R.color.glance_colorPrimaryInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_inverse_primary,
+            androidx.glance.R.color.glance_colorPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_primary_container,
+            androidx.glance.R.color.glance_colorOnPrimaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_primary_container,
+            androidx.glance.R.color.glance_colorSecondary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_secondary,
+            androidx.glance.R.color.glance_colorOnSecondary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_secondary,
+            androidx.glance.R.color.glance_colorSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_secondary_container,
+            androidx.glance.R.color.glance_colorOnSecondaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_secondary_container, // ktlint-disable max-line-length
+            androidx.glance.R.color.glance_colorTertiary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_tertiary,
+            androidx.glance.R.color.glance_colorOnTertiary to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_tertiary,
+            androidx.glance.R.color.glance_colorTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_tertiary_container,
+            androidx.glance.R.color.glance_colorOnTertiaryContainer to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_tertiary_container,
+            androidx.glance.R.color.glance_colorBackground to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_background,
+            androidx.glance.R.color.glance_colorOnBackground to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_background,
+            androidx.glance.R.color.glance_colorSurface to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_surface,
+            androidx.glance.R.color.glance_colorOnSurface to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_surface,
+            androidx.glance.R.color.glance_colorSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_surface_variant,
+            androidx.glance.R.color.glance_colorOnSurfaceVariant to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_on_surface_variant,
+            androidx.glance.R.color.glance_colorSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_inverse_surface,
+            androidx.glance.R.color.glance_colorOnSurfaceInverse to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_inverse_on_surface,
+            androidx.glance.R.color.glance_colorOutline to
+                com.google.android.material.R.color.m3_sys_color_dynamic_dark_outline,
+            androidx.glance.R.color.glance_colorError to
+                com.google.android.material.R.color.m3_sys_color_dark_error,
+            androidx.glance.R.color.glance_colorOnError to
+                com.google.android.material.R.color.m3_sys_color_dark_on_error,
+            androidx.glance.R.color.glance_colorErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_error_container,
+            androidx.glance.R.color.glance_colorOnErrorContainer to
+                com.google.android.material.R.color.m3_sys_color_dark_on_error_container,
+        )
+        v31NightColors.forEach {
+            assertColor(it.key, it.value)
+        }
+    }
+
+    private fun assertColor(@ColorRes source: Int, @ColorRes target: Int) {
+        assertThat(ContextCompat.getColor(context, source)).isEqualTo(
+            ContextCompat.getColor(context, target)
+        )
+    }
+}
\ No newline at end of file
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index cb23220..40b185d 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -44,9 +44,10 @@
 mockito = "2.25.0"
 protobuf = "3.19.4"
 paparazzi = "1.0.0"
-paparazziNative = "2021.1.1-573f070"
+paparazziNative = "2022.1.1-canary-f5f9f71"
 skiko = "0.7.7"
 sqldelight = "1.3.0"
+retrofit = "2.7.2"
 wire = "4.4.1"
 
 [libraries]
@@ -80,7 +81,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.0.1" }
+dackka = { module = "com.google.devsite:dackka", version = "1.0.2" }
 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" }
@@ -179,7 +180,8 @@
 protobufGradlePluginz = { module = "com.google.protobuf:protobuf-gradle-plugin", version = "0.8.18" }
 protobufLite = { module = "com.google.protobuf:protobuf-javalite", version.ref = "protobuf" }
 reactiveStreams = { module = "org.reactivestreams:reactive-streams", version = "1.0.0" }
-retrofit = { module = "com.squareup.retrofit2:retrofit", version = "2.7.2" }
+retrofit = { module = "com.squareup.retrofit2:retrofit", version.ref = "retrofit" }
+retrofitConverterWire = { module = "com.squareup.retrofit2:converter-wire", version.ref = "retrofit" }
 robolectric = { module = "org.robolectric:robolectric", version = "4.8.1" }
 rxjava2 = { module = "io.reactivex.rxjava2:rxjava", version = "2.2.9" }
 rxjava3 = { module = "io.reactivex.rxjava3:rxjava", version = "3.0.0" }
diff --git a/health/connect/connect-client/api/current.txt b/health/connect/connect-client/api/current.txt
index 3b35ea5..b37c38c6 100644
--- a/health/connect/connect-client/api/current.txt
+++ b/health/connect/connect-client/api/current.txt
@@ -5,7 +5,7 @@
     method public suspend Object? aggregate(androidx.health.connect.client.request.AggregateRequest request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.aggregate.AggregationResult>);
     method public suspend Object? aggregateGroupByDuration(androidx.health.connect.client.request.AggregateGroupByDurationRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration>>);
     method public suspend Object? aggregateGroupByPeriod(androidx.health.connect.client.request.AggregateGroupByPeriodRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod>>);
-    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     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>);
@@ -15,7 +15,7 @@
     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 isAvailable(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
     method public default static boolean isAvailable(android.content.Context context);
-    method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String uid, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
+    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>>);
     method public suspend Object? updateRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public abstract androidx.health.connect.client.PermissionController permissionController;
@@ -30,9 +30,16 @@
   }
 
   public interface PermissionController {
-    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionActivityContract();
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
     method public suspend Object? getGrantedPermissions(java.util.Set<androidx.health.connect.client.permission.HealthPermission> permissions, kotlin.coroutines.Continuation<? super java.util.Set<? extends androidx.health.connect.client.permission.HealthPermission>>);
     method public suspend Object? revokeAllPermissions(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    field public static final androidx.health.connect.client.PermissionController.Companion Companion;
+  }
+
+  public static final class PermissionController.Companion {
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
   }
 
 }
@@ -79,8 +86,8 @@
   }
 
   public final class DeletionChange implements androidx.health.connect.client.changes.Change {
-    method public String getUid();
-    property public final String uid;
+    method public String getRecordId();
+    property public final String recordId;
   }
 
   public final class UpsertionChange implements androidx.health.connect.client.changes.Change {
@@ -1303,19 +1310,19 @@
   }
 
   public final class Metadata {
-    ctor public Metadata(optional String uid, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
+    ctor public Metadata(optional String id, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
     method public String? getClientRecordId();
     method public long getClientRecordVersion();
     method public androidx.health.connect.client.records.metadata.DataOrigin getDataOrigin();
     method public androidx.health.connect.client.records.metadata.Device? getDevice();
+    method public String getId();
     method public java.time.Instant getLastModifiedTime();
-    method public String getUid();
     property public final String? clientRecordId;
     property public final long clientRecordVersion;
     property public final androidx.health.connect.client.records.metadata.DataOrigin dataOrigin;
     property public final androidx.health.connect.client.records.metadata.Device? device;
+    property public final String id;
     property public final java.time.Instant lastModifiedTime;
-    property public final String uid;
   }
 
 }
@@ -1358,8 +1365,8 @@
   }
 
   public final class InsertRecordsResponse {
-    method public java.util.List<java.lang.String> getRecordUidsList();
-    property public final java.util.List<java.lang.String> recordUidsList;
+    method public java.util.List<java.lang.String> getRecordIdsList();
+    property public final java.util.List<java.lang.String> recordIdsList;
   }
 
   public final class ReadRecordResponse<T extends androidx.health.connect.client.records.Record> {
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 3b35ea5..b37c38c6 100644
--- a/health/connect/connect-client/api/public_plus_experimental_current.txt
+++ b/health/connect/connect-client/api/public_plus_experimental_current.txt
@@ -5,7 +5,7 @@
     method public suspend Object? aggregate(androidx.health.connect.client.request.AggregateRequest request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.aggregate.AggregationResult>);
     method public suspend Object? aggregateGroupByDuration(androidx.health.connect.client.request.AggregateGroupByDurationRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration>>);
     method public suspend Object? aggregateGroupByPeriod(androidx.health.connect.client.request.AggregateGroupByPeriodRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod>>);
-    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     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>);
@@ -15,7 +15,7 @@
     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 isAvailable(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
     method public default static boolean isAvailable(android.content.Context context);
-    method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String uid, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
+    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>>);
     method public suspend Object? updateRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public abstract androidx.health.connect.client.PermissionController permissionController;
@@ -30,9 +30,16 @@
   }
 
   public interface PermissionController {
-    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionActivityContract();
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
     method public suspend Object? getGrantedPermissions(java.util.Set<androidx.health.connect.client.permission.HealthPermission> permissions, kotlin.coroutines.Continuation<? super java.util.Set<? extends androidx.health.connect.client.permission.HealthPermission>>);
     method public suspend Object? revokeAllPermissions(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    field public static final androidx.health.connect.client.PermissionController.Companion Companion;
+  }
+
+  public static final class PermissionController.Companion {
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
   }
 
 }
@@ -79,8 +86,8 @@
   }
 
   public final class DeletionChange implements androidx.health.connect.client.changes.Change {
-    method public String getUid();
-    property public final String uid;
+    method public String getRecordId();
+    property public final String recordId;
   }
 
   public final class UpsertionChange implements androidx.health.connect.client.changes.Change {
@@ -1303,19 +1310,19 @@
   }
 
   public final class Metadata {
-    ctor public Metadata(optional String uid, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
+    ctor public Metadata(optional String id, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
     method public String? getClientRecordId();
     method public long getClientRecordVersion();
     method public androidx.health.connect.client.records.metadata.DataOrigin getDataOrigin();
     method public androidx.health.connect.client.records.metadata.Device? getDevice();
+    method public String getId();
     method public java.time.Instant getLastModifiedTime();
-    method public String getUid();
     property public final String? clientRecordId;
     property public final long clientRecordVersion;
     property public final androidx.health.connect.client.records.metadata.DataOrigin dataOrigin;
     property public final androidx.health.connect.client.records.metadata.Device? device;
+    property public final String id;
     property public final java.time.Instant lastModifiedTime;
-    property public final String uid;
   }
 
 }
@@ -1358,8 +1365,8 @@
   }
 
   public final class InsertRecordsResponse {
-    method public java.util.List<java.lang.String> getRecordUidsList();
-    property public final java.util.List<java.lang.String> recordUidsList;
+    method public java.util.List<java.lang.String> getRecordIdsList();
+    property public final java.util.List<java.lang.String> recordIdsList;
   }
 
   public final class ReadRecordResponse<T extends androidx.health.connect.client.records.Record> {
diff --git a/health/connect/connect-client/api/restricted_current.txt b/health/connect/connect-client/api/restricted_current.txt
index 76c35ff..ce99c28 100644
--- a/health/connect/connect-client/api/restricted_current.txt
+++ b/health/connect/connect-client/api/restricted_current.txt
@@ -5,7 +5,7 @@
     method public suspend Object? aggregate(androidx.health.connect.client.request.AggregateRequest request, kotlin.coroutines.Continuation<? super androidx.health.connect.client.aggregate.AggregationResult>);
     method public suspend Object? aggregateGroupByDuration(androidx.health.connect.client.request.AggregateGroupByDurationRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByDuration>>);
     method public suspend Object? aggregateGroupByPeriod(androidx.health.connect.client.request.AggregateGroupByPeriodRequest request, kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.health.connect.client.aggregate.AggregationResultGroupedByPeriod>>);
-    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> uidsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    method public suspend Object? deleteRecords(kotlin.reflect.KClass<? extends androidx.health.connect.client.records.Record> recordType, java.util.List<java.lang.String> recordIdsList, java.util.List<java.lang.String> clientRecordIdsList, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     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>);
@@ -15,7 +15,7 @@
     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 isAvailable(android.content.Context context, optional java.util.List<java.lang.String> packageNames);
     method public default static boolean isAvailable(android.content.Context context);
-    method public suspend <T extends androidx.health.connect.client.records.Record> Object? readRecord(kotlin.reflect.KClass<T> recordType, String uid, kotlin.coroutines.Continuation<? super androidx.health.connect.client.response.ReadRecordResponse<T>>);
+    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>>);
     method public suspend Object? updateRecords(java.util.List<? extends androidx.health.connect.client.records.Record> records, kotlin.coroutines.Continuation<? super kotlin.Unit>);
     property public abstract androidx.health.connect.client.PermissionController permissionController;
@@ -30,9 +30,16 @@
   }
 
   public interface PermissionController {
-    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionActivityContract();
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public default static androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
     method public suspend Object? getGrantedPermissions(java.util.Set<androidx.health.connect.client.permission.HealthPermission> permissions, kotlin.coroutines.Continuation<? super java.util.Set<? extends androidx.health.connect.client.permission.HealthPermission>>);
     method public suspend Object? revokeAllPermissions(kotlin.coroutines.Continuation<? super kotlin.Unit>);
+    field public static final androidx.health.connect.client.PermissionController.Companion Companion;
+  }
+
+  public static final class PermissionController.Companion {
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract(optional String providerPackageName);
+    method public androidx.activity.result.contract.ActivityResultContract<java.util.Set<androidx.health.connect.client.permission.HealthPermission>,java.util.Set<androidx.health.connect.client.permission.HealthPermission>> createRequestPermissionResultContract();
   }
 
 }
@@ -79,8 +86,8 @@
   }
 
   public final class DeletionChange implements androidx.health.connect.client.changes.Change {
-    method public String getUid();
-    property public final String uid;
+    method public String getRecordId();
+    property public final String recordId;
   }
 
   public final class UpsertionChange implements androidx.health.connect.client.changes.Change {
@@ -1326,19 +1333,19 @@
   }
 
   public final class Metadata {
-    ctor public Metadata(optional String uid, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
+    ctor public Metadata(optional String id, optional androidx.health.connect.client.records.metadata.DataOrigin dataOrigin, optional java.time.Instant lastModifiedTime, optional String? clientRecordId, optional long clientRecordVersion, optional androidx.health.connect.client.records.metadata.Device? device);
     method public String? getClientRecordId();
     method public long getClientRecordVersion();
     method public androidx.health.connect.client.records.metadata.DataOrigin getDataOrigin();
     method public androidx.health.connect.client.records.metadata.Device? getDevice();
+    method public String getId();
     method public java.time.Instant getLastModifiedTime();
-    method public String getUid();
     property public final String? clientRecordId;
     property public final long clientRecordVersion;
     property public final androidx.health.connect.client.records.metadata.DataOrigin dataOrigin;
     property public final androidx.health.connect.client.records.metadata.Device? device;
+    property public final String id;
     property public final java.time.Instant lastModifiedTime;
-    property public final String uid;
   }
 
 }
@@ -1381,8 +1388,8 @@
   }
 
   public final class InsertRecordsResponse {
-    method public java.util.List<java.lang.String> getRecordUidsList();
-    property public final java.util.List<java.lang.String> recordUidsList;
+    method public java.util.List<java.lang.String> getRecordIdsList();
+    property public final java.util.List<java.lang.String> recordIdsList;
   }
 
   public final class ReadRecordResponse<T extends androidx.health.connect.client.records.Record> {
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/DeleteRecordsSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/DeleteRecordsSamples.kt
index 892e6f2..95712e3 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/DeleteRecordsSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/DeleteRecordsSamples.kt
@@ -34,7 +34,7 @@
 ) {
     healthConnectClient.deleteRecords(
         StepsRecord::class,
-        uidsList = listOf(uid1, uid2),
+        recordIdsList = listOf(uid1, uid2),
         clientRecordIdsList = emptyList()
     )
 }
diff --git a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
index e779dcc..e80bc61 100644
--- a/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
+++ b/health/connect/connect-client/samples/src/main/java/androidx/health/connect/client/samples/PermissionSamples.kt
@@ -25,10 +25,10 @@
 import androidx.health.connect.client.records.StepsRecord
 
 @Sampled
-fun RequestPermission(activity: ActivityResultCaller, permissionController: PermissionController) {
+fun RequestPermission(activity: ActivityResultCaller) {
     val requestPermission =
         activity.registerForActivityResult(
-            permissionController.createRequestPermissionActivityContract()
+            PermissionController.createRequestPermissionResultContract()
         ) { grantedPermissions ->
             if (
                 grantedPermissions.contains(
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 006fbc1..5f8c4d4 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
@@ -51,7 +51,7 @@
 
     /**
      * Inserts one or more [Record] and returns newly assigned
-     * [androidx.health.connect.client.records.metadata.Metadata.uid] generated. Insertion of
+     * [androidx.health.connect.client.records.metadata.Metadata.id] generated. Insertion of
      * multiple [records] is executed in a transaction - if one fails, none is inserted.
      *
      * For example, to insert basic data like step counts:
@@ -65,9 +65,11 @@
      *
      * [androidx.health.connect.client.records.metadata.Metadata.clientRecordId] can be used to
      * deduplicate data with a client provided unique identifier. When a subsequent [insertRecords]
-     * is called with the same [androidx.health.connect.client.records.metadata.Metadata.clientRecordId],
-     * whichever [Record] with the higher [androidx.health.connect.client.records.metadata.Metadata.clientRecordVersion]
-     * takes precedence.
+     * is called with the same
+     * [androidx.health.connect.client.records.metadata.Metadata.clientRecordId], whichever [Record]
+     * with the higher
+     * [androidx.health.connect.client.records.metadata.Metadata.clientRecordVersion] takes
+     * precedence.
      *
      * @param records List of records to insert
      * @return List of unique identifiers in the order of inserted records.
@@ -99,7 +101,8 @@
      * @sample androidx.health.connect.client.samples.DeleteByUniqueIdentifier
      *
      * @param recordType Which type of [Record] to delete, such as `Steps::class`
-     * @param uidsList List of uids of [Record] to delete
+     * @param recordIdsList List of
+     * [androidx.health.connect.client.records.metadata.Metadata.id] of [Record] to delete
      * @param clientRecordIdsList List of client record IDs of [Record] to delete
      * @throws RemoteException For any IPC transportation failures. Deleting by invalid identifiers
      * such as a non-existing identifier or deleting the same record multiple times will result in
@@ -110,7 +113,7 @@
      */
     suspend fun deleteRecords(
         recordType: KClass<out Record>,
-        uidsList: List<String>,
+        recordIdsList: List<String>,
         clientRecordIdsList: List<String>,
     )
 
@@ -132,10 +135,11 @@
     suspend fun deleteRecords(recordType: KClass<out Record>, timeRangeFilter: TimeRangeFilter)
 
     /**
-     * Reads one [Record] point with its [recordType] and [uid].
+     * Reads one [Record] point with its [recordType] and [recordId].
      *
      * @param recordType Which type of [Record] to read, such as `Steps::class`
-     * @param uid Uid of [Record] to read
+     * @param recordId [androidx.health.connect.client.records.metadata.Metadata.id] of
+     * [Record] to read
      * @return The [Record] data point.
      * @throws RemoteException For any IPC transportation failures. Update with invalid identifiers
      * will result in IPC failure.
@@ -143,7 +147,10 @@
      * @throws IOException For any disk I/O issues.
      * @throws IllegalStateException If service is not available.
      */
-    suspend fun <T : Record> readRecord(recordType: KClass<T>, uid: String): ReadRecordResponse<T>
+    suspend fun <T : Record> readRecord(
+        recordType: KClass<T>,
+        recordId: String
+    ): ReadRecordResponse<T>
 
     /**
      * Retrieves a collection of [Record]s.
@@ -265,9 +272,9 @@
      * Registers the provided [notificationIntentAction] and [recordTypes] for data notifications.
      *
      * Health Connect will automatically broadcast notification messages to the client application
-     * with the action specified by [notificationIntentAction] argument. Messages are
-     * sent when the data specified by [recordTypes] is updated. Messages may not be sent
-     * immediately, but in batches.
+     * with the action specified by [notificationIntentAction] argument. Messages are sent when the
+     * data specified by [recordTypes] is updated. Messages may not be sent immediately, but in
+     * batches.
      *
      * The client application must have a read permission granted for a [Record] in order to receive
      * notifications for it.
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
index 6a97d6f..a9def5a 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/PermissionController.kt
@@ -16,21 +16,14 @@
 package androidx.health.connect.client
 
 import androidx.activity.result.contract.ActivityResultContract
+import androidx.health.connect.client.HealthConnectClient.Companion.DEFAULT_PROVIDER_PACKAGE_NAME
+import androidx.health.connect.client.permission.HealthDataRequestPermissions
 import androidx.health.connect.client.permission.HealthPermission
 
 /** Interface for operations related to permissions. */
 interface PermissionController {
 
     /**
-     * Creates an [ActivityResultContract] to request Health permissions.
-     *
-     * @see androidx.activity.ComponentActivity.registerForActivityResult
-     * @sample androidx.health.connect.client.samples.RequestPermission
-     */
-    fun createRequestPermissionActivityContract():
-        ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>>
-
-    /**
      * Returns a set of [HealthPermission] granted by the user to the calling app, out of the input
      * [permissions] set.
      *
@@ -51,4 +44,23 @@
      * @throws IllegalStateException If service is not available.
      */
     suspend fun revokeAllPermissions()
+
+    companion object {
+        /**
+         * Creates an [ActivityResultContract] to request Health permissions.
+         *
+         * @param providerPackageName Optional provider package name to request health permissions
+         * from.
+         *
+         * @see androidx.activity.ComponentActivity.registerForActivityResult
+         * @sample androidx.health.connect.client.samples.RequestPermission
+         */
+        @JvmStatic
+        @JvmOverloads
+        fun createRequestPermissionResultContract(
+            providerPackageName: String = DEFAULT_PROVIDER_PACKAGE_NAME
+        ): ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>> {
+            return HealthDataRequestPermissions(providerPackageName = providerPackageName)
+        }
+    }
 }
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/changes/DeletionChange.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/changes/DeletionChange.kt
index 63de56e..38c927d 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/changes/DeletionChange.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/changes/DeletionChange.kt
@@ -19,10 +19,10 @@
 import androidx.health.connect.client.records.metadata.Metadata
 
 /**
- * A [Change] with [Metadata.uid] of deleted [Record]. For privacy, only unique identifiers of the
- * deletion are returned. Clients holding copies of data should keep a copy of [Metadata.uid] along
- * with its contents, if deletion propagation is desired.
+ * A [Change] with [Metadata.id] of deleted [Record]. For privacy, only unique identifiers of
+ * the deletion are returned. Clients holding copies of data should keep a copy of
+ * [Metadata.id] along with its contents, if deletion propagation is desired.
  *
- * @property uid [Metadata.uid] of deleted [Record].
+ * @property recordId [Metadata.id] of deleted [Record].
  */
-class DeletionChange internal constructor(public val uid: String) : Change
+class DeletionChange internal constructor(public val recordId: String) : Change
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 ca30d8f..5ed5ce1 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
@@ -15,7 +15,6 @@
  */
 package androidx.health.connect.client.impl
 
-import androidx.activity.result.contract.ActivityResultContract
 import androidx.health.connect.client.HealthConnectClient
 import androidx.health.connect.client.HealthConnectClient.Companion.HEALTH_CONNECT_CLIENT_TAG
 import androidx.health.connect.client.PermissionController
@@ -38,7 +37,6 @@
 import androidx.health.connect.client.impl.converters.response.toChangesResponse
 import androidx.health.connect.client.impl.converters.response.toReadRecordsResponse
 import androidx.health.connect.client.permission.HealthPermission
-import androidx.health.connect.client.permission.createHealthDataRequestPermissions
 import androidx.health.connect.client.records.Record
 import androidx.health.connect.client.request.AggregateGroupByDurationRequest
 import androidx.health.connect.client.request.AggregateGroupByPeriodRequest
@@ -69,18 +67,15 @@
     private val delegate: HealthDataAsyncClient,
 ) : HealthConnectClient, PermissionController {
 
-    override fun createRequestPermissionActivityContract():
-        ActivityResultContract<Set<HealthPermission>, Set<HealthPermission>> =
-        createHealthDataRequestPermissions(providerPackageName = providerPackageName)
-
     override suspend fun getGrantedPermissions(
         permissions: Set<HealthPermission>
     ): Set<HealthPermission> {
-        val grantedPermissions = delegate
-            .getGrantedPermissions(permissions.map { it.toProtoPermission() }.toSet())
-            .await()
-            .map { it.toJetpackPermission() }
-            .toSet()
+        val grantedPermissions =
+            delegate
+                .getGrantedPermissions(permissions.map { it.toProtoPermission() }.toSet())
+                .await()
+                .map { it.toJetpackPermission() }
+                .toSet()
         Logger.debug(
             HEALTH_CONNECT_CLIENT_TAG,
             "Granted ${grantedPermissions.size} out of ${permissions.size} permissions."
@@ -99,7 +94,7 @@
     override suspend fun insertRecords(records: List<Record>): InsertRecordsResponse {
         val uidList = delegate.insertData(records.map { it.toProto() }).await()
         Logger.debug(HEALTH_CONNECT_CLIENT_TAG, "${records.size} records inserted.")
-        return InsertRecordsResponse(recordUidsList = uidList)
+        return InsertRecordsResponse(recordIdsList = uidList)
     }
 
     override suspend fun updateRecords(records: List<Record>) {
@@ -109,18 +104,18 @@
 
     override suspend fun deleteRecords(
         recordType: KClass<out Record>,
-        uidsList: List<String>,
+        recordIdsList: List<String>,
         clientRecordIdsList: List<String>,
     ) {
         delegate
             .deleteData(
-                toDataTypeIdPairProtoList(recordType, uidsList),
+                toDataTypeIdPairProtoList(recordType, recordIdsList),
                 toDataTypeIdPairProtoList(recordType, clientRecordIdsList)
             )
             .await()
         Logger.debug(
             HEALTH_CONNECT_CLIENT_TAG,
-            "${uidsList.size + clientRecordIdsList.size} records deleted."
+            "${recordIdsList.size + clientRecordIdsList.size} records deleted."
         )
     }
 
@@ -135,11 +130,11 @@
     @Suppress("UNCHECKED_CAST") // Safe to cast as the type should match
     override suspend fun <T : Record> readRecord(
         recordType: KClass<T>,
-        uid: String,
+        recordId: String,
     ): ReadRecordResponse<T> {
-        val proto = delegate.readData(toReadDataRequestProto(recordType, uid)).await()
+        val proto = delegate.readData(toReadDataRequestProto(recordType, recordId)).await()
         val response = ReadRecordResponse(toRecord(proto) as T)
-        Logger.debug(HEALTH_CONNECT_CLIENT_TAG, "Reading record of $uid successful.")
+        Logger.debug(HEALTH_CONNECT_CLIENT_TAG, "Reading record of $recordId successful.")
         return response
     }
 
@@ -226,12 +221,15 @@
         notificationIntentAction: String,
         recordTypes: Iterable<KClass<out Record>>,
     ) {
-        delegate.registerForDataNotifications(
-            request = RequestProto.RegisterForDataNotificationsRequest.newBuilder()
-                .setNotificationIntentAction(notificationIntentAction)
-                .addAllDataTypes(recordTypes.map { it.toDataType() })
-                .build(),
-        ).await()
+        delegate
+            .registerForDataNotifications(
+                request =
+                    RequestProto.RegisterForDataNotificationsRequest.newBuilder()
+                        .setNotificationIntentAction(notificationIntentAction)
+                        .addAllDataTypes(recordTypes.map { it.toDataType() })
+                        .build(),
+            )
+            .await()
 
         Logger.debug(
             HEALTH_CONNECT_CLIENT_TAG,
@@ -240,11 +238,14 @@
     }
 
     override suspend fun unregisterFromDataNotifications(notificationIntentAction: String) {
-        delegate.unregisterFromDataNotifications(
-            request = RequestProto.UnregisterFromDataNotificationsRequest.newBuilder()
-                .setNotificationIntentAction(notificationIntentAction)
-                .build(),
-        ).await()
+        delegate
+            .unregisterFromDataNotifications(
+                request =
+                    RequestProto.UnregisterFromDataNotificationsRequest.newBuilder()
+                        .setNotificationIntentAction(notificationIntentAction)
+                        .build(),
+            )
+            .await()
 
         Logger.debug(
             HEALTH_CONNECT_CLIENT_TAG,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
index afcfac2..81a4143 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/ProtoToRecordUtils.kt
@@ -79,7 +79,7 @@
 internal val DataProto.DataPoint.metadata: Metadata
     get() =
         Metadata(
-            uid = if (hasUid()) uid else Metadata.EMPTY_UID,
+            id = if (hasUid()) uid else Metadata.EMPTY_ID,
             dataOrigin = DataOrigin(dataOrigin.applicationId),
             lastModifiedTime = Instant.ofEpochMilli(updateTimeMillis),
             clientRecordId = if (hasClientId()) clientId else null,
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
index 7ced9bd..ce70f94 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/impl/converters/records/RecordToProtoUtils.kt
@@ -52,8 +52,8 @@
 
 @SuppressWarnings("GoodTime") // Suppress GoodTime for serialize/de-serialize.
 private fun DataProto.DataPoint.Builder.setMetadata(metadata: Metadata) = apply {
-    if (metadata.uid != Metadata.EMPTY_UID) {
-        setUid(metadata.uid)
+    if (metadata.id != Metadata.EMPTY_ID) {
+        setUid(metadata.id)
     }
     if (metadata.dataOrigin.packageName.isNotEmpty()) {
         setDataOrigin(
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/metadata/Metadata.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/metadata/Metadata.kt
index 2861b86..7bcace3 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/metadata/Metadata.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/records/metadata/Metadata.kt
@@ -15,8 +15,8 @@
  */
 package androidx.health.connect.client.records.metadata
 
-import java.time.Instant
 import androidx.health.connect.client.records.Record
+import java.time.Instant
 
 /** Set of shared metadata fields for [Record]. */
 @SuppressWarnings("NewApi") // Temporary until we can enable java8 desugaring effectively.
@@ -26,7 +26,7 @@
      * When [Record] is created before insertion, this takes a sentinel value, any assigned value
      * will be ignored.
      */
-    public val uid: String = EMPTY_UID,
+    public val id: String = EMPTY_ID,
 
     /**
      * Where the data comes from, such as application information originally generated this data.
@@ -36,9 +36,9 @@
     public val dataOrigin: DataOrigin = DataOrigin(""),
 
     /**
-     * Automatically populated to when data was last modified (or originally created).
-     * When [Record] is created before inserted, this contains a sentinel value, any assigned value
-     * will be ignored.
+     * Automatically populated to when data was last modified (or originally created). When [Record]
+     * is created before inserted, this contains a sentinel value, any assigned value will be
+     * ignored.
      */
     public val lastModifiedTime: Instant = Instant.EPOCH,
 
@@ -71,7 +71,7 @@
         if (this === other) return true
         if (other !is Metadata) return false
 
-        if (uid != other.uid) return false
+        if (id != other.id) return false
         if (dataOrigin != other.dataOrigin) return false
         if (lastModifiedTime != other.lastModifiedTime) return false
         if (clientRecordId != other.clientRecordId) return false
@@ -82,7 +82,7 @@
     }
 
     override fun hashCode(): Int {
-        var result = uid.hashCode()
+        var result = id.hashCode()
         result = 31 * result + dataOrigin.hashCode()
         result = 31 * result + lastModifiedTime.hashCode()
         result = 31 * result + (clientRecordId?.hashCode() ?: 0)
@@ -92,7 +92,7 @@
     }
 
     internal companion object {
-        internal const val EMPTY_UID: String = ""
+        internal const val EMPTY_ID: String = ""
 
         /** A default instance of metadata with no fields initialised. */
         @JvmField internal val EMPTY = Metadata()
diff --git a/health/connect/connect-client/src/main/java/androidx/health/connect/client/response/InsertRecordsResponse.kt b/health/connect/connect-client/src/main/java/androidx/health/connect/client/response/InsertRecordsResponse.kt
index 68a3956..aa61efc 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/connect/client/response/InsertRecordsResponse.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/connect/client/response/InsertRecordsResponse.kt
@@ -24,8 +24,8 @@
 internal constructor(
     /*
      * Contains
-     * [androidx.health.connect.client.metadata.Metadata.uid] of inserted [Record] in same order as
+     * [androidx.health.connect.client.metadata.Metadata.recordId] of inserted [Record] in same order as
      * passed to [androidx.health.connect.client.HealthDataClient.insertRecords].
      */
-    val recordUidsList: List<String>
+    val recordIdsList: List<String>
 )
diff --git a/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/RegisterForDataNotificationsCallback.kt b/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/RegisterForDataNotificationsCallback.kt
index 2ca939a..061fe1f 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/RegisterForDataNotificationsCallback.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/RegisterForDataNotificationsCallback.kt
@@ -22,7 +22,7 @@
 import com.google.common.util.concurrent.SettableFuture
 
 internal class RegisterForDataNotificationsCallback(
-    private val resultFuture: SettableFuture<Void>,
+    private val resultFuture: SettableFuture<Void?>,
 ) : IRegisterForDataNotificationsCallback.Stub() {
 
     override fun onSuccess() {
diff --git a/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/UnregisterFromDataNotificationsCallback.kt b/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/UnregisterFromDataNotificationsCallback.kt
index dabafe48..f3201d8 100644
--- a/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/UnregisterFromDataNotificationsCallback.kt
+++ b/health/connect/connect-client/src/main/java/androidx/health/platform/client/impl/UnregisterFromDataNotificationsCallback.kt
@@ -22,7 +22,7 @@
 import com.google.common.util.concurrent.SettableFuture
 
 internal class UnregisterFromDataNotificationsCallback(
-    private val resultFuture: SettableFuture<Void>,
+    private val resultFuture: SettableFuture<Void?>,
 ) : IUnregisterFromDataNotificationsCallback.Stub() {
 
     override fun onSuccess() {
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt
new file mode 100644
index 0000000..7dc78ba
--- /dev/null
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/PermissionControllerTest.kt
@@ -0,0 +1,54 @@
+/*
+ * Copyright 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package androidx.health.connect.client
+
+import android.content.Context
+import androidx.health.connect.client.permission.HealthPermission
+import androidx.health.connect.client.records.StepsRecord
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import com.google.common.truth.Truth
+import org.junit.Before
+import org.junit.Test
+import org.junit.runner.RunWith
+
+private const val PROVIDER_PACKAGE_NAME = "com.example.fake.provider"
+
+@RunWith(AndroidJUnit4::class)
+class PermissionControllerTest {
+
+    private lateinit var context: Context
+
+    @Before
+    fun setUp() {
+        context = ApplicationProvider.getApplicationContext()
+    }
+
+    @Test
+    fun createIntentTest() {
+        val requestPermissionContract =
+            PermissionController.createRequestPermissionResultContract(PROVIDER_PACKAGE_NAME)
+        val intent =
+            requestPermissionContract.createIntent(
+                context,
+                setOf(HealthPermission.createReadPermission(StepsRecord::class))
+            )
+
+        Truth.assertThat(intent.action).isEqualTo("androidx.health.ACTION_REQUEST_PERMISSIONS")
+        Truth.assertThat(intent.`package`).isEqualTo(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 cfb1ce1..e09b709 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
@@ -235,7 +235,7 @@
             )
         }
 
-        assertThat(response.recordUidsList).containsExactly("0")
+        assertThat(response.recordIdsList).containsExactly("0")
         assertThat(fakeAhpServiceStub.lastUpsertDataRequest?.dataPoints)
             .containsExactly(
                 DataProto.DataPoint.newBuilder()
@@ -263,7 +263,7 @@
             )
         }
 
-        assertThat(response.recordUidsList).containsExactly("0")
+        assertThat(response.recordIdsList).containsExactly("0")
         assertThat(fakeAhpServiceStub.lastUpsertDataRequest?.dataPoints)
             .containsExactly(
                 DataProto.DataPoint.newBuilder()
@@ -293,7 +293,7 @@
             )
         }
 
-        assertThat(response.recordUidsList).containsExactly("0")
+        assertThat(response.recordIdsList).containsExactly("0")
         assertThat(fakeAhpServiceStub.lastUpsertDataRequest?.dataPoints)
             .containsExactly(
                 DataProto.DataPoint.newBuilder()
@@ -328,7 +328,7 @@
         val response = testBlocking {
             healthConnectClient.readRecord(
                 StepsRecord::class,
-                uid = "testUid",
+                recordId = "testUid",
             )
         }
 
@@ -352,7 +352,7 @@
                     endZoneOffset = null,
                     metadata =
                         Metadata(
-                            uid = "testUid",
+                            id = "testUid",
                             device = Device(),
                         )
                 )
@@ -409,7 +409,7 @@
                     endZoneOffset = null,
                     metadata =
                         Metadata(
-                            uid = "testUid",
+                            id = "testUid",
                             device = Device(),
                         )
                 )
@@ -472,7 +472,7 @@
                         startZoneOffset = null,
                         endTime = Instant.ofEpochMilli(5678L),
                         endZoneOffset = null,
-                        metadata = Metadata(uid = "testUid")
+                        metadata = Metadata(id = "testUid")
                     )
                 )
             )
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
index be713e2..be9cdbe 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/records/AllRecordsConverterTest.kt
@@ -109,7 +109,7 @@
 private val END_ZONE_OFFSET = ZoneOffset.ofHours(2)
 private val TEST_METADATA =
     Metadata(
-        uid = "uid",
+        id = "uid",
         clientRecordId = "clientId",
         clientRecordVersion = 10,
         device = Device(manufacturer = "manufacturer"),
diff --git a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/response/ChangesResponseConverterTest.kt b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/response/ChangesResponseConverterTest.kt
index 826ec92..dde8916 100644
--- a/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/response/ChangesResponseConverterTest.kt
+++ b/health/connect/connect-client/src/test/java/androidx/health/connect/client/impl/converters/response/ChangesResponseConverterTest.kt
@@ -79,7 +79,7 @@
                     endZoneOffset = null,
                     metadata =
                         Metadata(
-                            uid = "uid",
+                            id = "uid",
                             lastModifiedTime = Instant.ofEpochMilli(9999L),
                             dataOrigin = DataOrigin(packageName = "pkg1"),
                             device = Device()
@@ -97,8 +97,7 @@
 
         val changesResponse = toChangesResponse(proto)
         assertThat(changesResponse.changes).hasSize(1)
-        assertThat((changesResponse.changes[0] as? DeletionChange)?.uid)
-            .isEqualTo("deleteUid")
+        assertThat((changesResponse.changes[0] as? DeletionChange)?.recordId).isEqualTo("deleteUid")
     }
 
     @Test
diff --git a/health/health-services-client/api/1.0.0-beta01.txt b/health/health-services-client/api/1.0.0-beta01.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/1.0.0-beta01.txt
+++ b/health/health-services-client/api/1.0.0-beta01.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/api/current.txt b/health/health-services-client/api/current.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/current.txt
+++ b/health/health-services-client/api/current.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/api/public_plus_experimental_1.0.0-beta01.txt b/health/health-services-client/api/public_plus_experimental_1.0.0-beta01.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/public_plus_experimental_1.0.0-beta01.txt
+++ b/health/health-services-client/api/public_plus_experimental_1.0.0-beta01.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/api/public_plus_experimental_current.txt b/health/health-services-client/api/public_plus_experimental_current.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/public_plus_experimental_current.txt
+++ b/health/health-services-client/api/public_plus_experimental_current.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/api/restricted_1.0.0-beta01.txt b/health/health-services-client/api/restricted_1.0.0-beta01.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/restricted_1.0.0-beta01.txt
+++ b/health/health-services-client/api/restricted_1.0.0-beta01.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/api/restricted_current.txt b/health/health-services-client/api/restricted_current.txt
index 4f57e68..9d7e99c 100644
--- a/health/health-services-client/api/restricted_current.txt
+++ b/health/health-services-client/api/restricted_current.txt
@@ -5,7 +5,7 @@
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> addGoalToActiveExerciseAsync(androidx.health.services.client.data.ExerciseGoal<?> exerciseGoal);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> clearUpdateCallbackAsync(androidx.health.services.client.ExerciseUpdateCallback callback);
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> endExerciseAsync();
-    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushExerciseAsync();
+    method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> flushAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseCapabilities> getCapabilitiesAsync();
     method public com.google.common.util.concurrent.ListenableFuture<androidx.health.services.client.data.ExerciseInfo> getCurrentExerciseInfoAsync();
     method public com.google.common.util.concurrent.ListenableFuture<java.lang.Void> markLapAsync();
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
index 5b303bd..59e76bd 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/ExerciseClient.kt
@@ -148,7 +148,7 @@
      * @return a [ListenableFuture] that completes once the flush has been completed or fails if the
      * calling application does not own the active exercise.
      */
-    public fun flushExerciseAsync(): ListenableFuture<Void>
+    public fun flushAsync(): ListenableFuture<Void>
 
     /**
      * Ends the current lap, calls [ExerciseUpdateCallback.onLapSummaryReceived] with data spanning
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt b/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
index f7fc80d..b60d79a 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/data/ProtoParcelable.kt
@@ -35,7 +35,7 @@
     public abstract val proto: T
 
     /** Serialized representation of this object. */
-    protected val bytes: ByteArray by lazy { proto.toByteArray() }
+    protected val bytes: ByteArray get() { return proto.toByteArray() }
 
     public override fun describeContents(): Int = 0
 
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
index 94dd9c5..786f636 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/ServiceBackedExerciseClient.kt
@@ -115,7 +115,7 @@
         service.endExercise(packageName, StatusCallback(resultFuture))
     }
 
-    override fun flushExerciseAsync(): ListenableFuture<Void> {
+    override fun flushAsync(): ListenableFuture<Void> {
         val request = FlushRequest(packageName)
         return execute { service, resultFuture ->
             service.flushExercise(request, StatusCallback(resultFuture))
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
index 125f749..5219307 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/AutoPauseAndResumeConfigRequest.kt
@@ -32,12 +32,11 @@
     public val shouldEnable: Boolean,
 ) : ProtoParcelable<RequestsProto.AutoPauseAndResumeConfigRequest>() {
 
-    override val proto: RequestsProto.AutoPauseAndResumeConfigRequest by lazy {
+    override val proto: RequestsProto.AutoPauseAndResumeConfigRequest =
         RequestsProto.AutoPauseAndResumeConfigRequest.newBuilder()
             .setPackageName(packageName)
             .setShouldEnable(shouldEnable)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
index cb477c9..875a79b 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/CapabilitiesRequest.kt
@@ -30,9 +30,8 @@
 public class CapabilitiesRequest(public val packageName: String) :
     ProtoParcelable<RequestsProto.CapabilitiesRequest>() {
 
-    override val proto: RequestsProto.CapabilitiesRequest by lazy {
+    override val proto: RequestsProto.CapabilitiesRequest =
         RequestsProto.CapabilitiesRequest.newBuilder().setPackageName(packageName).build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
index b232dfb..86c53bf 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/FlushRequest.kt
@@ -30,9 +30,8 @@
 public class FlushRequest(public val packageName: String) :
     ProtoParcelable<RequestsProto.FlushRequest>() {
 
-    override val proto: RequestsProto.FlushRequest by lazy {
+    override val proto: RequestsProto.FlushRequest =
         RequestsProto.FlushRequest.newBuilder().setPackageName(packageName).build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
index dd15164..4cc8131 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureRegistrationRequest.kt
@@ -33,12 +33,11 @@
     public val dataType: DataType<*, *>,
 ) : ProtoParcelable<RequestsProto.MeasureRegistrationRequest>() {
 
-    override val proto: RequestsProto.MeasureRegistrationRequest by lazy {
+    override val proto: RequestsProto.MeasureRegistrationRequest =
         RequestsProto.MeasureRegistrationRequest.newBuilder()
             .setPackageName(packageName)
             .setDataType(dataType.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
index ee85ba4..6c07a1f 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/MeasureUnregistrationRequest.kt
@@ -33,12 +33,11 @@
     public val dataType: DataType<*, *>,
 ) : ProtoParcelable<RequestsProto.MeasureUnregistrationRequest>() {
 
-    override val proto: RequestsProto.MeasureUnregistrationRequest by lazy {
+    override val proto: RequestsProto.MeasureUnregistrationRequest =
         RequestsProto.MeasureUnregistrationRequest.newBuilder()
             .setPackageName(packageName)
             .setDataType(dataType.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
index 8367d89..40303af 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerCallbackRegistrationRequest.kt
@@ -33,12 +33,11 @@
     public val passiveListenerConfig: PassiveListenerConfig,
 ) : ProtoParcelable<RequestsProto.PassiveListenerCallbackRegistrationRequest>() {
 
-    override val proto: RequestsProto.PassiveListenerCallbackRegistrationRequest by lazy {
+    override val proto: RequestsProto.PassiveListenerCallbackRegistrationRequest =
         RequestsProto.PassiveListenerCallbackRegistrationRequest.newBuilder()
             .setPackageName(packageName)
             .setConfig(passiveListenerConfig.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
index b41e462..b280b85 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PassiveListenerServiceRegistrationRequest.kt
@@ -34,13 +34,12 @@
     public val passiveListenerConfig: PassiveListenerConfig,
 ) : ProtoParcelable<RequestsProto.PassiveListenerServiceRegistrationRequest>() {
 
-    override val proto: RequestsProto.PassiveListenerServiceRegistrationRequest by lazy {
+    override val proto: RequestsProto.PassiveListenerServiceRegistrationRequest =
         RequestsProto.PassiveListenerServiceRegistrationRequest.newBuilder()
             .setPackageName(packageName)
             .setListenerServiceClass(passiveListenerServiceClassName)
             .setConfig(passiveListenerConfig.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
index ab6c470..42a659c 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/PrepareExerciseRequest.kt
@@ -33,12 +33,11 @@
     public val warmUpConfig: WarmUpConfig,
 ) : ProtoParcelable<RequestsProto.PrepareExerciseRequest>() {
 
-    override val proto: RequestsProto.PrepareExerciseRequest by lazy {
+    override val proto: RequestsProto.PrepareExerciseRequest =
         RequestsProto.PrepareExerciseRequest.newBuilder()
             .setPackageName(packageName)
             .setConfig(warmUpConfig.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
index fab794b..617ce951 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/request/StartExerciseRequest.kt
@@ -33,12 +33,11 @@
     public val exerciseConfig: ExerciseConfig,
 ) : ProtoParcelable<RequestsProto.StartExerciseRequest>() {
 
-    override val proto: RequestsProto.StartExerciseRequest by lazy {
+    override val proto: RequestsProto.StartExerciseRequest =
         RequestsProto.StartExerciseRequest.newBuilder()
             .setPackageName(packageName)
             .setConfig(exerciseConfig.toProto())
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
index abd811f..d752820 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/AvailabilityResponse.kt
@@ -39,12 +39,11 @@
     public val availability: Availability,
 ) : ProtoParcelable<ResponsesProto.AvailabilityResponse>() {
 
-    override val proto: ResponsesProto.AvailabilityResponse by lazy {
+    override val proto: ResponsesProto.AvailabilityResponse =
         ResponsesProto.AvailabilityResponse.newBuilder()
             .setDataType(dataType.proto)
             .setAvailability(availability.toProto())
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
index 8e0aa07..c5ec12a 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/DataPointsResponse.kt
@@ -31,7 +31,7 @@
         proto: ResponsesProto.DataPointsResponse
     ) : this(proto.dataPointsList.map { DataPoint.fromProto(it) })
 
-    override val proto: ResponsesProto.DataPointsResponse by lazy {
+    override val proto: ResponsesProto.DataPointsResponse =
         ResponsesProto.DataPointsResponse.newBuilder()
             .addAllDataPoints(
                 dataPoints
@@ -45,7 +45,6 @@
                     }
             )
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
index 0791601..c7ce7be 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseCapabilitiesResponse.kt
@@ -34,11 +34,10 @@
     public val exerciseCapabilities: ExerciseCapabilities,
 ) : ProtoParcelable<ResponsesProto.ExerciseCapabilitiesResponse>() {
 
-    override val proto: ResponsesProto.ExerciseCapabilitiesResponse by lazy {
+    override val proto: ResponsesProto.ExerciseCapabilitiesResponse =
         ResponsesProto.ExerciseCapabilitiesResponse.newBuilder()
             .setCapabilities(exerciseCapabilities.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
index e08d6ba..b814697 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseInfoResponse.kt
@@ -31,9 +31,8 @@
 public class ExerciseInfoResponse(public val exerciseInfo: ExerciseInfo) :
     ProtoParcelable<ResponsesProto.ExerciseInfoResponse>() {
 
-    override val proto: ResponsesProto.ExerciseInfoResponse by lazy {
+    override val proto: ResponsesProto.ExerciseInfoResponse =
         ResponsesProto.ExerciseInfoResponse.newBuilder().setExerciseInfo(exerciseInfo.proto).build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
index b9fdef5..c8e24f8 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseLapSummaryResponse.kt
@@ -31,11 +31,10 @@
 public class ExerciseLapSummaryResponse(public val exerciseLapSummary: ExerciseLapSummary) :
     ProtoParcelable<ResponsesProto.ExerciseLapSummaryResponse>() {
 
-    override val proto: ResponsesProto.ExerciseLapSummaryResponse by lazy {
+    override val proto: ResponsesProto.ExerciseLapSummaryResponse =
         ResponsesProto.ExerciseLapSummaryResponse.newBuilder()
             .setLapSummary(exerciseLapSummary.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
index 5b9b295..377053e 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/ExerciseUpdateResponse.kt
@@ -31,11 +31,10 @@
 public class ExerciseUpdateResponse(public val exerciseUpdate: ExerciseUpdate) :
     ProtoParcelable<ResponsesProto.ExerciseUpdateResponse>() {
 
-    override val proto: ResponsesProto.ExerciseUpdateResponse by lazy {
+    override val proto: ResponsesProto.ExerciseUpdateResponse =
         ResponsesProto.ExerciseUpdateResponse.newBuilder()
             .setExerciseUpdate(exerciseUpdate.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
index c8f9965..600f26e 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/HealthEventResponse.kt
@@ -34,9 +34,8 @@
         proto: ResponsesProto.HealthEventResponse
     ) : this(HealthEvent(proto.healthEvent))
 
-    override val proto: ResponsesProto.HealthEventResponse by lazy {
+    override val proto: ResponsesProto.HealthEventResponse =
         ResponsesProto.HealthEventResponse.newBuilder().setHealthEvent(healthEvent.proto).build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
index d1876fa..c715092 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/MeasureCapabilitiesResponse.kt
@@ -33,11 +33,10 @@
     public val measureCapabilities: MeasureCapabilities,
 ) : ProtoParcelable<ResponsesProto.MeasureCapabilitiesResponse>() {
 
-    override val proto: ResponsesProto.MeasureCapabilitiesResponse by lazy {
+    override val proto: ResponsesProto.MeasureCapabilitiesResponse =
         ResponsesProto.MeasureCapabilitiesResponse.newBuilder()
             .setCapabilities(measureCapabilities.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
index 0839429..dd38942 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringCapabilitiesResponse.kt
@@ -33,11 +33,10 @@
     public val passiveMonitoringCapabilities: PassiveMonitoringCapabilities,
 ) : ProtoParcelable<ResponsesProto.PassiveMonitoringCapabilitiesResponse>() {
 
-    override val proto: ResponsesProto.PassiveMonitoringCapabilitiesResponse by lazy {
+    override val proto: ResponsesProto.PassiveMonitoringCapabilitiesResponse =
         ResponsesProto.PassiveMonitoringCapabilitiesResponse.newBuilder()
             .setCapabilities(passiveMonitoringCapabilities.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
index e70073f..186b9c4 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringGoalResponse.kt
@@ -36,9 +36,8 @@
         proto: ResponsesProto.PassiveMonitoringGoalResponse
     ) : this(PassiveGoal(proto.goal))
 
-    override val proto: ResponsesProto.PassiveMonitoringGoalResponse by lazy {
+    override val proto: ResponsesProto.PassiveMonitoringGoalResponse =
         ResponsesProto.PassiveMonitoringGoalResponse.newBuilder().setGoal(passiveGoal.proto).build()
-    }
 
     public companion object {
         @JvmField
diff --git a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
index 99fc3fc..0deaf87 100644
--- a/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
+++ b/health/health-services-client/src/main/java/androidx/health/services/client/impl/response/PassiveMonitoringUpdateResponse.kt
@@ -35,11 +35,10 @@
         proto: ResponsesProto.PassiveMonitoringUpdateResponse
     ) : this(PassiveMonitoringUpdate(proto.update))
 
-    override val proto: ResponsesProto.PassiveMonitoringUpdateResponse by lazy {
+    override val proto: ResponsesProto.PassiveMonitoringUpdateResponse =
         ResponsesProto.PassiveMonitoringUpdateResponse.newBuilder()
             .setUpdate(passiveMonitoringUpdate.proto)
             .build()
-    }
 
     public companion object {
         @JvmField
diff --git a/libraryversions.toml b/libraryversions.toml
index 0b2e353..9650fee 100644
--- a/libraryversions.toml
+++ b/libraryversions.toml
@@ -88,7 +88,7 @@
 PRIVACYSANDBOX_TOOLS = "1.0.0-alpha01"
 PROFILEINSTALLER = "1.3.0-alpha01"
 RECOMMENDATION = "1.1.0-alpha01"
-RECYCLERVIEW = "1.3.0-beta03"
+RECYCLERVIEW = "1.3.0-rc01"
 RECYCLERVIEW_SELECTION = "1.2.0-alpha02"
 REMOTECALLBACK = "1.0.0-alpha02"
 RESOURCEINSPECTION = "1.1.0-alpha01"
@@ -114,7 +114,7 @@
 TEST_UIAUTOMATOR = "2.3.0-alpha02"
 TEXT = "1.0.0-alpha01"
 TRACING = "1.2.0-alpha01"
-TRACING_PERFETTO = "1.0.0-alpha03"
+TRACING_PERFETTO = "1.0.0-alpha04"
 TRANSITION = "1.5.0-alpha01"
 TV = "1.0.0-alpha01"
 TVPROVIDER = "1.1.0-alpha02"
@@ -135,7 +135,7 @@
 WEAR_WATCHFACE = "1.2.0-alpha02"
 WEBKIT = "1.6.0-alpha02"
 WINDOW = "1.1.0-alpha04"
-WINDOW_EXTENSIONS = "1.1.0-alpha01"
+WINDOW_EXTENSIONS = "1.1.0-alpha02"
 WINDOW_SIDECAR = "1.0.0-rc01"
 WORK = "2.8.0-alpha05"
 
diff --git a/lifecycle/lifecycle-livedata-ktx/api/current.ignore b/lifecycle/lifecycle-livedata-ktx/api/current.ignore
deleted file mode 100644
index 75be76d..0000000
--- a/lifecycle/lifecycle-livedata-ktx/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.LiveDataScope#emit(T, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.lifecycle.LiveDataScope.emit
-ParameterNameChange: androidx.lifecycle.LiveDataScope#emitSource(androidx.lifecycle.LiveData<T>, kotlin.coroutines.Continuation<? super kotlinx.coroutines.DisposableHandle>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.lifecycle.LiveDataScope.emitSource
diff --git a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore b/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
deleted file mode 100644
index 75be76d..0000000
--- a/lifecycle/lifecycle-livedata-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.LiveDataScope#emit(T, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.lifecycle.LiveDataScope.emit
-ParameterNameChange: androidx.lifecycle.LiveDataScope#emitSource(androidx.lifecycle.LiveData<T>, kotlin.coroutines.Continuation<? super kotlinx.coroutines.DisposableHandle>) parameter #1:
-    Attempted to remove parameter name from parameter arg2 in androidx.lifecycle.LiveDataScope.emitSource
diff --git a/lifecycle/lifecycle-runtime-ktx/api/current.ignore b/lifecycle/lifecycle-runtime-ktx/api/current.ignore
deleted file mode 100644
index 5fdbc71..0000000
--- a/lifecycle/lifecycle-runtime-ktx/api/current.ignore
+++ /dev/null
@@ -1,35 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenCreated
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenCreated
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenResumed
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenResumed
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenStarted
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenStarted
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.PausingDispatcherKt.whenStateAtLeast
-ParameterNameChange: androidx.lifecycle.RepeatOnLifecycleKt#repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.RepeatOnLifecycleKt.repeatOnLifecycle
-ParameterNameChange: androidx.lifecycle.RepeatOnLifecycleKt#repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.RepeatOnLifecycleKt.repeatOnLifecycle
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withCreated
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withCreated
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withResumed
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withResumed
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withStarted
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withStarted
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.WithLifecycleStateKt.withStateAtLeast
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.WithLifecycleStateKt.withStateAtLeast
diff --git a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore
deleted file mode 100644
index 0e0303b..0000000
--- a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,39 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenCreated
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenCreated
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenResumed
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenResumed
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenStarted
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.PausingDispatcherKt.whenStarted
-ParameterNameChange: androidx.lifecycle.PausingDispatcherKt#whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.PausingDispatcherKt.whenStateAtLeast
-ParameterNameChange: androidx.lifecycle.RepeatOnLifecycleKt#repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.RepeatOnLifecycleKt.repeatOnLifecycle
-ParameterNameChange: androidx.lifecycle.RepeatOnLifecycleKt#repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.RepeatOnLifecycleKt.repeatOnLifecycle
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#suspendWithStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, boolean, kotlinx.coroutines.CoroutineDispatcher, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #5:
-    Attempted to remove parameter name from parameter arg6 in androidx.lifecycle.WithLifecycleStateKt.suspendWithStateAtLeastUnchecked
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withCreated(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withCreated
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withCreated(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withCreated
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withResumed(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withResumed
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withResumed(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withResumed
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStarted(androidx.lifecycle.Lifecycle, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withStarted
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStarted(androidx.lifecycle.LifecycleOwner, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #2:
-    Attempted to remove parameter name from parameter arg3 in androidx.lifecycle.WithLifecycleStateKt.withStarted
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.WithLifecycleStateKt.withStateAtLeast
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStateAtLeast(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.WithLifecycleStateKt.withStateAtLeast
-ParameterNameChange: androidx.lifecycle.WithLifecycleStateKt#withStateAtLeastUnchecked(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State, kotlin.jvm.functions.Function0<? extends R>, kotlin.coroutines.Continuation<? super R>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.lifecycle.WithLifecycleStateKt.withStateAtLeastUnchecked
diff --git a/lifecycle/lifecycle-runtime-testing/api/current.ignore b/lifecycle/lifecycle-runtime-testing/api/current.ignore
deleted file mode 100644
index 1d9b6f0..0000000
--- a/lifecycle/lifecycle-runtime-testing/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.testing.TestLifecycleOwner#setCurrentState(androidx.lifecycle.Lifecycle.State) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.lifecycle.testing.TestLifecycleOwner.setCurrentState
diff --git a/lifecycle/lifecycle-runtime-testing/api/restricted_current.ignore b/lifecycle/lifecycle-runtime-testing/api/restricted_current.ignore
deleted file mode 100644
index 1d9b6f0..0000000
--- a/lifecycle/lifecycle-runtime-testing/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.lifecycle.testing.TestLifecycleOwner#setCurrentState(androidx.lifecycle.Lifecycle.State) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.lifecycle.testing.TestLifecycleOwner.setCurrentState
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/current.ignore b/lifecycle/lifecycle-viewmodel-compose/api/current.ignore
deleted file mode 100644
index 8be97c1..0000000
--- a/lifecycle/lifecycle-viewmodel-compose/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.viewmodel.compose.ViewModelKt#viewModel(Class<VM>, androidx.lifecycle.ViewModelStoreOwner, String, androidx.lifecycle.ViewModelProvider.Factory):
-    Attempted to remove @NonNull annotation from method androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(Class<VM>,androidx.lifecycle.ViewModelStoreOwner,String,androidx.lifecycle.ViewModelProvider.Factory)
-InvalidNullConversion: androidx.lifecycle.viewmodel.compose.ViewModelKt#viewModel(Class<VM>, androidx.lifecycle.ViewModelStoreOwner, String, androidx.lifecycle.ViewModelProvider.Factory) parameter #2:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(Class<VM> modelClass, androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, String key, androidx.lifecycle.ViewModelProvider.Factory factory)
diff --git a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.ignore
deleted file mode 100644
index 8be97c1..0000000
--- a/lifecycle/lifecycle-viewmodel-compose/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.lifecycle.viewmodel.compose.ViewModelKt#viewModel(Class<VM>, androidx.lifecycle.ViewModelStoreOwner, String, androidx.lifecycle.ViewModelProvider.Factory):
-    Attempted to remove @NonNull annotation from method androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(Class<VM>,androidx.lifecycle.ViewModelStoreOwner,String,androidx.lifecycle.ViewModelProvider.Factory)
-InvalidNullConversion: androidx.lifecycle.viewmodel.compose.ViewModelKt#viewModel(Class<VM>, androidx.lifecycle.ViewModelStoreOwner, String, androidx.lifecycle.ViewModelProvider.Factory) parameter #2:
-    Attempted to change parameter from @Nullable to @NonNull: incompatible change for parameter key in androidx.lifecycle.viewmodel.compose.ViewModelKt.viewModel(Class<VM> modelClass, androidx.lifecycle.ViewModelStoreOwner viewModelStoreOwner, String key, androidx.lifecycle.ViewModelProvider.Factory factory)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
deleted file mode 100644
index f1adf55..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ChangedType: androidx.lifecycle.SavedStateHandle#getLiveData(String):
-    Method androidx.lifecycle.SavedStateHandle.getLiveData has changed return type from androidx.lifecycle.MutableLiveData<T!> to androidx.lifecycle.MutableLiveData<T>
-ChangedType: androidx.lifecycle.SavedStateHandle#getLiveData(String, T):
-    Method androidx.lifecycle.SavedStateHandle.getLiveData has changed return type from androidx.lifecycle.MutableLiveData<T!> to androidx.lifecycle.MutableLiveData<T>
-ChangedType: androidx.lifecycle.SavedStateHandle#keys():
-    Method androidx.lifecycle.SavedStateHandle.keys has changed return type from java.util.Set<java.lang.String!> to java.util.Set<java.lang.String>
-
-
-RemovedMethod: androidx.lifecycle.SavedStateHandle#SavedStateHandle(java.util.Map<java.lang.String,java.lang.Object>):
-    Removed constructor androidx.lifecycle.SavedStateHandle(java.util.Map<java.lang.String,java.lang.Object>)
-RemovedMethod: androidx.lifecycle.SavedStateViewModelFactory#create(Class<T>):
-    Removed method androidx.lifecycle.SavedStateViewModelFactory.create(Class<T>)
diff --git a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
deleted file mode 100644
index e3484c25..0000000
--- a/lifecycle/lifecycle-viewmodel-savedstate/api/restricted_current.ignore
+++ /dev/null
@@ -1,19 +0,0 @@
-// Baseline format: 1.0
-ChangedSuperclass: androidx.lifecycle.AbstractSavedStateViewModelFactory:
-    Class androidx.lifecycle.AbstractSavedStateViewModelFactory superclass changed from androidx.lifecycle.ViewModelProvider.KeyedFactory to java.lang.Object
-ChangedSuperclass: androidx.lifecycle.SavedStateViewModelFactory:
-    Class androidx.lifecycle.SavedStateViewModelFactory superclass changed from androidx.lifecycle.ViewModelProvider.KeyedFactory to java.lang.Object
-
-
-ChangedType: androidx.lifecycle.SavedStateHandle#getLiveData(String):
-    Method androidx.lifecycle.SavedStateHandle.getLiveData has changed return type from androidx.lifecycle.MutableLiveData<T!> to androidx.lifecycle.MutableLiveData<T>
-ChangedType: androidx.lifecycle.SavedStateHandle#getLiveData(String, T):
-    Method androidx.lifecycle.SavedStateHandle.getLiveData has changed return type from androidx.lifecycle.MutableLiveData<T!> to androidx.lifecycle.MutableLiveData<T>
-ChangedType: androidx.lifecycle.SavedStateHandle#keys():
-    Method androidx.lifecycle.SavedStateHandle.keys has changed return type from java.util.Set<java.lang.String!> to java.util.Set<java.lang.String>
-
-
-RemovedMethod: androidx.lifecycle.AbstractSavedStateViewModelFactory#create(String, Class<T>):
-    Removed method androidx.lifecycle.AbstractSavedStateViewModelFactory.create(String,Class<T>)
-RemovedMethod: androidx.lifecycle.SavedStateHandle#SavedStateHandle(java.util.Map<java.lang.String,java.lang.Object>):
-    Removed constructor androidx.lifecycle.SavedStateHandle(java.util.Map<java.lang.String,java.lang.Object>)
diff --git a/lifecycle/lifecycle-viewmodel/api/current.ignore b/lifecycle/lifecycle-viewmodel/api/current.ignore
deleted file mode 100644
index d5671bd..0000000
--- a/lifecycle/lifecycle-viewmodel/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.lifecycle.ViewModelProviderKt:
-    Removed class androidx.lifecycle.ViewModelProviderKt
diff --git a/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore b/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore
deleted file mode 100644
index d5671bd..0000000
--- a/lifecycle/lifecycle-viewmodel/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-RemovedClass: androidx.lifecycle.ViewModelProviderKt:
-    Removed class androidx.lifecycle.ViewModelProviderKt
diff --git a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeCallWithImplicitCast.java b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeCallWithImplicitCast.java
index e62c987..0e074a7 100644
--- a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeCallWithImplicitCast.java
+++ b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeCallWithImplicitCast.java
@@ -16,6 +16,7 @@
 
 package androidx;
 
+import android.app.Notification;
 import android.graphics.drawable.AdaptiveIconDrawable;
 import android.graphics.drawable.Drawable;
 import android.graphics.drawable.Icon;
@@ -58,4 +59,19 @@
     public Icon methodReturnsIconAsIcon() {
         return Icon.createWithAdaptiveBitmap(null);
     }
+
+    /**
+     * This uses the constructed value as Notification.Style in a method call.
+     */
+    @RequiresApi(24)
+    public void methodUsesStyleAsParam() {
+        useStyle(new Notification.DecoratedCustomViewStyle());
+    }
+
+    /**
+     * This is here so there's a method to use the DecoratedCustomViewStyle as a Style.
+     */
+    static boolean useStyle(Notification.Style style) {
+        return false;
+    }
 }
diff --git a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorQualifiedClass.java b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorQualifiedClass.java
index 4a2daec..fbf96f9 100644
--- a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorQualifiedClass.java
+++ b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeConstructorQualifiedClass.java
@@ -28,7 +28,7 @@
      * In the generated fix, the constructor call should not be `new DecoratedCustomViewStyle()`.
      */
     @RequiresApi(24)
-    public void callQualifiedConstructor() {
-        new Notification.DecoratedCustomViewStyle();
+    public Notification.DecoratedCustomViewStyle callQualifiedConstructor() {
+        return new Notification.DecoratedCustomViewStyle();
     }
 }
diff --git a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeMethodWithQualifiedClass.java b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeMethodWithQualifiedClass.java
index 3f660a4..134b28f 100644
--- a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeMethodWithQualifiedClass.java
+++ b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeMethodWithQualifiedClass.java
@@ -33,8 +33,10 @@
      * In the generated fix, all three types should appear as qualified class names.
      */
     @RequiresApi(19)
-    public void unsafeReferenceWithQualifiedClasses(PrintAttributes.Builder builder,
-            PrintAttributes.MediaSize mediaSize) {
-        builder.setMediaSize(mediaSize);
+    public PrintAttributes.Builder unsafeReferenceWithQualifiedClasses(
+            PrintAttributes.Builder builder,
+            PrintAttributes.MediaSize mediaSize
+    ) {
+        return builder.setMediaSize(mediaSize);
     }
 }
diff --git a/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeReferenceWithExistingFix.java b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeReferenceWithExistingFix.java
new file mode 100644
index 0000000..efa821b
--- /dev/null
+++ b/lint-checks/integration-tests/src/main/java/androidx/AutofixUnsafeReferenceWithExistingFix.java
@@ -0,0 +1,59 @@
+/*
+ * 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;
+
+import android.content.res.ColorStateList;
+import android.graphics.drawable.Drawable;
+import android.view.View;
+
+import androidx.annotation.DoNotInline;
+import androidx.annotation.RequiresApi;
+
+/**
+ * Test class containing unsafe method references.
+ */
+@SuppressWarnings("unused")
+public class AutofixUnsafeReferenceWithExistingFix {
+
+    /**
+     * Unsafe reference to a new API with an already existing fix method in Api21Impl.
+     */
+    @RequiresApi(21)
+    void unsafeReferenceFixMethodExists(View view) {
+        view.setBackgroundTintList(new ColorStateList(null, null));
+    }
+
+    /**
+     * Unsafe reference to a new API without an existing fix method, but requiring API 21.
+     */
+    @RequiresApi(21)
+    void unsafeReferenceFixClassExists(Drawable drawable) {
+        drawable.getOutline(null);
+    }
+
+    @RequiresApi(21)
+    static class Api21Impl {
+        private Api21Impl() {
+            // This class is not instantiable.
+        }
+
+        @DoNotInline
+        static void setBackgroundTintList(View view, ColorStateList tint) {
+            view.setBackgroundTintList(tint);
+        }
+    }
+}
diff --git a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
index cdf4dd9..4bc5ab0 100644
--- a/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
+++ b/lint-checks/src/main/java/androidx/build/lint/ClassVerificationFailureDetector.kt
@@ -45,12 +45,17 @@
 import com.intellij.psi.PsiClass
 import com.intellij.psi.PsiClassType
 import com.intellij.psi.PsiCompiledElement
+import com.intellij.psi.PsiElement
+import com.intellij.psi.PsiExpressionList
 import com.intellij.psi.PsiMethod
+import com.intellij.psi.PsiMethodCallExpression
 import com.intellij.psi.PsiModifierListOwner
+import com.intellij.psi.PsiParenthesizedExpression
 import com.intellij.psi.PsiSuperExpression
 import com.intellij.psi.PsiType
 import com.intellij.psi.util.PsiTreeUtil
 import com.intellij.psi.util.PsiTypesUtil
+import com.intellij.psi.util.PsiUtil
 import kotlin.math.min
 import org.jetbrains.uast.UCallExpression
 import org.jetbrains.uast.UClass
@@ -504,15 +509,18 @@
             // The host class should never be null if we're looking at Java code.
             val callContainingClass = call.getContainingUClass() ?: return null
 
-            val (wrapperMethodName, methodForInsertion) = generateWrapperMethod(
-                method,
-                // Find what type the result of this call is used as
-                PsiTypesUtil.getExpectedTypeByParent(callPsi)
-            ) ?: return null
+            val (wrapperMethodName, wrapperMethodParams, methodForInsertion) =
+                generateWrapperMethod(
+                    method,
+                    // Find what type the result of this call is used as
+                    getExpectedTypeByParent(callPsi)
+                ) ?: return null
 
             val (wrapperClassName, insertionPoint, insertionSource) = generateInsertionSource(
                 api,
                 callContainingClass,
+                wrapperMethodName,
+                wrapperMethodParams,
                 methodForInsertion
             )
 
@@ -524,28 +532,73 @@
                 wrapperMethodName
             )
 
-            return fix().name("Extract to static inner class")
-                .composite(
-                    fix()
-                        .replace()
-                        .range(insertionPoint)
-                        .beginning()
-                        .with(insertionSource)
-                        .shortenNames()
-                        .build(),
-                    fix()
-                        .replace()
-                        .range(context.getLocation(call))
-                        .with(replacementCall)
-                        .shortenNames()
-                        .build(),
+            val fix = fix()
+                .name("Extract to static inner class")
+                .composite()
+                .add(fix()
+                    .replace()
+                    .range(context.getLocation(call))
+                    .with(replacementCall)
+                    .shortenNames()
+                    .build()
                 )
+
+            if (insertionPoint != null) {
+                fix.add(fix()
+                    .replace()
+                    .range(insertionPoint)
+                    .beginning()
+                    .with(insertionSource)
+                    .shortenNames()
+                    .build()
+                )
+            }
+
+            return fix.build()
+        }
+
+        /**
+         * Find what type the parent of the element is expecting the element to be.
+         */
+        private fun getExpectedTypeByParent(element: PsiElement): PsiType? {
+            val expectedType = PsiTypesUtil.getExpectedTypeByParent(element)
+            if (expectedType != null) return expectedType
+
+            // PsiTypesUtil didn't know the expected type, but it doesn't handle the case when the
+            // value is a parameter to a method call. See if that's what this is.
+            val (parent, childOfParent) = getParentSkipParens(element)
+            if (parent is PsiExpressionList) {
+                val grandparent = PsiUtil.skipParenthesizedExprUp(parent.parent)
+                if (grandparent is PsiMethodCallExpression) {
+                    val paramIndex = parent.expressions.indexOf(childOfParent)
+                    if (paramIndex < 0) return null
+                    val method = grandparent.resolveMethod() ?: return null
+                    return method.parameterList.getParameter(paramIndex)?.type
+                }
+            }
+            // Not a parameter, still don't know the expected type (or there isn't one).
+            return null
+        }
+
+        /**
+         * Return the first parent of the element which isn't a PsiParenthesizedExpression, and also
+         * return the direct child element of that parent.
+         */
+        private fun getParentSkipParens(element: PsiElement): Pair<PsiElement, PsiElement> {
+            val parent = element.parent
+            return if (parent is PsiParenthesizedExpression) {
+                getParentSkipParens(parent)
+            } else {
+                Pair(parent, element)
+            }
         }
 
         /**
          * Generates source code for a wrapper method and class (where applicable) and calculates
          * the insertion point. If the wrapper class already exists, returns source code for the
          * method body only with an insertion point at the end of the existing wrapper class body.
+         * If the wrapper class and method both already exists, just returns the name of the
+         * wrapper class.
          *
          * Source code follows the general format:
          *
@@ -559,19 +612,26 @@
          *
          * @param api API level at which the platform method can be safely called
          * @param callContainingClass Class containing the call to the platform method
+         * @param wrapperMethodName The name of the wrapper method, used to check if the wrapper
+         * method already exists
+         * @param wrapperMethodParams List of the types of the wrapper method's parameters, used to
+         * check if the wrapper method already exists
          * @param wrapperMethodBody Source code for the wrapper method
          * @return Triple containing (1) the name of the static wrapper class, (2) the insertion
-         * point for the generated source code, and (3) generated source code for a static wrapper
-         * method, including a static wrapper class if necessary
+         * point for the generated source code (or null if the wrapper method already exists), and
+         * (3) generated source code for a static wrapper method, including a static wrapper class
+         * if necessary (or null if the wrapper method already exists)
          */
         private fun generateInsertionSource(
             api: Int,
             callContainingClass: UClass,
+            wrapperMethodName: String,
+            wrapperMethodParams: List<PsiType>,
             wrapperMethodBody: String,
-        ): Triple<String, Location, String> {
+        ): Triple<String, Location?, String?> {
             val wrapperClassName = "Api${api}Impl"
-            val implInsertionPoint: Location
-            val implForInsertion: String
+            val implInsertionPoint: Location?
+            val implForInsertion: String?
 
             val existingWrapperClass = callContainingClass.innerClasses.find { innerClass ->
                 innerClass.name == wrapperClassName
@@ -590,8 +650,18 @@
 
                 """.trimIndent()
             } else {
-                implInsertionPoint = context.getLocation(existingWrapperClass.lastChild)
-                implForInsertion = wrapperMethodBody.trimIndent()
+                val existingWrapperMethod = existingWrapperClass.methods.find { method ->
+                    method.name == wrapperMethodName &&
+                        wrapperMethodParams == getParameterTypes(method)
+                }
+                if (existingWrapperMethod == null) {
+                    implInsertionPoint = context.getLocation(existingWrapperClass.lastChild)
+                    // Add a newline to force the `}`s for the class and method onto different lines
+                    implForInsertion = wrapperMethodBody.trimIndent() + "\n"
+                } else {
+                    implInsertionPoint = null
+                    implForInsertion = null
+                }
             }
 
             return Triple(
@@ -686,7 +756,7 @@
         private fun generateWrapperMethod(
             method: PsiMethod,
             expectedReturnType: PsiType?
-        ): Pair<String, String>? {
+        ): Triple<String, List<PsiType>, String>? {
             val evaluator = context.evaluator
             val isStatic = evaluator.isStatic(method)
             val isConstructor = method.isConstructor
@@ -712,6 +782,9 @@
             }
             val typedParamsStr = (listOfNotNull(hostParam) + typedParams).joinToString(", ")
 
+            val paramTypes = listOf(PsiTypesUtil.getClassType(containingClass)) +
+                getParameterTypes(method)
+
             val namedParamsStr = method.parameters.joinToString(separator = ", ") { param ->
                 "${param.name}"
             }
@@ -744,12 +817,21 @@
             if (expectedReturnType != null && expectedReturnType.canonicalText != returnTypeStr) {
                 returnTypeStr = expectedReturnType.canonicalText
                 wrapperMethodName += "Returns${expectedReturnType.presentableText}"
+            } else if (expectedReturnType == null && returnTypeStr != "void" &&
+                !classAvailableAtMinSdk(returnTypeStr)) {
+                // This method returns a value of a type that isn't available at the min SDK.
+                // The expected return type is null either because the returned value isn't used or
+                // getExpectedTypeByParent didn't know how it is used. In case it is used and is
+                // actually expected to be a different type, don't suggest an autofix to prevent an
+                // invalid implicit cast.
+                return null
             }
 
             val returnStmtStr = if ("void" == returnTypeStr) "" else "return "
 
-            return Pair(
+            return Triple(
                 wrapperMethodName,
+                paramTypes,
                 """
                     @androidx.annotation.DoNotInline
                     static $typeParamsStr$returnTypeStr $wrapperMethodName($typedParamsStr) {
@@ -759,6 +841,22 @@
             )
         }
 
+        /**
+         * Returns a list of the method's parameter types.
+         */
+        private fun getParameterTypes(method: PsiMethod): List<PsiType> =
+            method.parameterList.parameters.map { it.type }
+
+        /**
+         * Check if the specified class is available at the min SDK.
+         */
+        private fun classAvailableAtMinSdk(className: String): Boolean {
+            val apiDatabase = apiDatabase ?: return false
+            val minSdk = getMinSdk(context)
+            val version = apiDatabase.getClassVersion(className)
+            return version <= minSdk
+        }
+
         private fun getInheritanceChain(
             derivedClass: PsiClassType,
             baseClass: PsiClassType?
diff --git a/lint-checks/src/test/java/androidx/build/lint/ClassVerificationFailureDetectorTest.kt b/lint-checks/src/test/java/androidx/build/lint/ClassVerificationFailureDetectorTest.kt
index 4e1192b..efdf27c 100644
--- a/lint-checks/src/test/java/androidx/build/lint/ClassVerificationFailureDetectorTest.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/ClassVerificationFailureDetectorTest.kt
@@ -18,6 +18,7 @@
 
 package androidx.build.lint
 
+import androidx.build.lint.Stubs.Companion.DoNotInline
 import androidx.build.lint.Stubs.Companion.RequiresApi
 import androidx.build.lint.Stubs.Companion.IntRange
 import org.junit.Ignore
@@ -347,6 +348,48 @@
         check(*input).expectFixDiffs(expectedFix)
     }
 
+    @Test
+    fun `Auto-fix unsafe reference in Java source when the fix code already exists`() {
+        val input = arrayOf(
+            javaSample("androidx.AutofixUnsafeReferenceWithExistingFix"),
+            RequiresApi,
+            DoNotInline
+        )
+
+        /* ktlint-disable max-line-length */
+        val expected = """
+src/androidx/AutofixUnsafeReferenceWithExistingFix.java:37: Error: This call references a method added in API level 21; however, the containing class androidx.AutofixUnsafeReferenceWithExistingFix is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+        view.setBackgroundTintList(new ColorStateList(null, null));
+             ~~~~~~~~~~~~~~~~~~~~~
+src/androidx/AutofixUnsafeReferenceWithExistingFix.java:45: Error: This call references a method added in API level 21; however, the containing class androidx.AutofixUnsafeReferenceWithExistingFix is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+        drawable.getOutline(null);
+                 ~~~~~~~~~~
+2 errors, 0 warnings
+        """
+
+        val expectedFix = """
+Fix for src/androidx/AutofixUnsafeReferenceWithExistingFix.java line 37: Extract to static inner class:
+@@ -37 +37
+-         view.setBackgroundTintList(new ColorStateList(null, null));
++         Api21Impl.setBackgroundTintList(view, new ColorStateList(null, null));
+Fix for src/androidx/AutofixUnsafeReferenceWithExistingFix.java line 45: Extract to static inner class:
+@@ -45 +45
+-         drawable.getOutline(null);
++         Api21Impl.getOutline(drawable, null);
+@@ -58 +58
+-     }
++     @annotation.DoNotInline
++ static void getOutline(Drawable drawable, android.graphics.Outline outline) {
++     drawable.getOutline(outline);
+@@ -60 +62
++ }
++ }
+        """
+        /* ktlint-enable max-line-length */
+
+        check(*input).expect(expected).expectFixDiffs(expectedFix)
+    }
+
     @Ignore("Ignored until the fix for b/241573146 is in the current version of studio")
     @Test
     fun `Detection and auto-fix for qualified expressions (issue 205026874)`() {
@@ -418,18 +461,17 @@
 
         /* ktlint-disable max-line-length */
         val expected = """
-src/androidx/AutofixUnsafeMethodWithQualifiedClass.java:38: Error: This call references a method added in API level 19; however, the containing class androidx.AutofixUnsafeMethodWithQualifiedClass is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
-        builder.setMediaSize(mediaSize);
-                ~~~~~~~~~~~~
+src/androidx/AutofixUnsafeMethodWithQualifiedClass.java:40: Error: This call references a method added in API level 19; however, the containing class androidx.AutofixUnsafeMethodWithQualifiedClass is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+        return builder.setMediaSize(mediaSize);
+                       ~~~~~~~~~~~~
 1 errors, 0 warnings
         """
 
         val expectedFixDiffs = """
-Fix for src/androidx/AutofixUnsafeMethodWithQualifiedClass.java line 38: Extract to static inner class:
-@@ -38 +38
--         builder.setMediaSize(mediaSize);
-+         Api19Impl.setMediaSize(builder, mediaSize);
+Fix for src/androidx/AutofixUnsafeMethodWithQualifiedClass.java line 40: Extract to static inner class:
 @@ -40 +40
++         return Api19Impl.setMediaSize(builder, mediaSize);
++     }
 + @RequiresApi(19)
 + static class Api19Impl {
 +     private Api19Impl() {
@@ -438,10 +480,9 @@
 +
 +     @DoNotInline
 +     static PrintAttributes.Builder setMediaSize(PrintAttributes.Builder builder, PrintAttributes.MediaSize mediaSize) {
-+         return builder.setMediaSize(mediaSize);
-+     }
+@@ -42 +52
 +
-@@ -41 +52
+@@ -43 +54
 + }
         """
         /* ktlint-enable max-line-length */
@@ -578,27 +619,30 @@
 
         /* ktlint-disable max-line-length */
         val expected = """
-src/androidx/AutofixUnsafeCallWithImplicitCast.java:35: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+src/androidx/AutofixUnsafeCallWithImplicitCast.java:36: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
         return new AdaptiveIconDrawable(null, null);
                ~~~~~~~~~~~~~~~~~~~~~~~~
-src/androidx/AutofixUnsafeCallWithImplicitCast.java:43: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+src/androidx/AutofixUnsafeCallWithImplicitCast.java:44: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
         return new AdaptiveIconDrawable(null, null);
                ~~~~~~~~~~~~~~~~~~~~~~~~
-src/androidx/AutofixUnsafeCallWithImplicitCast.java:51: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+src/androidx/AutofixUnsafeCallWithImplicitCast.java:52: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
         return Icon.createWithAdaptiveBitmap(null);
                     ~~~~~~~~~~~~~~~~~~~~~~~~
-src/androidx/AutofixUnsafeCallWithImplicitCast.java:59: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+src/androidx/AutofixUnsafeCallWithImplicitCast.java:60: Error: This call references a method added in API level 26; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
         return Icon.createWithAdaptiveBitmap(null);
                     ~~~~~~~~~~~~~~~~~~~~~~~~
-4 errors, 0 warnings
+src/androidx/AutofixUnsafeCallWithImplicitCast.java:68: Error: This call references a method added in API level 24; however, the containing class androidx.AutofixUnsafeCallWithImplicitCast is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
+        useStyle(new Notification.DecoratedCustomViewStyle());
+                 ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+5 errors, 0 warnings
         """
 
         val expectedFix = """
-Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 35: Extract to static inner class:
-@@ -35 +35
+Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 36: Extract to static inner class:
+@@ -36 +36
 -         return new AdaptiveIconDrawable(null, null);
 +         return Api26Impl.createAdaptiveIconDrawableReturnsDrawable(null, null);
-@@ -61 +61
+@@ -77 +77
 + @RequiresApi(26)
 + static class Api26Impl {
 +     private Api26Impl() {
@@ -610,13 +654,13 @@
 +         return new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable);
 +     }
 +
-@@ -62 +73
+@@ -78 +89
 + }
-Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 43: Extract to static inner class:
-@@ -43 +43
+Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 44: Extract to static inner class:
+@@ -44 +44
 -         return new AdaptiveIconDrawable(null, null);
 +         return Api26Impl.createAdaptiveIconDrawable(null, null);
-@@ -61 +61
+@@ -77 +77
 + @RequiresApi(26)
 + static class Api26Impl {
 +     private Api26Impl() {
@@ -628,13 +672,13 @@
 +         return new AdaptiveIconDrawable(backgroundDrawable, foregroundDrawable);
 +     }
 +
-@@ -62 +73
+@@ -78 +89
 + }
-Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 51: Extract to static inner class:
-@@ -51 +51
+Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 52: Extract to static inner class:
+@@ -52 +52
 -         return Icon.createWithAdaptiveBitmap(null);
 +         return Api26Impl.createWithAdaptiveBitmapReturnsObject(null);
-@@ -61 +61
+@@ -77 +77
 + @RequiresApi(26)
 + static class Api26Impl {
 +     private Api26Impl() {
@@ -646,13 +690,13 @@
 +         return Icon.createWithAdaptiveBitmap(bits);
 +     }
 +
-@@ -62 +73
+@@ -78 +89
 + }
-Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 59: Extract to static inner class:
-@@ -59 +59
+Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 60: Extract to static inner class:
+@@ -60 +60
 -         return Icon.createWithAdaptiveBitmap(null);
 +         return Api26Impl.createWithAdaptiveBitmap(null);
-@@ -61 +61
+@@ -77 +77
 + @RequiresApi(26)
 + static class Api26Impl {
 +     private Api26Impl() {
@@ -664,7 +708,25 @@
 +         return Icon.createWithAdaptiveBitmap(bits);
 +     }
 +
-@@ -62 +73
+@@ -78 +89
++ }
+Fix for src/androidx/AutofixUnsafeCallWithImplicitCast.java line 68: Extract to static inner class:
+@@ -68 +68
+-         useStyle(new Notification.DecoratedCustomViewStyle());
++         useStyle(Api24Impl.createDecoratedCustomViewStyleReturnsStyle());
+@@ -77 +77
++ @RequiresApi(24)
++ static class Api24Impl {
++     private Api24Impl() {
++         // This class is not instantiable.
++     }
++
++     @DoNotInline
++     static Notification.Style createDecoratedCustomViewStyleReturnsStyle() {
++         return new Notification.DecoratedCustomViewStyle();
++     }
++
+@@ -78 +89
 + }
         """
         /* ktlint-enable max-line-length */
@@ -682,17 +744,16 @@
         /* ktlint-disable max-line-length */
         val expected = """
 src/androidx/AutofixUnsafeConstructorQualifiedClass.java:32: Error: This call references a method added in API level 24; however, the containing class androidx.AutofixUnsafeConstructorQualifiedClass is reachable from earlier API levels and will fail run-time class verification. [ClassVerificationFailure]
-        new Notification.DecoratedCustomViewStyle();
-        ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
+        return new Notification.DecoratedCustomViewStyle();
+               ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
 1 errors, 0 warnings
         """
 
         val expectedFix = """
 Fix for src/androidx/AutofixUnsafeConstructorQualifiedClass.java line 32: Extract to static inner class:
 @@ -32 +32
--         new Notification.DecoratedCustomViewStyle();
-+         Api24Impl.createDecoratedCustomViewStyle();
-@@ -34 +34
++         return Api24Impl.createDecoratedCustomViewStyle();
++     }
 + @RequiresApi(24)
 + static class Api24Impl {
 +     private Api24Impl() {
@@ -701,8 +762,7 @@
 +
 +     @DoNotInline
 +     static Notification.DecoratedCustomViewStyle createDecoratedCustomViewStyle() {
-+         return new Notification.DecoratedCustomViewStyle();
-+     }
+@@ -34 +44
 +
 @@ -35 +46
 + }
diff --git a/lint-checks/src/test/java/androidx/build/lint/Stubs.kt b/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
index b48f7d9..3feb926 100644
--- a/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
+++ b/lint-checks/src/test/java/androidx/build/lint/Stubs.kt
@@ -261,6 +261,23 @@
 annotation class Ignore
             """
         )
+
+        val DoNotInline = TestFiles.java(
+            """
+package androidx.annotation;
+
+import static java.lang.annotation.ElementType.METHOD;
+import static java.lang.annotation.RetentionPolicy.CLASS;
+
+import java.lang.annotation.Retention;
+import java.lang.annotation.Target;
+
+@Retention(CLASS)
+@Target({METHOD})
+public @interface DoNotInline {
+}
+            """
+        )
         /* ktlint-enable max-line-length */
     }
 }
diff --git a/navigation/navigation-common/api/current.ignore b/navigation/navigation-common/api/current.ignore
deleted file mode 100644
index 112574f..0000000
--- a/navigation/navigation-common/api/current.ignore
+++ /dev/null
@@ -1,45 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.AnimBuilder#setEnter(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setEnter
-ParameterNameChange: androidx.navigation.AnimBuilder#setExit(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setExit
-ParameterNameChange: androidx.navigation.AnimBuilder#setPopEnter(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setPopEnter
-ParameterNameChange: androidx.navigation.AnimBuilder#setPopExit(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setPopExit
-ParameterNameChange: androidx.navigation.NavAction#setDefaultArguments(android.os.Bundle) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavAction.setDefaultArguments
-ParameterNameChange: androidx.navigation.NavAction#setNavOptions(androidx.navigation.NavOptions) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavAction.setNavOptions
-ParameterNameChange: androidx.navigation.NavActionBuilder#setDestinationId(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavActionBuilder.setDestinationId
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setDefaultValue(Object) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setDefaultValue
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setNullable(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setNullable
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setType(androidx.navigation.NavType<?>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setType
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setAction
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setMimeType(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setMimeType
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setUriPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setUriPattern
-ParameterNameChange: androidx.navigation.NavDestination#setId(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setId
-ParameterNameChange: androidx.navigation.NavDestination#setLabel(CharSequence) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setLabel
-ParameterNameChange: androidx.navigation.NavDestination#setRoute(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setRoute
-ParameterNameChange: androidx.navigation.NavDestinationBuilder#setLabel(CharSequence) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestinationBuilder.setLabel
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setLaunchSingleTop(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setLaunchSingleTop
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setPopUpTo(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setPopUpTo
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setRestoreState(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setRestoreState
-ParameterNameChange: androidx.navigation.PopUpToBuilder#setInclusive(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.PopUpToBuilder.setInclusive
-ParameterNameChange: androidx.navigation.PopUpToBuilder#setSaveState(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.PopUpToBuilder.setSaveState
diff --git a/navigation/navigation-common/api/restricted_current.ignore b/navigation/navigation-common/api/restricted_current.ignore
deleted file mode 100644
index 112574f..0000000
--- a/navigation/navigation-common/api/restricted_current.ignore
+++ /dev/null
@@ -1,45 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.AnimBuilder#setEnter(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setEnter
-ParameterNameChange: androidx.navigation.AnimBuilder#setExit(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setExit
-ParameterNameChange: androidx.navigation.AnimBuilder#setPopEnter(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setPopEnter
-ParameterNameChange: androidx.navigation.AnimBuilder#setPopExit(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.AnimBuilder.setPopExit
-ParameterNameChange: androidx.navigation.NavAction#setDefaultArguments(android.os.Bundle) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavAction.setDefaultArguments
-ParameterNameChange: androidx.navigation.NavAction#setNavOptions(androidx.navigation.NavOptions) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavAction.setNavOptions
-ParameterNameChange: androidx.navigation.NavActionBuilder#setDestinationId(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavActionBuilder.setDestinationId
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setDefaultValue(Object) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setDefaultValue
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setNullable(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setNullable
-ParameterNameChange: androidx.navigation.NavArgumentBuilder#setType(androidx.navigation.NavType<?>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavArgumentBuilder.setType
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setAction
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setMimeType(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setMimeType
-ParameterNameChange: androidx.navigation.NavDeepLinkDslBuilder#setUriPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDeepLinkDslBuilder.setUriPattern
-ParameterNameChange: androidx.navigation.NavDestination#setId(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setId
-ParameterNameChange: androidx.navigation.NavDestination#setLabel(CharSequence) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setLabel
-ParameterNameChange: androidx.navigation.NavDestination#setRoute(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestination.setRoute
-ParameterNameChange: androidx.navigation.NavDestinationBuilder#setLabel(CharSequence) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavDestinationBuilder.setLabel
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setLaunchSingleTop(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setLaunchSingleTop
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setPopUpTo(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setPopUpTo
-ParameterNameChange: androidx.navigation.NavOptionsBuilder#setRestoreState(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavOptionsBuilder.setRestoreState
-ParameterNameChange: androidx.navigation.PopUpToBuilder#setInclusive(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.PopUpToBuilder.setInclusive
-ParameterNameChange: androidx.navigation.PopUpToBuilder#setSaveState(boolean) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.PopUpToBuilder.setSaveState
diff --git a/navigation/navigation-dynamic-features-fragment/api/current.ignore b/navigation/navigation-dynamic-features-fragment/api/current.ignore
deleted file mode 100644
index 22bc6c9..0000000
--- a/navigation/navigation-dynamic-features-fragment/api/current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder.setModuleName
diff --git a/navigation/navigation-dynamic-features-fragment/api/restricted_current.ignore b/navigation/navigation-dynamic-features-fragment/api/restricted_current.ignore
deleted file mode 100644
index 22bc6c9..0000000
--- a/navigation/navigation-dynamic-features-fragment/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigator.Destination.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.fragment.DynamicFragmentNavigatorDestinationBuilder.setModuleName
diff --git a/navigation/navigation-dynamic-features-runtime/api/current.ignore b/navigation/navigation-dynamic-features-runtime/api/current.ignore
deleted file mode 100644
index ae02ffb..0000000
--- a/navigation/navigation-dynamic-features-runtime/api/current.ignore
+++ /dev/null
@@ -1,33 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setAction
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setActivityClassName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setActivityClassName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setData(android.net.Uri) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setData
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setDataPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setDataPattern
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setTargetPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setTargetPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph#setProgressDestination(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph.setProgressDestination
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setGraphPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setGraphPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setGraphResourceName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setGraphResourceName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder#setGraphPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder.setGraphPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setProgressDestination(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setProgressDestination
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setProgressDestinationRoute(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setProgressDestinationRoute
diff --git a/navigation/navigation-dynamic-features-runtime/api/restricted_current.ignore b/navigation/navigation-dynamic-features-runtime/api/restricted_current.ignore
deleted file mode 100644
index ae02ffb..0000000
--- a/navigation/navigation-dynamic-features-runtime/api/restricted_current.ignore
+++ /dev/null
@@ -1,33 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigator.Destination.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setAction
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setActivityClassName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setActivityClassName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setData(android.net.Uri) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setData
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setDataPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setDataPattern
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder#setTargetPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicActivityNavigatorDestinationBuilder.setTargetPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph#setProgressDestination(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicGraphNavigator.DynamicNavGraph.setProgressDestination
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setGraphPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setGraphPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setGraphResourceName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setGraphResourceName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeGraphNavigator.DynamicIncludeNavGraph.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder#setGraphPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicIncludeNavGraphBuilder.setGraphPackage
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setModuleName(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setModuleName
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setProgressDestination(int) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setProgressDestination
-ParameterNameChange: androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder#setProgressDestinationRoute(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.dynamicfeatures.DynamicNavGraphBuilder.setProgressDestinationRoute
diff --git a/navigation/navigation-fragment/build.gradle b/navigation/navigation-fragment/build.gradle
index 12ced4a..298cc9d 100644
--- a/navigation/navigation-fragment/build.gradle
+++ b/navigation/navigation-fragment/build.gradle
@@ -23,7 +23,7 @@
 }
 
 dependencies {
-    api("androidx.fragment:fragment-ktx:1.5.1")
+    api("androidx.fragment:fragment-ktx:1.5.2")
     api(project(":navigation:navigation-runtime"))
     api("androidx.slidingpanelayout:slidingpanelayout:1.2.0")
     api(libs.kotlinStdlib)
diff --git a/navigation/navigation-runtime/api/current.ignore b/navigation/navigation-runtime/api/current.ignore
deleted file mode 100644
index 881cddb..0000000
--- a/navigation/navigation-runtime/api/current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setAction
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setActivityClass
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setData(android.net.Uri) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setData
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setDataPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setDataPattern
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setTargetPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setTargetPackage
-ParameterNameChange: androidx.navigation.NavController#setGraph(androidx.navigation.NavGraph) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavController.setGraph
diff --git a/navigation/navigation-runtime/api/restricted_current.ignore b/navigation/navigation-runtime/api/restricted_current.ignore
deleted file mode 100644
index 881cddb..0000000
--- a/navigation/navigation-runtime/api/restricted_current.ignore
+++ /dev/null
@@ -1,13 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setAction(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setAction
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setActivityClass(kotlin.reflect.KClass<? extends android.app.Activity>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setActivityClass
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setData(android.net.Uri) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setData
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setDataPattern(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setDataPattern
-ParameterNameChange: androidx.navigation.ActivityNavigatorDestinationBuilder#setTargetPackage(String) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.ActivityNavigatorDestinationBuilder.setTargetPackage
-ParameterNameChange: androidx.navigation.NavController#setGraph(androidx.navigation.NavGraph) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.navigation.NavController.setGraph
diff --git a/paging/paging-common/src/test/kotlin/androidx/paging/CachingTest.kt b/paging/paging-common/src/test/kotlin/androidx/paging/CachingTest.kt
index bd36cf3..151431c 100644
--- a/paging/paging-common/src/test/kotlin/androidx/paging/CachingTest.kt
+++ b/paging/paging-common/src/test/kotlin/androidx/paging/CachingTest.kt
@@ -13,9 +13,6 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
-
-@file:Suppress("DEPRECATION") // b/220884818
-
 package androidx.paging
 
 import androidx.paging.ActiveFlowTracker.FlowType
@@ -39,49 +36,25 @@
 import kotlinx.coroutines.flow.toList
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.runBlocking
-import kotlinx.coroutines.test.TestCoroutineScope
-import kotlinx.coroutines.test.runBlockingTest
 import kotlinx.coroutines.yield
-import org.junit.After
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
 import java.util.concurrent.atomic.AtomicInteger
+import kotlinx.coroutines.test.TestScope
+import kotlinx.coroutines.test.UnconfinedTestDispatcher
 import kotlinx.coroutines.test.runCurrent
+import kotlinx.coroutines.test.runTest
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(JUnit4::class)
 class CachingTest {
-    private val testScope = TestCoroutineScope()
-
     private val tracker = ActiveFlowTrackerImpl()
 
-    @After
-    fun checkResources() {
-        try {
-            testScope.cleanupTestCoroutines()
-        } catch (e: AssertionError) {
-            if (e.message?.contains("Test finished with active jobs") != true) {
-                throw e
-            }
-        }
-    }
-
-    // It's a workaround for https://github.com/Kotlin/kotlinx.coroutines/issues/1531
-    private fun TestCoroutineScope.wrapRunTest(
-        testBody: suspend TestCoroutineScope.() -> Unit
-    ) {
-        try {
-            this.runBlockingTest(testBody)
-        } catch (e: AssertionError) {
-            if (e.message?.contains("Test finished with active jobs") != true) {
-                throw e
-            }
-        }
-    }
+    private val testScope = TestScope(UnconfinedTestDispatcher())
 
     @Test
-    fun noSharing() = testScope.runBlockingTest {
+    fun noSharing() = testScope.runTest {
         val pageFlow = buildPageFlow()
         val firstCollect = pageFlow.collectItemsUntilSize(6)
         val secondCollect = pageFlow.collectItemsUntilSize(9)
@@ -106,8 +79,8 @@
     }
 
     @Test
-    fun cached() = testScope.wrapRunTest {
-        val pageFlow = buildPageFlow().cachedIn(testScope, tracker)
+    fun cached() = testScope.runTest {
+        val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker)
         val firstCollect = pageFlow.collectItemsUntilSize(6)
         val secondCollect = pageFlow.collectItemsUntilSize(9)
         assertThat(firstCollect).isEqualTo(
@@ -131,14 +104,14 @@
     }
 
     @Test
-    fun cached_afterMapping() = testScope.wrapRunTest {
+    fun cached_afterMapping() = testScope.runTest {
         var mappingCnt = 0
         val pageFlow = buildPageFlow().map { pagingData ->
             val mappingIndex = mappingCnt++
             pagingData.map {
                 it.copy(metadata = mappingIndex.toString())
             }
-        }.cachedIn(testScope, tracker)
+        }.cachedIn(backgroundScope, tracker)
         val firstCollect = pageFlow.collectItemsUntilSize(6)
         val secondCollect = pageFlow.collectItemsUntilSize(9)
         assertThat(firstCollect).isEqualTo(
@@ -166,9 +139,9 @@
     }
 
     @Test
-    fun cached_beforeMapping() = testScope.wrapRunTest {
+    fun cached_beforeMapping() = testScope.runTest {
         var mappingCnt = 0
-        val pageFlow = buildPageFlow().cachedIn(testScope, tracker).map { pagingData ->
+        val pageFlow = buildPageFlow().cachedIn(backgroundScope, tracker).map { pagingData ->
             val mappingIndex = mappingCnt++
             pagingData.map {
                 it.copy(metadata = mappingIndex.toString())
@@ -201,14 +174,14 @@
     }
 
     @Test
-    fun cached_afterMapping_withMoreMappingAfterwards() = testScope.wrapRunTest {
+    fun cached_afterMapping_withMoreMappingAfterwards() = testScope.runTest {
         var mappingCnt = 0
         val pageFlow = buildPageFlow().map { pagingData ->
             val mappingIndex = mappingCnt++
             pagingData.map {
                 it.copy(metadata = mappingIndex.toString())
             }
-        }.cachedIn(testScope, tracker).map { pagingData ->
+        }.cachedIn(backgroundScope, tracker).map { pagingData ->
             val mappingIndex = mappingCnt++
             pagingData.map {
                 it.copy(metadata = "${it.metadata}_$mappingIndex")
@@ -285,10 +258,10 @@
     }
 
     @Test
-    fun cachedWithPassiveCollector() = testScope.wrapRunTest {
-        val flow = buildPageFlow().cachedIn(testScope, tracker)
+    fun cachedWithPassiveCollector() = testScope.runTest {
+        val flow = buildPageFlow().cachedIn(backgroundScope, tracker)
         val passive = ItemCollector(flow)
-        passive.collectPassivelyIn(testScope)
+        passive.collectPassivelyIn(backgroundScope)
         testScope.runCurrent()
         // collecting on the paged source will trigger initial page
         assertThat(passive.items()).isEqualTo(
@@ -310,7 +283,7 @@
         assertThat(flow.collectItemsUntilSize(9)).isEqualTo(firstList)
         assertThat(passive.items()).isEqualTo(firstList)
         val passive2 = ItemCollector(flow)
-        passive2.collectPassivelyIn(testScope)
+        passive2.collectPassivelyIn(backgroundScope)
         testScope.runCurrent()
         // a new passive one should receive all existing items immediately
         assertThat(passive2.items()).isEqualTo(firstList)
@@ -334,9 +307,9 @@
      * invalidations create new PagingData BUT a new collector only sees the latest one.
      */
     @Test
-    public fun unusedPagingDataIsNeverCollectedByNewDownstream(): Unit = testScope.wrapRunTest {
+    public fun unusedPagingDataIsNeverCollectedByNewDownstream(): Unit = testScope.runTest {
         val factory = StringPagingSource.VersionedFactory()
-        val flow = buildPageFlow(factory).cachedIn(testScope, tracker)
+        val flow = buildPageFlow(factory).cachedIn(backgroundScope, tracker)
         val collector = ItemCollector(flow)
         val job = SupervisorJob()
         val subScope = CoroutineScope(coroutineContext + job)
@@ -364,7 +337,7 @@
         // create another collector from shared, should only receive 1 paging data and that
         // should be the latest because previous PagingData is invalidated
         val collector2 = ItemCollector(flow)
-        collector2.collectPassivelyIn(testScope)
+        collector2.collectPassivelyIn(backgroundScope)
         testScope.runCurrent()
         assertThat(collector2.items()).isEqualTo(
             buildItems(
diff --git a/paging/paging-runtime/api/current.ignore b/paging/paging-runtime/api/current.ignore
index 2bd7f73..41c88d3 100644
--- a/paging/paging-runtime/api/current.ignore
+++ b/paging/paging-runtime/api/current.ignore
@@ -5,13 +5,3 @@
     Attempted to remove parameter name from parameter arg1 in androidx.paging.LoadStateAdapter.setLoadState
 ParameterNameChange: androidx.paging.PagingDataAdapter#submitData(androidx.paging.PagingData<T>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.paging.PagingDataAdapter.submitData
-
-
-RemovedMethod: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,androidx.recyclerview.widget.ListUpdateCallback,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,androidx.recyclerview.widget.ListUpdateCallback,kotlinx.coroutines.CoroutineDispatcher,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.PagingDataAdapter#PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.PagingDataAdapter#PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,kotlinx.coroutines.CoroutineDispatcher,kotlinx.coroutines.CoroutineDispatcher)
diff --git a/paging/paging-runtime/api/restricted_current.ignore b/paging/paging-runtime/api/restricted_current.ignore
index 2bd7f73..41c88d3 100644
--- a/paging/paging-runtime/api/restricted_current.ignore
+++ b/paging/paging-runtime/api/restricted_current.ignore
@@ -5,13 +5,3 @@
     Attempted to remove parameter name from parameter arg1 in androidx.paging.LoadStateAdapter.setLoadState
 ParameterNameChange: androidx.paging.PagingDataAdapter#submitData(androidx.paging.PagingData<T>, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.paging.PagingDataAdapter.submitData
-
-
-RemovedMethod: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,androidx.recyclerview.widget.ListUpdateCallback,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.AsyncPagingDataDiffer#AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, androidx.recyclerview.widget.ListUpdateCallback, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.AsyncPagingDataDiffer(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,androidx.recyclerview.widget.ListUpdateCallback,kotlinx.coroutines.CoroutineDispatcher,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.PagingDataAdapter#PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,kotlinx.coroutines.CoroutineDispatcher)
-RemovedMethod: androidx.paging.PagingDataAdapter#PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>, kotlinx.coroutines.CoroutineDispatcher, kotlinx.coroutines.CoroutineDispatcher):
-    Removed constructor androidx.paging.PagingDataAdapter(androidx.recyclerview.widget.DiffUtil.ItemCallback<T>,kotlinx.coroutines.CoroutineDispatcher,kotlinx.coroutines.CoroutineDispatcher)
diff --git a/paging/paging-testing/api/current.txt b/paging/paging-testing/api/current.txt
new file mode 100644
index 0000000..84fd8f6
--- /dev/null
+++ b/paging/paging-testing/api/current.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package  {
+
+  public final class TestPager<Key, Value> {
+    ctor public TestPager(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingConfig config);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult.Page<Key,Value>>);
+    method public suspend Object? getPages(kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
+    method public suspend Object? refresh(optional Key? initialKey, optional kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+  }
+
+}
+
diff --git a/paging/paging-testing/api/public_plus_experimental_current.txt b/paging/paging-testing/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..84fd8f6
--- /dev/null
+++ b/paging/paging-testing/api/public_plus_experimental_current.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package  {
+
+  public final class TestPager<Key, Value> {
+    ctor public TestPager(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingConfig config);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult.Page<Key,Value>>);
+    method public suspend Object? getPages(kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
+    method public suspend Object? refresh(optional Key? initialKey, optional kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+  }
+
+}
+
diff --git a/paging/paging-testing/api/res-current.txt b/paging/paging-testing/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/paging/paging-testing/api/res-current.txt
diff --git a/paging/paging-testing/api/restricted_current.txt b/paging/paging-testing/api/restricted_current.txt
new file mode 100644
index 0000000..84fd8f6
--- /dev/null
+++ b/paging/paging-testing/api/restricted_current.txt
@@ -0,0 +1,12 @@
+// Signature format: 4.0
+package  {
+
+  public final class TestPager<Key, Value> {
+    ctor public TestPager(androidx.paging.PagingSource<Key,Value> pagingSource, androidx.paging.PagingConfig config);
+    method public suspend Object? getLastLoadedPage(kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult.Page<Key,Value>>);
+    method public suspend Object? getPages(kotlin.coroutines.Continuation<? super java.util.List<? extends androidx.paging.PagingSource.LoadResult.Page<Key,Value>>>);
+    method public suspend Object? refresh(optional Key? initialKey, optional kotlin.coroutines.Continuation<? super androidx.paging.PagingSource.LoadResult<Key,Value>>);
+  }
+
+}
+
diff --git a/paging/paging-testing/build.gradle b/paging/paging-testing/build.gradle
new file mode 100644
index 0000000..cfe7df2
--- /dev/null
+++ b/paging/paging-testing/build.gradle
@@ -0,0 +1,48 @@
+/*
+ * Copyright (C) 2022 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+import androidx.build.LibraryType
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("org.jetbrains.kotlin.android")
+}
+
+dependencies {
+    api(libs.kotlinStdlib)
+    implementation(project(":paging:paging-common"))
+
+    testImplementation(libs.junit)
+    testImplementation(libs.kotlinCoroutinesTest)
+    testImplementation(project(":internal-testutils-paging"))
+    testImplementation(libs.kotlinTest)
+    testImplementation(libs.truth)
+}
+
+androidx {
+    name = "androidx.paging:paging-testing"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenGroup = LibraryGroups.PAGING
+    inceptionYear = "2022"
+    description = "Test artifact for Paging implementation"
+    publish = Publish.SNAPSHOT_ONLY
+}
+
+android {
+    namespace "androidx.paging.testing"
+}
diff --git a/paging/paging-testing/src/main/java/androidx/paging/TestPager.kt b/paging/paging-testing/src/main/java/androidx/paging/TestPager.kt
new file mode 100644
index 0000000..982a264
--- /dev/null
+++ b/paging/paging-testing/src/main/java/androidx/paging/TestPager.kt
@@ -0,0 +1,121 @@
+/*
+ * 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.
+ */
+
+import androidx.paging.LoadType
+import androidx.paging.PagingConfig
+import androidx.paging.PagingSource
+import androidx.paging.PagingSource.LoadParams
+import androidx.paging.PagingSource.LoadResult
+import androidx.paging.Pager
+import java.util.concurrent.atomic.AtomicBoolean
+import kotlinx.coroutines.sync.Mutex
+import kotlinx.coroutines.sync.withLock
+
+/**
+ * A fake [Pager] class to simulate how a real Pager and UI would load data from a PagingSource.
+ *
+ * As Paging's first load is always of type [LoadType.REFRESH], the first load operation of
+ * the [TestPager] must be a call to [refresh].
+ *
+ * This class only supports loads from a single instance of PagingSource. To simulate
+ * multi-generational Paging behavior, you must create a new [TestPager] by supplying a
+ * new instance of [PagingSource].
+ *
+ * @param pagingSource the [PagingSource] to load data from.
+ * @param config the [PagingConfig] to configure this TestPager's loading behavior.
+ */
+public class TestPager<Key : Any, Value : Any>(
+    private val pagingSource: PagingSource<Key, Value>,
+    private val config: PagingConfig,
+) {
+    private val hasRefreshed = AtomicBoolean(false)
+
+    private val lock = Mutex()
+
+    private var nextKey: Key? = null
+    private var prevKey: Key? = null
+    private val pages = ArrayDeque<LoadResult.Page<Key, Value>>()
+
+    // TODO add instruction that refresh() must be called before either append() or prepend()
+    /**
+     * Performs a load of [LoadType.REFRESH] on the PagingSource.
+     *
+     * If initialKey != null, refresh will start loading from the supplied key.
+     *
+     * Since Paging's first load is always of [LoadType.REFRESH], this method must be the very
+     * first load operation to be called on the TestPager. For example, you can call
+     * [getLastLoadedPage] before any load operations.
+     *
+     * Returns the LoadResult upon refresh on the [PagingSource].
+     *
+     * @param initialKey the [Key] to start loading data from on initial refresh.
+     *
+     * @throws IllegalStateException TestPager does not support multi-generational paging behavior.
+     * As such, multiple calls to refresh() on this TestPager is illegal. The [PagingSource] passed
+     * in to this [TestPager] will also be invalidated to prevent reuse of this pager for loads.
+     * However, other [TestPager] methods that does not invoke loads can still be called,
+     * such as [getLastLoadedPage].
+     */
+    public suspend fun refresh(initialKey: Key? = null): LoadResult<Key, Value> {
+        ensureValidPagingSource()
+        if (!hasRefreshed.compareAndSet(false, true)) {
+            pagingSource.invalidate()
+            throw IllegalStateException("TestPager does not support multi-generational access " +
+                "and refresh() can only be called once per TestPager. To start a new generation," +
+                "create a new TestPager with a new PagingSource.")
+        }
+
+        return lock.withLock {
+            pagingSource.load(
+                LoadParams.Refresh(initialKey, config.initialLoadSize, config.enablePlaceholders)
+            ).also { result ->
+                if (result is LoadResult.Page) {
+                    pages.addLast(result)
+                    nextKey = result.nextKey
+                    prevKey = result.prevKey
+                }
+            }
+        }
+    }
+
+    /**
+     * Returns the most recent [LoadResult.Page] loaded from the [PagingSource]. Null if
+     * no pages have been returned from [PagingSource]. For example, if PagingSource has
+     * only returned [LoadResult.Error] or [LoadResult.Invalid].
+     */
+    public suspend fun getLastLoadedPage(): LoadResult.Page<Key, Value>? {
+        return lock.withLock {
+            pages.lastOrNull()
+        }
+    }
+
+    /**
+     * Returns the current list of [LoadResult.Page] loaded so far from the [PagingSource].
+     */
+    public suspend fun getPages(): List<LoadResult.Page<Key, Value>> {
+        return lock.withLock {
+            pages.toList()
+        }
+    }
+
+    private fun ensureValidPagingSource() {
+        check(!pagingSource.invalid) {
+            "This TestPager cannot perform further loads as PagingSource $pagingSource has " +
+                "been invalidated. If the PagingSource is expected to be invalid, you can " +
+                "continue to load by creating a new TestPager with a new PagingSource."
+        }
+    }
+}
\ No newline at end of file
diff --git a/paging/paging-testing/src/test/kotlin/androidx/paging/TestPagerTest.kt b/paging/paging-testing/src/test/kotlin/androidx/paging/TestPagerTest.kt
new file mode 100644
index 0000000..50d75d9
--- /dev/null
+++ b/paging/paging-testing/src/test/kotlin/androidx/paging/TestPagerTest.kt
@@ -0,0 +1,204 @@
+/*
+ * 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.paging
+
+import androidx.paging.PagingSource.LoadResult
+import TestPager
+import com.google.common.truth.Truth.assertThat
+import kotlin.test.assertFailsWith
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.test.runTest
+import org.junit.Assert.assertTrue
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.JUnit4
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(JUnit4::class)
+class TestPagerTest {
+
+    @Test
+    fun refresh_returnPage() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            val result = pager.run {
+                refresh()
+            } as LoadResult.Page
+
+            assertThat(result.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
+        }
+    }
+
+    @Test
+    fun refresh_withInitialKey() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            val result = pager.run {
+                refresh(50)
+            } as LoadResult.Page
+
+            assertThat(result.data).containsExactlyElementsIn(listOf(50, 51, 52, 53, 54)).inOrder()
+        }
+    }
+
+    @Test
+    fun refresh_returnError() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            source.errorNextLoad = true
+            val result = pager.run {
+                refresh()
+            }
+            assertTrue(result is LoadResult.Error)
+
+            val page = pager.run {
+                getLastLoadedPage()
+            }
+            assertThat(page).isNull()
+        }
+    }
+
+    @Test
+    fun refresh_returnInvalid() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            source.nextLoadResult = LoadResult.Invalid()
+            val result = pager.run {
+                refresh()
+            }
+            assertTrue(result is LoadResult.Invalid)
+
+            val page = pager.run {
+                getLastLoadedPage()
+            }
+            assertThat(page).isNull()
+        }
+    }
+
+    @Test
+    fun refresh_invalidPagingSource() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            source.invalidate()
+            assertTrue(source.invalid)
+            assertFailsWith<IllegalStateException> {
+                pager.run {
+                    refresh()
+                }
+            }
+        }
+    }
+
+    @Test
+    fun refresh_getlastPage() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            val page: LoadResult.Page<Int, Int>? = pager.run {
+                refresh()
+                getLastLoadedPage()
+            }
+            assertThat(page).isNotNull()
+            assertThat(page?.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
+        }
+    }
+
+    @Test
+    fun refresh_getPages() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            val pages = pager.run {
+                refresh()
+                getPages()
+            }
+            assertThat(pages).hasSize(1)
+            assertThat(pages).containsExactlyElementsIn(
+                listOf(
+                    LoadResult.Page(
+                        data = listOf(0, 1, 2, 3, 4),
+                        prevKey = null,
+                        nextKey = 5,
+                        itemsBefore = 0,
+                        itemsAfter = 95
+                    )
+                )
+            ).inOrder()
+        }
+    }
+
+    @Test
+    fun multipleRefresh_onSinglePager_throws() {
+        val source = TestPagingSource()
+        val pager = TestPager(source, CONFIG)
+
+        runTest {
+            pager.run {
+                // second refresh should throw since testPager is not mult-generational
+                assertFailsWith<IllegalStateException> {
+                    refresh()
+                    refresh()
+                }
+            }
+            assertTrue(source.invalid)
+            // the first refresh should still have succeeded
+            assertThat(pager.run {
+                getPages()
+            }).hasSize(1)
+        }
+    }
+
+    @Test
+    fun multipleRefresh_onMultiplePagers() = runTest {
+        val source1 = TestPagingSource()
+        val pager1 = TestPager(source1, CONFIG)
+
+        // first gen
+        val result1 = pager1.run {
+            refresh()
+        } as LoadResult.Page
+
+        assertThat(result1.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
+
+        // second gen
+        val source2 = TestPagingSource()
+        val pager2 = TestPager(source2, CONFIG)
+
+        val result2 = pager2.run {
+            refresh()
+        } as LoadResult.Page
+
+        assertThat(result2.data).containsExactlyElementsIn(listOf(0, 1, 2, 3, 4)).inOrder()
+    }
+
+    private val CONFIG = PagingConfig(
+        pageSize = 3,
+        initialLoadSize = 5,
+    )
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/build.gradle b/privacysandbox/tools/tools-apicompiler/build.gradle
index fea0b81..0ccdf39 100644
--- a/privacysandbox/tools/tools-apicompiler/build.gradle
+++ b/privacysandbox/tools/tools-apicompiler/build.gradle
@@ -15,6 +15,8 @@
  */
 
 import androidx.build.LibraryType
+import androidx.build.SdkHelperKt
+import androidx.build.SupportConfig
 
 plugins {
     id("AndroidXPlugin")
@@ -24,12 +26,28 @@
 dependencies {
     api(libs.kotlinStdlib)
     implementation(libs.kspApi)
+    implementation(libs.kotlinPoet)
     implementation project(path: ':privacysandbox:tools:tools')
     implementation project(path: ':privacysandbox:tools:tools-core')
 
     testImplementation(project(":room:room-compiler-processing-testing"))
     testImplementation(libs.junit)
     testImplementation(libs.truth)
+    // Include android jar for compilation of generated sources.
+    testImplementation(fileTree(
+            dir: "${SdkHelperKt.getSdkPath(project)}/platforms/$SupportConfig.COMPILE_SDK_VERSION/",
+            include: "android.jar"
+    ))
+    // Get AIDL compiler path and pass it to tests for code generation.
+    def aidlCompilerPath = "${SdkHelperKt.getSdkPath(project)}/build-tools/${SupportConfig.BUILD_TOOLS_VERSION}/aidl"
+    test {
+        inputs.files(aidlCompilerPath)
+                .withPropertyName("aidl_compiler_path")
+                .withPathSensitivity(PathSensitivity.NAME_ONLY)
+        doFirst {
+            systemProperty "aidl_compiler_path", aidlCompilerPath
+        }
+    }
 }
 
 androidx {
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompiler.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompiler.kt
index 3423e89..2b1d466 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompiler.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompiler.kt
@@ -16,42 +16,54 @@
 
 package androidx.privacysandbox.tools.apicompiler
 
+import androidx.privacysandbox.tools.apicompiler.generator.SdkCodeGenerator
 import androidx.privacysandbox.tools.apicompiler.parser.ApiParser
 import com.google.devtools.ksp.processing.CodeGenerator
-import com.google.devtools.ksp.processing.Dependencies
 import com.google.devtools.ksp.processing.KSPLogger
 import com.google.devtools.ksp.processing.Resolver
 import com.google.devtools.ksp.processing.SymbolProcessor
 import com.google.devtools.ksp.processing.SymbolProcessorEnvironment
 import com.google.devtools.ksp.processing.SymbolProcessorProvider
 import com.google.devtools.ksp.symbol.KSAnnotated
+import java.nio.file.Paths
 
 class PrivacySandboxKspCompiler(
     private val logger: KSPLogger,
-    private val codeGenerator: CodeGenerator
+    private val codeGenerator: CodeGenerator,
+    private val options: Map<String, String>,
 ) :
     SymbolProcessor {
+    companion object {
+        const val AIDL_COMPILER_PATH_OPTIONS_KEY = "aidl_compiler_path"
+    }
+
     var invoked = false
 
     override fun process(resolver: Resolver): List<KSAnnotated> {
         if (invoked) {
             return emptyList()
         }
-        ApiParser(resolver, logger).parseApi()
-        // TODO: remove once actual conde generation is in place.
-        createTestFile()
         invoked = true
-        return emptyList()
-    }
 
-    private fun createTestFile() {
-        val testFile = codeGenerator.createNewFile(Dependencies(false), "", "TestFile", "txt")
-        testFile.write("TestFile".toByteArray())
+        val path = options[AIDL_COMPILER_PATH_OPTIONS_KEY]?.let(Paths::get)
+        if (path == null) {
+            logger.error("KSP argument '$AIDL_COMPILER_PATH_OPTIONS_KEY' was not set.")
+            return emptyList()
+        }
+
+        val parsedApi = ApiParser(resolver, logger).parseApi()
+
+        SdkCodeGenerator(codeGenerator, parsedApi, path).generate()
+        return emptyList()
     }
 
     class Provider : SymbolProcessorProvider {
         override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
-            return PrivacySandboxKspCompiler(environment.logger, environment.codeGenerator)
+            return PrivacySandboxKspCompiler(
+                environment.logger,
+                environment.codeGenerator,
+                environment.options
+            )
         }
     }
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
new file mode 100644
index 0000000..5f845f9
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/AbstractSdkProviderGenerator.kt
@@ -0,0 +1,111 @@
+/*
+ * 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.privacysandbox.tools.apicompiler.generator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.ParsedApi
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.TypeSpec
+
+class AbstractSdkProviderGenerator(
+    private val codeGenerator: CodeGenerator,
+    private val api: ParsedApi,
+) {
+    companion object {
+        private val SANDBOXED_SDK_PROVIDER_CLASS =
+            ClassName("android.app.sdksandbox", "SandboxedSdkProvider")
+        private val DATA_RECEIVED_CALLBACK_CLASS =
+            ClassName("android.app.sdksandbox", "SandboxedSdkProvider", "DataReceivedCallback")
+        private val SANDBOXED_SDK_CLASS =
+            ClassName("android.app.sdksandbox", "SandboxedSdk")
+        private val SANDBOXED_SDK_CONTEXT_CLASS =
+            ClassName("android.app.sdksandbox", "SandboxedSdkContext")
+        private val CONTEXT_CLASS = ClassName("android.content", "Context")
+        private val BUNDLE_CLASS = ClassName("android.os", "Bundle")
+        private val VIEW_CLASS = ClassName("android.view", "View")
+    }
+
+    fun generate() {
+        val packageName = service().packageName
+        val className = "AbstractSandboxedSdkProvider"
+        val classSpec =
+            TypeSpec.classBuilder(className)
+                .superclass(SANDBOXED_SDK_PROVIDER_CLASS)
+                .addModifiers(KModifier.ABSTRACT)
+                .addFunction(generateOnLoadSdkFunction())
+                .addFunction(generateGetViewFunction())
+                .addFunction(generateOnDataReceivedFunction())
+                .addFunction(generateCreateServiceFunction(service()))
+
+        val fileSpec = FileSpec.builder(packageName, className)
+            .addType(classSpec.build())
+            .build()
+        codeGenerator.createNewFile(Dependencies(false), packageName, className).write(fileSpec)
+    }
+
+    private fun generateOnLoadSdkFunction(): FunSpec {
+        return FunSpec.builder("onLoadSdk")
+            .addModifiers(KModifier.OVERRIDE)
+            .addParameter("params", BUNDLE_CLASS)
+            .returns(SANDBOXED_SDK_CLASS)
+            .addStatement("val sdk = ${getCreateServiceFunctionName(service())}(getContext())")
+            .addStatement(
+                "return ${SANDBOXED_SDK_CLASS.simpleName}" +
+                    "(${service().stubDelegateName()}(sdk))"
+            )
+            .build()
+    }
+
+    private fun generateGetViewFunction(): FunSpec {
+        return FunSpec.builder("getView")
+            .addModifiers(KModifier.OVERRIDE)
+            .addParameter("windowContext", CONTEXT_CLASS)
+            .addParameter("params", BUNDLE_CLASS)
+            .addParameter("width", Int::class)
+            .addParameter("height", Int::class)
+            .returns(VIEW_CLASS)
+            .addStatement("TODO(\"Implement\")")
+            .build()
+    }
+
+    private fun generateCreateServiceFunction(service: AnnotatedInterface): FunSpec {
+        return FunSpec.builder(getCreateServiceFunctionName(service))
+            .addModifiers(KModifier.ABSTRACT, KModifier.PROTECTED)
+            .addParameter("context", CONTEXT_CLASS)
+            .returns(service.toPoetClassName())
+            .build()
+    }
+
+    private fun generateOnDataReceivedFunction(): FunSpec {
+        return FunSpec.builder("onDataReceived")
+            .addModifiers(KModifier.OVERRIDE)
+            .addParameter("data", BUNDLE_CLASS)
+            .addParameter("callback", DATA_RECEIVED_CALLBACK_CLASS)
+            .build()
+    }
+
+    private fun service() = api.services.first()
+
+    private fun getCreateServiceFunctionName(service: AnnotatedInterface) = "create${service.name}"
+
+    private fun AnnotatedInterface.toPoetClassName() = ClassName(packageName, name)
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/KotlinPoetUtils.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/KotlinPoetUtils.kt
new file mode 100644
index 0000000..92efc2a
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/KotlinPoetUtils.kt
@@ -0,0 +1,59 @@
+/*
+ * 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.privacysandbox.tools.apicompiler.generator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.Type
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+import java.io.OutputStream
+
+/** KotlinPoet's [ClassName] for this class. */
+internal fun AnnotatedInterface.specClassName() = ClassName(packageName, name)
+
+/** KotlinPoet's [ClassName] for this type. */
+internal fun Type.specClassName(): ClassName {
+    val parts = name.split('.')
+    val packageName = parts.dropLast(1).joinToString(".")
+    val className = parts.last()
+    return ClassName(packageName, className)
+}
+
+/** Convenience method to write [FileSpec]s to KSP-generated [OutputStream]s. */
+internal fun OutputStream.write(spec: FileSpec) = bufferedWriter().use(spec::writeTo)
+
+internal fun TypeSpec.Builder.primaryConstructor(vararg properties: PropertySpec):
+    TypeSpec.Builder {
+    val propertiesWithInitializer =
+        properties.map {
+            it.toBuilder().initializer(it.name)
+                .build()
+        }
+    primaryConstructor(
+        FunSpec.constructorBuilder()
+            .addParameters(propertiesWithInitializer.map { ParameterSpec(it.name, it.type) })
+            .addModifiers(KModifier.INTERNAL)
+            .build()
+    )
+    addProperties(propertiesWithInitializer)
+    return this
+}
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
new file mode 100644
index 0000000..b15569f
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/SdkCodeGenerator.kt
@@ -0,0 +1,57 @@
+/*
+ * 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.privacysandbox.tools.apicompiler.generator
+
+import androidx.privacysandbox.tools.core.ParsedApi
+import androidx.privacysandbox.tools.core.generator.AidlCompiler
+import androidx.privacysandbox.tools.core.generator.AidlGenerator
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import java.nio.file.Files.createTempDirectory
+import java.nio.file.Path
+
+class SdkCodeGenerator(
+    private val codeGenerator: CodeGenerator,
+    private val api: ParsedApi,
+    private val aidlCompilerPath: Path,
+) {
+    fun generate() {
+        generateAidlSources()
+        AbstractSdkProviderGenerator(codeGenerator, api).generate()
+        StubDelegatesGenerator(codeGenerator, api).generate()
+    }
+
+    private fun generateAidlSources() {
+        val workingDir = createTempDirectory("aidl")
+        try {
+            AidlGenerator.generate(AidlCompiler(aidlCompilerPath), api, workingDir)
+                .forEach { source ->
+                    // Sources created by the AIDL compiler have to be copied to files created
+                    // through the KSP APIs, so that they are included in downstream compilation.
+                    val kspGeneratedFile = codeGenerator.createNewFile(
+                        Dependencies(false),
+                        source.packageName,
+                        source.interfaceName,
+                        extensionName = "java"
+                    )
+                    source.file.inputStream().copyTo(kspGeneratedFile)
+                }
+        } finally {
+            workingDir.toFile().deleteRecursively()
+        }
+    }
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt
new file mode 100644
index 0000000..b079b87
--- /dev/null
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/generator/StubDelegatesGenerator.kt
@@ -0,0 +1,89 @@
+/*
+ * 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.privacysandbox.tools.apicompiler.generator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.Method
+import androidx.privacysandbox.tools.core.ParsedApi
+import androidx.privacysandbox.tools.core.generator.aidlName
+import androidx.privacysandbox.tools.core.generator.transactionCallbackName
+import com.google.devtools.ksp.processing.CodeGenerator
+import com.google.devtools.ksp.processing.Dependencies
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+
+class StubDelegatesGenerator(
+    private val codeGenerator: CodeGenerator,
+    private val api: ParsedApi,
+) {
+
+    fun generate() {
+        api.services.forEach(::generateServiceStubDelegate)
+    }
+
+    private fun generateServiceStubDelegate(service: AnnotatedInterface) {
+        val className = service.stubDelegateName()
+        val aidlBaseClassName = ClassName(service.packageName, service.aidlName(), "Stub")
+
+        val classSpec =
+            TypeSpec.classBuilder(className)
+                .superclass(aidlBaseClassName)
+                .primaryConstructor(
+                    PropertySpec.builder(
+                        "delegate",
+                        service.specClassName(),
+                    ).addModifiers(KModifier.PRIVATE).build()
+                )
+                .addFunctions(
+                    service.methods.map(::toFunSpec)
+                )
+
+        val fileSpec = FileSpec.builder(service.packageName, className)
+            .addType(classSpec.build())
+            .build()
+        codeGenerator.createNewFile(Dependencies(false), service.packageName, className)
+            .write(fileSpec)
+    }
+
+    private fun toFunSpec(method: Method): FunSpec {
+        val returnStatement =
+            "return delegate.${method.name}(${method.parameters.joinToString(", ") { it.name }})"
+        return FunSpec.builder(method.name)
+            .addModifiers(KModifier.OVERRIDE)
+            .addParameters(getParameters(method))
+            .returns(method.returnType.specClassName())
+            .addStatement(returnStatement)
+            .build()
+    }
+
+    private fun getParameters(method: Method) = listOf(
+        *method.parameters.map { parameter ->
+            ParameterSpec(parameter.name, parameter.type.specClassName())
+        }.toTypedArray(),
+        ParameterSpec(
+            "transactionCallback",
+            ClassName(api.services.first().packageName, method.returnType.transactionCallbackName())
+        )
+    )
+}
+
+internal fun AnnotatedInterface.stubDelegateName() = "${name}StubDelegate"
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParser.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParser.kt
index 43759b1..ae13329 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParser.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParser.kt
@@ -31,6 +31,7 @@
 import com.google.devtools.ksp.symbol.KSName
 import com.google.devtools.ksp.symbol.KSType
 import com.google.devtools.ksp.symbol.KSValueParameter
+import com.google.devtools.ksp.symbol.Modifier
 
 /** Convenience extension to get the full qualifier + name from a [KSName]. */
 internal fun KSName.getFullName(): String {
@@ -56,6 +57,17 @@
             logger.error("Only interfaces can be annotated with @PrivacySandboxService.")
             return setOf()
         }
+        if (interfacesWithServiceAnnotation.count() > 1) {
+            logger.error(
+                "Multiple interfaces annotated with @PrivacySandboxService are not supported " +
+                    "(${
+                        interfacesWithServiceAnnotation.joinToString {
+                            it.simpleName.getShortName()
+                        }
+                    })."
+            )
+            return setOf()
+        }
         return interfacesWithServiceAnnotation.map(this::parseInterface).toSet()
     }
 
@@ -78,6 +90,7 @@
             parameters = getAllParameters(method),
             // TODO: returnType "Can be null if an error occurred during resolution".
             returnType = parseType(method, method.returnType!!.resolve()),
+            isSuspend = method.modifiers.contains(Modifier.SUSPEND)
         )
     }
 
diff --git a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidator.kt b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidator.kt
index 87556c7..25a3e8a 100644
--- a/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidator.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/main/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidator.kt
@@ -87,9 +87,6 @@
                 })."
             )
         }
-        if (!method.modifiers.contains(Modifier.SUSPEND)) {
-            logger.error("Error in $name: method should be a suspend function.")
-        }
     }
 
     fun validateParameter(method: KSFunctionDeclaration, parameter: KSValueParameter) {
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
index 168aaf8..98ae0cc 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/PrivacySandboxKspCompilerTest.kt
@@ -37,8 +37,8 @@
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
                     interface MySdk {
-                        suspend fun doStuff(x: Int, y: Int): String
-                        suspend fun doMoreStuff()
+                        fun doStuff(x: Int, y: Int): String
+                        fun doMoreStuff()
                     }
                 """
             )
@@ -48,11 +48,79 @@
             compile(
                 Files.createTempDirectory("test").toFile(),
                 TestCompilationArguments(
-                    sources = listOf(source),
+                    sources = listOf(source) + getSyntheticAndroidClasses(),
                     symbolProcessorProviders = listOf(provider),
+                    processorOptions = getProcessorOptions(),
                 )
             )
-        ).succeeds()
+        ).also {
+            it.generatesExactlySources(
+                "com/mysdk/IMySdk.java",
+                "com/mysdk/ICancellationSignal.java",
+                "com/mysdk/IUnitTransactionCallback.java",
+                "com/mysdk/IStringTransactionCallback.java",
+                "com/mysdk/AbstractSandboxedSdkProvider.kt",
+                "com/mysdk/MySdkStubDelegate.kt",
+            )
+        }.also {
+            it.generatesSourcesWithContents(
+                "com/mysdk/AbstractSandboxedSdkProvider.kt" to """
+                    |package com.mysdk
+                    |
+                    |import android.app.sdksandbox.SandboxedSdk
+                    |import android.app.sdksandbox.SandboxedSdkProvider
+                    |import android.content.Context
+                    |import android.os.Bundle
+                    |import android.view.View
+                    |import kotlin.Int
+                    |import kotlin.Unit
+                    |
+                    |public abstract class AbstractSandboxedSdkProvider : SandboxedSdkProvider() {
+                    |  public override fun onLoadSdk(params: Bundle): SandboxedSdk {
+                    |    val sdk = createMySdk(getContext())
+                    |    return SandboxedSdk(MySdkStubDelegate(sdk))
+                    |  }
+                    |
+                    |  public override fun getView(
+                    |    windowContext: Context,
+                    |    params: Bundle,
+                    |    width: Int,
+                    |    height: Int,
+                    |  ): View {
+                    |    TODO("Implement")
+                    |  }
+                    |
+                    |  public override fun onDataReceived(`data`: Bundle,
+                    |      callback: SandboxedSdkProvider.DataReceivedCallback): Unit {
+                    |  }
+                    |
+                    |  protected abstract fun createMySdk(context: Context): MySdk
+                    |}
+                    |
+                """.trimMargin(),
+                "com/mysdk/MySdkStubDelegate.kt" to """
+                    |package com.mysdk
+                    |
+                    |import kotlin.Int
+                    |import kotlin.String
+                    |import kotlin.Unit
+                    |
+                    |public class MySdkStubDelegate internal constructor(
+                    |  private val `delegate`: MySdk,
+                    |) : IMySdk.Stub() {
+                    |  public override fun doStuff(
+                    |    x: Int,
+                    |    y: Int,
+                    |    transactionCallback: IStringTransactionCallback,
+                    |  ): String = delegate.doStuff(x, y)
+                    |
+                    |  public override fun doMoreStuff(transactionCallback: IUnitTransactionCallback): Unit =
+                    |      delegate.doMoreStuff()
+                    |}
+                    |
+                """.trimMargin(),
+            )
+        }
     }
 
     @Test
@@ -76,10 +144,78 @@
             compile(
                 Files.createTempDirectory("test").toFile(),
                 TestCompilationArguments(
-                    sources = listOf(source),
-                    symbolProcessorProviders = listOf(provider)
+                    sources = listOf(source) + getSyntheticAndroidClasses(),
+                    symbolProcessorProviders = listOf(provider),
+                    processorOptions = getProcessorOptions(),
                 )
             )
         ).fails()
     }
+
+    private fun getProcessorOptions() =
+        mapOf(
+            "aidl_compiler_path" to (System.getProperty("aidl_compiler_path")
+                ?: throw IllegalArgumentException("aidl_compiler_path flag not set."))
+        )
+
+    private fun getSyntheticAndroidClasses() =
+        listOf(
+            Source.java(
+                "android.app.sdksandbox.SandboxedSdk",
+                """
+                    package android.app.sdksandbox;
+                    import android.os.IBinder;
+                    public class SandboxedSdk {
+                        public SandboxedSdk(IBinder sdkInterface) {}
+                    }
+                """.trimIndent()
+            ),
+            Source.java(
+                "android.app.sdksandbox.SandboxedSdkProvider",
+                """
+                    package android.app.sdksandbox;
+                    import android.content.Context;
+                    import android.os.Bundle;
+                    import android.view.View;
+                    public abstract class SandboxedSdkProvider {
+                        public abstract SandboxedSdk onLoadSdk(Bundle params) throws LoadSdkException;
+                        public abstract View getView(
+                                Context windowContext, Bundle params, int width, int height);
+                        public final Context getContext() {
+                            return null;
+                        }
+                        public abstract void onDataReceived(
+                                Bundle data, DataReceivedCallback callback);
+                        public interface DataReceivedCallback {
+                            void onDataReceivedSuccess(Bundle params);
+                            void onDataReceivedError(String errorMessage);
+                        }
+                    }
+                """.trimIndent()
+            ),
+            Source.java(
+                "android.app.sdksandbox.LoadSdkException",
+                """
+                    package android.app.sdksandbox;
+                    import android.os.Parcel;
+                    import android.os.Parcelable;
+                    public final class LoadSdkException extends Exception implements Parcelable {
+                        @Override
+                        public int describeContents() {
+                            return 0;
+                        }
+                        @Override
+                        public void writeToParcel(Parcel destination, int flags) {
+                        }
+                    }
+                """.trimIndent()
+            ),
+            Source.java(
+                "android.app.sdksandbox.SandboxedSdkContext",
+                """
+                    package android.app.sdksandbox;
+                    public final class SandboxedSdkContext {}
+                """.trimIndent()
+            ),
+        )
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParserTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParserTest.kt
index a44a798..0a44f1de 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParserTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiParserTest.kt
@@ -42,7 +42,7 @@
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
                     interface MySdk {
-                        fun doStuff(x: Int, y: Int): String
+                        suspend fun doStuff(x: Int, y: Int): String
                         fun doMoreStuff()
                     }
                 """
@@ -72,12 +72,14 @@
                                 ),
                                 returnType = Type(
                                     name = "kotlin.String",
-                                )
+                                ),
+                                isSuspend = true,
                             ),
                             Method(
                                 name = "doMoreStuff",
                                 parameters = listOf(),
-                                returnType = Type("kotlin.Unit")
+                                returnType = Type("kotlin.Unit"),
+                                isSuspend = false,
                             )
                         )
                     )
@@ -104,4 +106,32 @@
         checkSourceFails(source)
             .containsError("Only interfaces can be annotated with @PrivacySandboxService.")
     }
+
+    @Test
+    fun multipleServices_fails() {
+        val source =
+            Source.kotlin(
+                "com/mysdk/MySdk.kt",
+                """
+                    package com.mysdk
+                    import androidx.privacysandbox.tools.PrivacySandboxService
+                    @PrivacySandboxService
+                    interface MySdk
+                """
+            )
+        val source2 =
+            Source.kotlin(
+                "com/mysdk/MySdk2.kt",
+                """
+                    package com.mysdk
+                    import androidx.privacysandbox.tools.PrivacySandboxService
+                    @PrivacySandboxService
+                    interface MySdk2
+                """
+            )
+
+        checkSourceFails(source, source2)
+            .containsError("Multiple interfaces annotated with @PrivacySandboxService are not " +
+                "supported (MySdk, MySdk2).")
+    }
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidatorTest.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidatorTest.kt
index 45600f9..3255884 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidatorTest.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/parser/ApiValidatorTest.kt
@@ -112,13 +112,6 @@
     }
 
     @Test
-    fun nonSuspendMethod_fails() {
-        checkSourceFails(serviceMethod("fun foo()")).containsExactlyErrors(
-            "Error in com.mysdk.MySdk.foo: method should be a suspend function."
-        )
-    }
-
-    @Test
     fun parameterWitDefaultValue_fails() {
         checkSourceFails(serviceMethod("suspend fun foo(x: Int = 5)")).containsExactlyErrors(
             "Error in com.mysdk.MySdk.foo: parameters cannot have default values."
diff --git a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
index ad2c397..dfa50e4 100644
--- a/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
+++ b/privacysandbox/tools/tools-apicompiler/src/test/java/androidx/privacysandbox/tools/apicompiler/util/KspTestRunner.kt
@@ -50,12 +50,12 @@
     return provider.processor.capture!!
 }
 
-fun checkSourceFails(source: Source): CompilationResultSubject {
+fun checkSourceFails(vararg sources: Source): CompilationResultSubject {
     val provider = CapturingSymbolProcessor.Provider()
     val result = compile(
         Files.createTempDirectory("test").toFile(),
         TestCompilationArguments(
-            sources = listOf(source),
+            sources = sources.asList(),
             symbolProcessorProviders = listOf(provider)
         )
     )
@@ -69,7 +69,26 @@
     }
 
     fun succeeds() {
-        assertWithMessage("UnexpectedErrors: ${getErrorMessages()}").that(result.success).isTrue()
+        assertWithMessage(
+            "UnexpectedErrors:\n${getFullErrorMessages()?.joinToString("\n")}"
+        ).that(
+            result.success
+        ).isTrue()
+    }
+
+    fun generatesExactlySources(vararg sourcePaths: String) {
+        succeeds()
+        assertThat(result.generatedSources.map(Source::relativePath))
+            .containsExactlyElementsIn(sourcePaths)
+    }
+
+    fun generatesSourcesWithContents(vararg sources: Pair<String, String>) {
+        succeeds()
+        val contentsByFile = result.generatedSources.associate { it.relativePath to it.contents }
+        for ((file, content) in sources) {
+            assertWithMessage("File $file was not generated").that(contentsByFile).containsKey(file)
+            assertThat(contentsByFile[file]).isEqualTo(content)
+        }
     }
 
     fun fails() {
@@ -88,6 +107,16 @@
 
     private fun getErrorMessages() =
         result.diagnostics[Diagnostic.Kind.ERROR]?.map(DiagnosticMessage::msg)
+
+    private fun getFullErrorMessages() =
+        result.diagnostics[Diagnostic.Kind.ERROR]?.map(::toReadableMessage)
+
+    private fun toReadableMessage(message: DiagnosticMessage) = """
+            |Error: ${message.msg}
+            |Location: ${message.location?.source?.relativePath}:${message.location?.line}
+            |File:
+            |${message.location?.source?.contents}
+        """.trimMargin()
 }
 
 private class CapturingSymbolProcessor(private val logger: KSPLogger) : SymbolProcessor {
diff --git a/privacysandbox/tools/tools-apigenerator/build.gradle b/privacysandbox/tools/tools-apigenerator/build.gradle
index 73298f7..2b70c56 100644
--- a/privacysandbox/tools/tools-apigenerator/build.gradle
+++ b/privacysandbox/tools/tools-apigenerator/build.gradle
@@ -16,6 +16,8 @@
 
 import androidx.build.LibraryType
 import androidx.build.RunApiTasks
+import androidx.build.SdkHelperKt
+import androidx.build.SupportConfig
 
 plugins {
     id("AndroidXPlugin")
@@ -33,8 +35,25 @@
 
     testImplementation(project(":room:room-compiler-processing-testing"))
     testImplementation(project(":internal-testutils-truth"))
+    testImplementation(libs.kotlinCoroutinesCore)
     testImplementation(libs.junit)
     testImplementation(libs.truth)
+
+    // Include android jar for compilation of generated sources.
+    testImplementation(fileTree(
+            dir: "${SdkHelperKt.getSdkPath(project)}/platforms/$SupportConfig.COMPILE_SDK_VERSION/",
+            include: "android.jar"
+    ))
+    // Get AIDL compiler path and pass it to tests for code generation.
+    def aidlCompilerPath = "${SdkHelperKt.getSdkPath(project)}/build-tools/${SupportConfig.BUILD_TOOLS_VERSION}/aidl"
+    test {
+        inputs.files(aidlCompilerPath)
+                .withPropertyName("aidl_compiler_path")
+                .withPathSensitivity(PathSensitivity.NAME_ONLY)
+        doFirst {
+            systemProperty "aidl_compiler_path", aidlCompilerPath
+        }
+    }
 }
 
 androidx {
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ClientProxyTypeGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ClientProxyTypeGenerator.kt
new file mode 100644
index 0000000..d8927fd
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ClientProxyTypeGenerator.kt
@@ -0,0 +1,69 @@
+/*
+ * 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.privacysandbox.tools.apigenerator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.Method
+import androidx.privacysandbox.tools.core.Parameter
+import androidx.privacysandbox.tools.core.poet.build
+import androidx.privacysandbox.tools.core.poet.poetSpec
+import androidx.privacysandbox.tools.core.poet.primaryConstructor
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeSpec
+
+internal class ClientProxyTypeGenerator(private val service: AnnotatedInterface) {
+    internal val className =
+        ClassName(service.packageName, "${service.name}ClientProxy")
+    internal val remoteBinderClassName =
+        ClassName(service.packageName, "I${service.name}")
+
+    fun generate(): TypeSpec = TypeSpec.classBuilder(className).build {
+        addModifiers(KModifier.PRIVATE)
+        addSuperinterface(ClassName(service.packageName, service.name))
+        primaryConstructor(
+            listOf(
+                PropertySpec.builder("remote", remoteBinderClassName)
+                    .addModifiers(KModifier.PRIVATE).build()
+            )
+        )
+        addFunctions(service.methods.map(::generateProxyMethodImplementation))
+    }
+
+    private fun generateProxyMethodImplementation(method: Method) =
+        FunSpec.builder(method.name).build {
+            addParameters(method.parameters.map { it.poetSpec() })
+            addModifiers(KModifier.OVERRIDE)
+
+            val parameterList = buildList {
+                addAll(method.parameters.map(Parameter::name))
+                add("null")
+            }
+            val returnsUnit = method.returnType.name == Unit::class.qualifiedName
+            if (returnsUnit) {
+                addStatement(
+                    "remote.${method.name}(${parameterList.joinToString()})"
+                )
+            } else {
+                addStatement(
+                    "return remote.${method.name}(${parameterList.joinToString()})!!"
+                )
+            }
+        }
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
index 7c5b807..86b2df4 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGenerator.kt
@@ -17,19 +17,11 @@
 package androidx.privacysandbox.tools.apigenerator
 
 import androidx.privacysandbox.tools.apigenerator.parser.ApiStubParser
-import androidx.privacysandbox.tools.core.AnnotatedInterface
-import androidx.privacysandbox.tools.core.Method
-import androidx.privacysandbox.tools.core.Parameter
-import androidx.privacysandbox.tools.core.Type
-import com.squareup.kotlinpoet.ClassName
-import com.squareup.kotlinpoet.CodeBlock
-import com.squareup.kotlinpoet.FileSpec
-import com.squareup.kotlinpoet.FunSpec
-import com.squareup.kotlinpoet.KModifier
-import com.squareup.kotlinpoet.ParameterSpec
-import com.squareup.kotlinpoet.TypeName
-import com.squareup.kotlinpoet.TypeSpec
+import androidx.privacysandbox.tools.core.ParsedApi
+import androidx.privacysandbox.tools.core.generator.AidlCompiler
+import androidx.privacysandbox.tools.core.generator.AidlGenerator
 import java.io.File
+import java.nio.file.Files
 import java.nio.file.Path
 import kotlin.io.path.exists
 import kotlin.io.path.isDirectory
@@ -47,8 +39,6 @@
      * @param aidlCompiler AIDL compiler binary. It must target API 30 or above.
      * @param outputDirectory Output directory for the sources.
      */
-    // AIDL compiler parameter will be used once we start generating Binders.
-    @Suppress("UNUSED_PARAMETER")
     fun generate(
         sdkInterfaceDescriptors: Path,
         aidlCompiler: Path,
@@ -60,59 +50,28 @@
 
         val output = outputDirectory.toFile()
         val sdkApi = ApiStubParser.parse(sdkInterfaceDescriptors)
+        generateBinders(sdkApi, AidlCompiler(aidlCompiler), output)
+
         sdkApi.services.forEach {
-            generateServiceInterface(it, output)
-            generateServiceImplementation(it, output)
+            ServiceInterfaceFileGenerator(it).generate().writeTo(output)
+            ServiceFactoryFileGenerator(it).generate().writeTo(output)
         }
     }
 
-    private fun generateServiceInterface(service: AnnotatedInterface, outputDir: File) {
-        val annotatedInterface =
-            TypeSpec.interfaceBuilder(ClassName(service.packageName, service.name))
-                .addFunctions(service.methods.map {
-                    it.poetSpec()
-                        .toBuilder().addModifiers(KModifier.ABSTRACT)
-                        .build()
-                })
-                .build()
-        FileSpec.get(service.packageName, annotatedInterface)
-            .toBuilder()
-            .addKotlinDefaultImports(includeJvm = false, includeJs = false)
-            .build()
-            .writeTo(outputDir)
-    }
-
-    private fun generateServiceImplementation(service: AnnotatedInterface, outputDir: File) {
-        val implementation =
-            TypeSpec.classBuilder(ClassName(service.packageName, "${service.name}Impl"))
-                .addSuperinterface(ClassName(service.packageName, service.name))
-                .addFunctions(service.methods.map {
-                    it.poetSpec()
-                        .toBuilder().addModifiers(KModifier.OVERRIDE)
-                        .addCode(CodeBlock.of("TODO()"))
-                        .build()
-                })
-                .build()
-        FileSpec.get(service.packageName, implementation)
-            .toBuilder()
-            .addKotlinDefaultImports(includeJvm = false, includeJs = false)
-            .build()
-            .writeTo(outputDir)
-    }
-
-    private fun Method.poetSpec(): FunSpec {
-        return FunSpec.builder(name)
-            .addParameters(parameters.map { it.poetSpec() })
-            .returns(returnType.poetSpec())
-            .build()
-    }
-
-    private fun Parameter.poetSpec(): ParameterSpec {
-        return ParameterSpec.builder(name, type.poetSpec()).build()
-    }
-
-    private fun Type.poetSpec(): TypeName {
-        val splits = name.split('.')
-        return ClassName(splits.dropLast(1).joinToString("."), splits.last())
+    private fun generateBinders(sdkApi: ParsedApi, aidlCompiler: AidlCompiler, output: File) {
+        val aidlWorkingDir = output.resolve("tmp-aidl").also { it.mkdir() }
+        try {
+            val generatedFiles =
+                AidlGenerator.generate(aidlCompiler, sdkApi, aidlWorkingDir.toPath())
+            generatedFiles.forEach {
+                val relativePath = aidlWorkingDir.toPath().relativize(it.file.toPath())
+                val source = it.file.toPath()
+                val dest = output.toPath().resolve(relativePath)
+                dest.toFile().parentFile.mkdirs()
+                Files.move(source, dest)
+            }
+        } finally {
+            aidlWorkingDir.deleteRecursively()
+        }
     }
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
new file mode 100644
index 0000000..41050eb
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceFactoryFileGenerator.kt
@@ -0,0 +1,92 @@
+/*
+ * 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.privacysandbox.tools.apigenerator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.poet.build
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+
+internal class ServiceFactoryFileGenerator(private val service: AnnotatedInterface) {
+    private val proxyTypeGenerator by lazy {
+        ClientProxyTypeGenerator(service)
+    }
+
+    fun generate(): FileSpec =
+        FileSpec.builder(service.packageName, "${service.name}Factory").build {
+            addImport("kotlinx.coroutines", "suspendCancellableCoroutine")
+            addImport("kotlin.coroutines", "resume")
+            addImport("kotlin.coroutines", "resumeWithException")
+            addKotlinDefaultImports(includeJvm = false, includeJs = false)
+
+            addFunction(generateFactoryFunction())
+
+            addType(proxyTypeGenerator.generate())
+        }
+
+    private fun generateFactoryFunction() = FunSpec.builder("create${service.name}").build {
+        addModifiers(KModifier.SUSPEND)
+        addParameter(ParameterSpec("context", AndroidClassNames.context))
+        returns(ClassName(service.packageName, service.name))
+
+        addCode(CodeBlock.builder().build {
+            beginControlFlow("return suspendCancellableCoroutine")
+            addStatement("val sdkSandboxManager = context.getSystemService(%T::class.java)",
+                AndroidClassNames.sandboxManager)
+            addNamed("""
+                |sdkSandboxManager.loadSdk(
+                |    %sdkPackageName:S,
+                |    %bundle:T.EMPTY,
+                |    { obj: Runnable -> obj.run() },
+                |    object : %outcomeReceiver:T<%sandboxedSdk:T, %loadSdkException:T> {
+                |        override fun onResult(result: %sandboxedSdk:T) {
+                |            it.resume(%proxy:T(
+                |                %aidlInterface:T.Stub.asInterface(result.getInterface())))
+                |        }
+                |
+                |        override fun onError(error: %loadSdkException:T) {
+                |            it.resumeWithException(error)
+                |        }
+                |    })
+            """.trimMargin(),
+                mapOf(
+                    "sdkPackageName" to service.packageName,
+                    "proxy" to proxyTypeGenerator.className,
+                    "aidlInterface" to proxyTypeGenerator.remoteBinderClassName,
+                    "bundle" to AndroidClassNames.bundle,
+                    "outcomeReceiver" to AndroidClassNames.outcomeReceiver,
+                    "sandboxedSdk" to AndroidClassNames.sandboxedSdk,
+                    "loadSdkException" to AndroidClassNames.loadSdkException,
+                ))
+            endControlFlow()
+        })
+    }
+}
+
+private object AndroidClassNames {
+    val context = ClassName("android.content", "Context")
+    val bundle = ClassName("android.os", "Bundle")
+    val outcomeReceiver = ClassName("android.os", "OutcomeReceiver")
+    val sandboxManager =
+        ClassName("android.app.sdksandbox", "SdkSandboxManager")
+    val sandboxedSdk = ClassName("android.app.sdksandbox", "SandboxedSdk")
+    val loadSdkException = ClassName("android.app.sdksandbox", "LoadSdkException")
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt
new file mode 100644
index 0000000..304abf5
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/ServiceInterfaceFileGenerator.kt
@@ -0,0 +1,48 @@
+/*
+ * 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.privacysandbox.tools.apigenerator
+
+import androidx.privacysandbox.tools.core.AnnotatedInterface
+import androidx.privacysandbox.tools.core.Method
+import androidx.privacysandbox.tools.core.poet.build
+import androidx.privacysandbox.tools.core.poet.poetSpec
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.TypeSpec
+
+internal class ServiceInterfaceFileGenerator(private val service: AnnotatedInterface) {
+
+    fun generate(): FileSpec {
+        val annotatedInterface =
+            TypeSpec.interfaceBuilder(ClassName(service.packageName, service.name)).build {
+                addFunctions(service.methods.map(::generateInterfaceMethod))
+            }
+
+        return FileSpec.get(service.packageName, annotatedInterface).toBuilder().build {
+            addKotlinDefaultImports(includeJvm = false, includeJs = false)
+        }
+    }
+
+    private fun generateInterfaceMethod(method: Method) =
+        FunSpec.builder(method.name).build {
+            addModifiers(KModifier.ABSTRACT)
+            addParameters(method.parameters.map { it.poetSpec() })
+            returns(method.returnType.poetSpec())
+        }
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
index 8c406cc..ad50d7d 100644
--- a/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/main/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParser.kt
@@ -145,6 +145,7 @@
             function.name,
             function.valueParameters.map { Parameter(it.name, parseType(it.type)) },
             parseType(function.returnType),
+            Flag.Function.IS_SUSPEND(function.flags)
         )
     }
 
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/data/input-src/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/data/input-src/com/mysdk/MySdk.kt
deleted file mode 100644
index 012925b..0000000
--- a/privacysandbox/tools/tools-apigenerator/src/test/data/input-src/com/mysdk/MySdk.kt
+++ /dev/null
@@ -1,10 +0,0 @@
-package com.mysdk
-
-import androidx.privacysandbox.tools.PrivacySandboxService
-
-@PrivacySandboxService
-interface MySdk {
-    fun doSomething(magicNumber: Int, awesomeString: String): Boolean
-
-    fun returnMagicNumber(): Int
-}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdk.kt
deleted file mode 100644
index d41d136..0000000
--- a/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdk.kt
+++ /dev/null
@@ -1,7 +0,0 @@
-package com.mysdk
-
-public interface MySdk {
-  public fun doSomething(magicNumber: Int, awesomeString: String): Boolean
-
-  public fun returnMagicNumber(): Int
-}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdkImpl.kt b/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdkImpl.kt
deleted file mode 100644
index ec0d7e4..0000000
--- a/privacysandbox/tools/tools-apigenerator/src/test/data/output-src/com/mysdk/MySdkImpl.kt
+++ /dev/null
@@ -1,11 +0,0 @@
-package com.mysdk
-
-public class MySdkImpl : MySdk {
-  public override fun doSomething(magicNumber: Int, awesomeString: String): Boolean {
-    TODO()
-  }
-
-  public override fun returnMagicNumber(): Int {
-    TODO()
-  }
-}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGeneratorTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGeneratorTest.kt
index bc832c6..689a593 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGeneratorTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/PrivacySandboxApiGeneratorTest.kt
@@ -20,7 +20,7 @@
 import com.google.common.truth.Truth.assertThat
 import java.io.File
 import java.nio.file.Files
-import java.nio.file.Path
+import kotlin.io.path.Path
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -28,51 +28,56 @@
 @RunWith(JUnit4::class)
 class PrivacySandboxApiGeneratorTest {
     @Test
-    fun annotatedInterface_isParsed() {
-        val source =
-            loadTestSource(inputTestDataDir, "com/mysdk/MySdk.kt")
-        val descriptors = compileIntoInterfaceDescriptorsJar(source)
+    fun testSandboxSdk_compilesAndGeneratesExpectedOutput() {
+        val descriptors =
+            compileIntoInterfaceDescriptorsJar(
+                *loadSourcesFromDirectory(inputTestDataDir).toTypedArray()
+            )
+        val aidlPath = System.getProperty("aidl_compiler_path")?.let(::Path)
+            ?: throw IllegalArgumentException("aidl_compiler_path flag not set.")
 
         val generator = PrivacySandboxApiGenerator()
 
         val outputDir = Files.createTempDirectory("output").also { it.toFile().deleteOnExit() }
-        generator.generate(descriptors, Path.of(""), outputDir)
+        generator.generate(descriptors, aidlPath, outputDir)
+        val outputSources = loadSourcesFromDirectory(outputDir.toFile())
 
-        assertOutputDirContainsSources(
-            outputDir, listOf(
-                loadTestSource(outputTestDataDir, "com/mysdk/MySdk.kt"),
-                loadTestSource(outputTestDataDir, "com/mysdk/MySdkImpl.kt"),
-            )
-        )
-    }
+        assertCompiles(outputSources)
 
-    private fun assertOutputDirContainsSources(outputDir: Path, expectedSources: List<Source>) {
-        val outputMap =
-            outputDir.toFile().walk().filter { it.isFile }.map {
-                outputDir.relativize(it.toPath()).toString() to it.readText()
-            }.toMap()
-        val expectedSourcesMap = expectedSources.associate { it.relativePath to it.contents }
-        assertCompiles(outputMap.map { (relativePath, contents) ->
-            Source.kotlin(
-                relativePath,
-                contents
+        val expectedSources = loadSourcesFromDirectory(outputTestDataDir)
+        assertThat(outputSources.map(Source::relativePath))
+            .containsExactlyElementsIn(
+                expectedSources.map(Source::relativePath) + listOf(
+                    "com/mysdk/ITestSandboxSdk.java",
+                    "com/mysdk/IUnitTransactionCallback.java",
+                    "com/mysdk/ICancellationSignal.java",
+                    "com/mysdk/IIntTransactionCallback.java",
+                    "com/mysdk/IFloatTransactionCallback.java",
+                    "com/mysdk/ICharTransactionCallback.java",
+                    "com/mysdk/IDoubleTransactionCallback.java",
+                    "com/mysdk/IBooleanTransactionCallback.java",
+                    "com/mysdk/IStringTransactionCallback.java",
+                    "com/mysdk/ILongTransactionCallback.java",
+                )
             )
-        })
-        // Not comparing the maps directly because the StringSubject error output is slightly
-        // better than the binary assertion provided by the MapSubject.
-        assertThat(outputMap.keys)
-            .containsExactlyElementsIn(expectedSourcesMap.keys)
-        for ((relativePath, sourceContent) in expectedSourcesMap) {
-            assertThat(outputMap[relativePath]).isEqualTo(sourceContent)
+
+        val outputSourceMap = outputSources.associateBy(Source::relativePath)
+        for (expected in expectedSources) {
+            assertThat(outputSourceMap[expected.relativePath]?.contents)
+                .isEqualTo(expected.contents)
         }
     }
 
-    private fun loadTestSource(rootDir: File, relativePath: String): Source {
-        val contents = rootDir.resolve(File(relativePath))
-        return Source.loadKotlinSource(contents, relativePath)
+    private fun loadSourcesFromDirectory(directory: File): List<Source> {
+        return directory.walk().filter { it.isFile }.map {
+            val relativePath = directory.toPath().relativize(it.toPath()).toString()
+            val qualifiedName = relativePath.removeSuffix(".${it.extension}").replace('/', '.')
+            Source.load(file = it, qName = qualifiedName, relativePath = relativePath)
+        }.toList()
     }
+
     companion object {
-        val inputTestDataDir = File("src/test/data/input-src")
-        val outputTestDataDir = File("src/test/data/output-src")
+        val inputTestDataDir = File("src/test/test-data/input")
+        val outputTestDataDir = File("src/test/test-data/output")
     }
 }
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/TestUtils.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/TestUtils.kt
index a516469..c05ced6 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/TestUtils.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/TestUtils.kt
@@ -45,7 +45,7 @@
     val tempDir = createTempDirectory("compile").toFile().also { it.deleteOnExit() }
     return compile(
         tempDir, TestCompilationArguments(
-            sources = sources,
+            sources = sources + syntheticPrivacySandboxSources,
         )
     )
 }
@@ -70,3 +70,43 @@
 
     return sdkInterfaceDescriptors.toPath()
 }
+
+// PrivacySandbox platform APIs are not available in AndroidX prebuilts nor are they stable, so
+// while that's the case we use fake stubs to run our compilation tests.
+private val syntheticPrivacySandboxSources = listOf(
+    Source.java(
+        "android.app.sdksandbox.SdkSandboxManager", """
+        |package android.app.sdksandbox;
+        |
+        |import android.os.Bundle;
+        |import android.os.OutcomeReceiver;
+        |import java.util.concurrent.Executor;
+        |
+        |public final class SdkSandboxManager {
+        |    public void loadSdk(
+        |        String sdkName,
+        |        Bundle params,
+        |        Executor executor,
+        |        OutcomeReceiver<SandboxedSdk, LoadSdkException> receiver) {}
+        |}
+        |""".trimMargin()
+    ),
+    Source.java(
+        "android.app.sdksandbox.SandboxedSdk", """
+        |package android.app.sdksandbox;
+        |
+        |import android.os.IBinder;
+        |
+        |public final class SandboxedSdk {
+        |    public IBinder getInterface() { return null; }
+        |}
+        |""".trimMargin()
+    ),
+    Source.java(
+        "android.app.sdksandbox.LoadSdkException", """
+        |package android.app.sdksandbox;
+        |
+        |public final class LoadSdkException extends Exception {}
+        |""".trimMargin()
+    ),
+)
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
index 0d25e10..33ee4d2 100644
--- a/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
+++ b/privacysandbox/tools/tools-apigenerator/src/test/java/androidx/privacysandbox/tools/apigenerator/parser/ApiStubParserTest.kt
@@ -34,12 +34,12 @@
     @Test
     fun annotatedInterface_isParsed() {
         val source = Source.kotlin(
-            "com/mysdk/MySdk.kt", """
+            "com/mysdk/TestSandboxSdk.kt", """
                     package com.mysdk
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
                     interface MySdk {
-                      fun doSomething(magicNumber: Int, awesomeString: String)
+                      suspend fun doSomething(magicNumber: Int, awesomeString: String)
                       fun returnMagicNumber(): Int
                     }
                 """
@@ -55,12 +55,14 @@
                                 Parameter("magicNumber", Type("kotlin.Int")),
                                 Parameter("awesomeString", Type("kotlin.String"))
                             ),
-                            returnType = Type("kotlin.Unit")
+                            returnType = Type("kotlin.Unit"),
+                            isSuspend = true,
                         ),
                         Method(
                             name = "returnMagicNumber",
                             parameters = listOf(),
-                            returnType = Type("kotlin.Int")
+                            returnType = Type("kotlin.Int"),
+                            isSuspend = false,
                         )
                     )
                 )
@@ -71,7 +73,7 @@
     fun nonAnnotatedClasses_areSafelyIgnored() {
         val interfaces = compileAndParseApi(
             Source.kotlin(
-                "com/mysdk/MySdk.kt", """
+                "com/mysdk/TestSandboxSdk.kt", """
                     package com.mysdk
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
@@ -101,7 +103,7 @@
     @Test
     fun annotatedInterfaceWithEmptyPackageName_isHandledSafely() {
         val source = Source.kotlin(
-            "MySdk.kt", """
+            "TestSandboxSdk.kt", """
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
                     interface MySdk
@@ -132,7 +134,7 @@
     @Test
     fun annotatedKotlinClass_throws() {
         val source = Source.kotlin(
-            "com/mysdk/MySdk.kt", """
+            "com/mysdk/TestSandboxSdk.kt", """
                     package com.mysdk
                     import androidx.privacysandbox.tools.PrivacySandboxService
                     @PrivacySandboxService
@@ -172,7 +174,7 @@
     @Test
     fun missingAnnotatedInterface_throws() {
         val source = Source.kotlin(
-            "com/mysdk/MySdk.kt", """
+            "com/mysdk/TestSandboxSdk.kt", """
                     package com.mysdk
                     interface MySdk
                 """
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/input/com/mysdk/TestSandboxSdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/input/com/mysdk/TestSandboxSdk.kt
new file mode 100644
index 0000000..f5429e1
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/input/com/mysdk/TestSandboxSdk.kt
@@ -0,0 +1,24 @@
+package com.mysdk
+
+import androidx.privacysandbox.tools.PrivacySandboxService
+
+@PrivacySandboxService
+interface TestSandboxSdk {
+    fun echoBoolean(input: Boolean): Boolean
+
+    fun echoInt(input: Int): Int
+
+    fun echoLong(input: Long): Long
+
+    fun echoFloat(input: Float): Float
+
+    fun echoDouble(input: Double): Double
+
+    fun echoChar(input: Char): Char
+
+    fun echoString(input: String): String
+
+    fun receiveMultipleArguments(first: Int, second: String, third: Long)
+
+    fun receiveAndReturnNothing()
+}
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdk.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdk.kt
new file mode 100644
index 0000000..bb06d30
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdk.kt
@@ -0,0 +1,25 @@
+package com.mysdk
+
+public interface TestSandboxSdk {
+  public fun echoBoolean(input: Boolean): Boolean
+
+  public fun echoChar(input: Char): Char
+
+  public fun echoDouble(input: Double): Double
+
+  public fun echoFloat(input: Float): Float
+
+  public fun echoInt(input: Int): Int
+
+  public fun echoLong(input: Long): Long
+
+  public fun echoString(input: String): String
+
+  public fun receiveAndReturnNothing(): Unit
+
+  public fun receiveMultipleArguments(
+    first: Int,
+    second: String,
+    third: Long,
+  ): Unit
+}
diff --git a/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdkFactory.kt b/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdkFactory.kt
new file mode 100644
index 0000000..c41ee10
--- /dev/null
+++ b/privacysandbox/tools/tools-apigenerator/src/test/test-data/output/com/mysdk/TestSandboxSdkFactory.kt
@@ -0,0 +1,59 @@
+package com.mysdk
+
+import android.app.sdksandbox.LoadSdkException
+import android.app.sdksandbox.SandboxedSdk
+import android.app.sdksandbox.SdkSandboxManager
+import android.content.Context
+import android.os.Bundle
+import android.os.OutcomeReceiver
+import kotlin.coroutines.resume
+import kotlin.coroutines.resumeWithException
+import kotlinx.coroutines.suspendCancellableCoroutine
+
+public suspend fun createTestSandboxSdk(context: Context): TestSandboxSdk =
+    suspendCancellableCoroutine {
+  val sdkSandboxManager = context.getSystemService(SdkSandboxManager::class.java)
+  sdkSandboxManager.loadSdk(
+      "com.mysdk",
+      Bundle.EMPTY,
+      { obj: Runnable -> obj.run() },
+      object : OutcomeReceiver<SandboxedSdk, LoadSdkException> {
+          override fun onResult(result: SandboxedSdk) {
+              it.resume(TestSandboxSdkClientProxy(
+                  ITestSandboxSdk.Stub.asInterface(result.getInterface())))
+          }
+
+          override fun onError(error: LoadSdkException) {
+              it.resumeWithException(error)
+          }
+      })}
+
+private class TestSandboxSdkClientProxy(
+  private val remote: ITestSandboxSdk,
+) : TestSandboxSdk {
+  public override fun echoBoolean(input: Boolean) = remote.echoBoolean(input, null)!!
+
+  public override fun echoChar(input: Char) = remote.echoChar(input, null)!!
+
+  public override fun echoDouble(input: Double) = remote.echoDouble(input, null)!!
+
+  public override fun echoFloat(input: Float) = remote.echoFloat(input, null)!!
+
+  public override fun echoInt(input: Int) = remote.echoInt(input, null)!!
+
+  public override fun echoLong(input: Long) = remote.echoLong(input, null)!!
+
+  public override fun echoString(input: String) = remote.echoString(input, null)!!
+
+  public override fun receiveAndReturnNothing(): Unit {
+    remote.receiveAndReturnNothing(null)
+  }
+
+  public override fun receiveMultipleArguments(
+    first: Int,
+    second: String,
+    third: Long,
+  ): Unit {
+    remote.receiveMultipleArguments(first, second, third, null)
+  }
+}
diff --git a/privacysandbox/tools/tools-core/build.gradle b/privacysandbox/tools/tools-core/build.gradle
index cd69a76..8b050e3 100644
--- a/privacysandbox/tools/tools-core/build.gradle
+++ b/privacysandbox/tools/tools-core/build.gradle
@@ -26,6 +26,7 @@
 
 dependencies {
     api(libs.kotlinStdlib)
+    implementation(libs.kotlinPoet)
 
     testImplementation(project(":room:room-compiler-processing-testing"))
     testImplementation(libs.junit)
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/AnnotatedInterface.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/AnnotatedInterface.kt
index fb83982..5cfd77d 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/AnnotatedInterface.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/AnnotatedInterface.kt
@@ -17,7 +17,7 @@
 package androidx.privacysandbox.tools.core
 
 /** Result of parsing a Kotlin interface. */
-public data class AnnotatedInterface(
+data class AnnotatedInterface(
     val name: String,
     val packageName: String,
     val methods: List<Method> = emptyList(),
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Method.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Method.kt
index 297103d..3fae552 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Method.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Method.kt
@@ -16,8 +16,9 @@
 
 package androidx.privacysandbox.tools.core
 
-public data class Method(
+data class Method(
     val name: String,
     val parameters: List<Parameter>,
     val returnType: Type,
+    val isSuspend: Boolean,
 )
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Parameter.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Parameter.kt
index 408df1a..33a4873 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Parameter.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Parameter.kt
@@ -16,7 +16,7 @@
 
 package androidx.privacysandbox.tools.core
 
-public data class Parameter(
+data class Parameter(
     val name: String,
     val type: Type,
 )
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/ParsedApi.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/ParsedApi.kt
index 5b19389..0ee024d 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/ParsedApi.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/ParsedApi.kt
@@ -17,6 +17,6 @@
 package androidx.privacysandbox.tools.core
 
 /** Result of parsing a full developer-defined API for an SDK. */
-public data class ParsedApi(
+data class ParsedApi(
     val services: Set<AnnotatedInterface>,
 )
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Type.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Type.kt
index 8e44ae9..92f2d90 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Type.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/Type.kt
@@ -16,6 +16,6 @@
 
 package androidx.privacysandbox.tools.core
 
-public data class Type(
+data class Type(
   val name: String,
 )
\ No newline at end of file
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlCompiler.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlCompiler.kt
index 4b03bb9..9cea44e 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlCompiler.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlCompiler.kt
@@ -31,6 +31,7 @@
                 aidlCompilerPath.toString(),
                 "--structured",
                 "--lang=java",
+                "--include=$workingDir",
                 "--out=$workingDir",
                 *sources.map(Path::toString).toTypedArray()
             )
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
index 848a845..1aba77b 100644
--- a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/generator/AidlGenerator.kt
@@ -25,17 +25,33 @@
 import java.nio.file.Path
 import java.nio.file.Paths
 
-class AidlGenerator(
-    private val aidlCompiler: AidlCompiler
+class AidlGenerator private constructor(
+    private val aidlCompiler: AidlCompiler,
+    private val api: ParsedApi,
+    private val workingDir: Path,
 ) {
-    fun generate(api: ParsedApi, workingDir: Path): List<GeneratedSource> {
-        val aidlInterfaces = generateAidlInterfaces(api, workingDir)
-        return compileAidlInterfaces(aidlInterfaces, workingDir)
+    init {
+        check(api.services.count() <= 1) { "Multiple services are not supported." }
     }
 
-    private fun generateAidlInterfaces(api: ParsedApi, workingDir: Path): List<GeneratedSource> {
+    companion object {
+        fun generate(
+            aidlCompiler: AidlCompiler,
+            api: ParsedApi,
+            workingDir: Path,
+        ): List<GeneratedSource> {
+            return AidlGenerator(aidlCompiler, api, workingDir).generate()
+        }
+    }
+
+    private fun generate(): List<GeneratedSource> {
+        if (api.services.isEmpty()) return listOf()
+        return compileAidlInterfaces(generateAidlInterfaces())
+    }
+
+    private fun generateAidlInterfaces(): List<GeneratedSource> {
         workingDir.toFile().ensureDirectory()
-        val aidlSources = generateAidlContent(api).map {
+        val aidlSources = generateAidlContent().map {
             val aidlFile = getAidlFile(workingDir, it)
             aidlFile.parentFile.mkdirs()
             aidlFile.createNewFile()
@@ -45,10 +61,7 @@
         return aidlSources
     }
 
-    private fun compileAidlInterfaces(
-        aidlSources: List<GeneratedSource>,
-        workingDir: Path
-    ): List<GeneratedSource> {
+    private fun compileAidlInterfaces(aidlSources: List<GeneratedSource>): List<GeneratedSource> {
         aidlCompiler.compile(workingDir, aidlSources.map { it.file.toPath() })
         val javaSources = aidlSources.map {
             GeneratedSource(
@@ -65,41 +78,83 @@
         return javaSources
     }
 
-    private fun generateAidlContent(api: ParsedApi) =
-        api.services.map { service ->
+    private fun generateAidlContent(): List<InMemorySource> {
+        // TODO: implement better tooling to generate AIDL (AidlPoet).
+        val transactionCallbacks = generateTransactionCallbacks()
+        val service =
             InMemorySource(
-                service.packageName,
-                aidlNameForInterface(service),
-                generateAidlService(service)
+                packageName(),
+                service().aidlName(),
+                generateAidlService(transactionCallbacks)
             )
-        }
-
-    private fun generateAidlService(service: AnnotatedInterface): String {
-        val generatedMethods = service.methods.joinToString(
-            separator = "\n\t",
-            transform = ::generateAidlMethod
-        )
-        return """
-                package ${service.packageName};
-                oneway interface ${aidlNameForInterface(service)} {
-                    $generatedMethods
-                }
-            """.trimIndent()
+        return transactionCallbacks + generateICancellationSignal() + service
     }
 
-    private fun generateAidlMethod(method: Method) =
-        "void ${method.name}" +
-            "(${method.parameters.joinToString(transform = ::generateAidlParameter)});"
+    private fun generateAidlService(
+        transactionCallbacks: List<InMemorySource>
+    ): String {
+        val transactionCallbackImports =
+            transactionCallbacks.map {
+                "import ${it.packageName}.${it.interfaceName};"
+            }.sorted().joinToString(separator = "\n|")
+        val generatedMethods = service().methods.map(::generateAidlMethod).sorted()
+            .joinToString("\n|    ")
+        return """
+                |package ${packageName()};
+                |$transactionCallbackImports
+                |interface ${service().aidlName()} {
+                |    $generatedMethods
+                |}
+            """.trimMargin()
+    }
+
+    private fun generateAidlMethod(method: Method): String {
+        val parameters =
+            method.parameters.map(::generateAidlParameter) +
+                "${method.returnType.transactionCallbackName()} transactionCallback"
+        // TODO remove return type.
+        return "${method.returnType.toAidlType()} ${method.name}(${parameters.joinToString()});"
+    }
 
     private fun generateAidlParameter(parameter: Parameter) =
+        // TODO validate that parameter type is not Unit
         "${parameter.type.toAidlType()} ${parameter.name}"
 
-    private fun getAidlFile(rootPath: Path, aidlSource: InMemorySource) =
-        Paths.get(
-            rootPath.toString(),
-            *aidlSource.packageName.split(".").toTypedArray(),
-            aidlSource.interfaceName + ".aidl"
-        ).toFile()
+    private fun generateTransactionCallbacks(): List<InMemorySource> {
+        return service().methods.map(Method::returnType).toSet()
+            .map { generateTransactionCallback(it) }
+    }
+
+    private fun generateTransactionCallback(type: Type): InMemorySource {
+        val interfaceName = type.transactionCallbackName()
+        val  { if (it == "void") "" else "$it result" }
+        return InMemorySource(
+            packageName = packageName(), interfaceName = interfaceName, fileContents = """
+                    package ${packageName()};
+                    import ${packageName()}.ICancellationSignal;
+                    oneway interface $interfaceName {
+                        void onCancellable(ICancellationSignal cancellationSignal);
+                        void onSuccess($onSuccessParameter);
+                        void onFailure(int errorCode, String errorMessage);
+                    }
+                """.trimIndent()
+        )
+    }
+
+    private fun generateICancellationSignal() = InMemorySource(
+        packageName = packageName(), interfaceName = "ICancellationSignal", fileContents = """
+                package ${packageName()};
+                oneway interface ICancellationSignal {
+                    void cancel();
+                }
+            """.trimIndent()
+    )
+
+    private fun getAidlFile(rootPath: Path, aidlSource: InMemorySource) = Paths.get(
+        rootPath.toString(),
+        *aidlSource.packageName.split(".").toTypedArray(),
+        aidlSource.interfaceName + ".aidl"
+    ).toFile()
 
     private fun getJavaFileForAidlFile(aidlFile: File): File {
         check(aidlFile.extension == "aidl") {
@@ -108,8 +163,9 @@
         return aidlFile.resolveSibling("${aidlFile.nameWithoutExtension}.java")
     }
 
-    private fun aidlNameForInterface(annotatedInterface: AnnotatedInterface) =
-        "I${annotatedInterface.name}"
+    private fun service() = api.services.first()
+
+    private fun packageName() = service().packageName
 }
 
 data class InMemorySource(
@@ -133,16 +189,20 @@
     }
 }
 
-internal fun Type.toAidlType() =
-    when (name) {
-        Boolean::class.qualifiedName -> "boolean"
-        Int::class.qualifiedName -> "int"
-        Long::class.qualifiedName -> "long"
-        Float::class.qualifiedName -> "float"
-        Double::class.qualifiedName -> "double"
-        String::class.qualifiedName -> "string"
-        Char::class.qualifiedName -> "char"
-        Short::class.qualifiedName -> "short"
-        Unit::class.qualifiedName -> "void"
-        else -> throw IllegalArgumentException("Unsupported type conversion ${this.name}")
-    }
+fun AnnotatedInterface.aidlName() = "I$name"
+
+fun Type.transactionCallbackName() = "I${name.split(".").last()}TransactionCallback"
+
+internal fun Type.toAidlType() = when (name) {
+    Boolean::class.qualifiedName -> "boolean"
+    Int::class.qualifiedName -> "int"
+    Long::class.qualifiedName -> "long"
+    Float::class.qualifiedName -> "float"
+    Double::class.qualifiedName -> "double"
+    String::class.qualifiedName -> "String"
+    Char::class.qualifiedName -> "char"
+    // TODO: AIDL doesn't support short, make sure it's handled correctly.
+    Short::class.qualifiedName -> "int"
+    Unit::class.qualifiedName -> "void"
+    else -> throw IllegalArgumentException("Unsupported type conversion ${this.name}")
+}
diff --git a/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/poet/KotlinPoetSpecs.kt b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/poet/KotlinPoetSpecs.kt
new file mode 100644
index 0000000..c83188c
--- /dev/null
+++ b/privacysandbox/tools/tools-core/src/main/java/androidx/privacysandbox/tools/core/poet/KotlinPoetSpecs.kt
@@ -0,0 +1,86 @@
+/*
+ * 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.privacysandbox.tools.core.poet
+
+import androidx.privacysandbox.tools.core.Parameter
+import androidx.privacysandbox.tools.core.Type
+import com.squareup.kotlinpoet.ClassName
+import com.squareup.kotlinpoet.CodeBlock
+import com.squareup.kotlinpoet.FileSpec
+import com.squareup.kotlinpoet.FunSpec
+import com.squareup.kotlinpoet.KModifier
+import com.squareup.kotlinpoet.ParameterSpec
+import com.squareup.kotlinpoet.PropertySpec
+import com.squareup.kotlinpoet.TypeName
+import com.squareup.kotlinpoet.TypeSpec
+
+/** [ParameterSpec] equivalent to this parameter. */
+public fun Parameter.poetSpec(): ParameterSpec {
+    return ParameterSpec.builder(name, type.poetSpec()).build()
+}
+
+/** [TypeName] equivalent to this parameter. */
+public fun Type.poetSpec(): TypeName {
+    val splits = name.split('.')
+    return ClassName(splits.dropLast(1).joinToString("."), splits.last())
+}
+
+/**
+ * Defines the primary constructor of this type with the given list of properties.
+ *
+ * @param modifiers extra modifiers added to the constructor
+ */
+public fun TypeSpec.Builder.primaryConstructor(
+    properties: List<PropertySpec>,
+    vararg modifiers: KModifier,
+) {
+    val propertiesWithInitializer =
+        properties.map {
+            it.toBuilder().initializer(it.name)
+                .build()
+        }
+    primaryConstructor(
+        FunSpec.constructorBuilder().build {
+            addParameters(propertiesWithInitializer.map { ParameterSpec(it.name, it.type) })
+            addModifiers(*modifiers)
+        }
+    )
+    addProperties(propertiesWithInitializer)
+}
+
+/** Builds a [TypeSpec] using the given builder block. */
+public fun TypeSpec.Builder.build(block: TypeSpec.Builder.() -> Unit): TypeSpec {
+    block()
+    return build()
+}
+
+public fun CodeBlock.Builder.build(block: CodeBlock.Builder.() -> Unit): CodeBlock {
+    block()
+    return build()
+}
+
+/** Builds a [FunSpec] using the given builder block. */
+public fun FunSpec.Builder.build(block: FunSpec.Builder.() -> Unit): FunSpec {
+    block()
+    return build()
+}
+
+/** Builds a [FileSpec] using the given builder block. */
+public fun FileSpec.Builder.build(block: FileSpec.Builder.() -> Unit): FileSpec {
+    block()
+    return build()
+}
diff --git a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlGeneratorTest.kt b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlGeneratorTest.kt
index 42800d2..d8556af 100644
--- a/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlGeneratorTest.kt
+++ b/privacysandbox/tools/tools-core/src/test/java/androidx/privacysandbox/tools/core/generator/AidlGeneratorTest.kt
@@ -47,21 +47,60 @@
                             name = "doStuff",
                             parameters = listOf(
                                 Parameter(
-                                    name = "x",
+                                    name = "a",
+                                    type = Type(
+                                        name = "kotlin.Boolean",
+                                    )
+                                ),
+                                Parameter(
+                                    name = "b",
                                     type = Type(
                                         name = "kotlin.Int",
                                     )
                                 ),
                                 Parameter(
-                                    name = "y",
+                                    name = "c",
                                     type = Type(
-                                        name = "kotlin.Int",
+                                        name = "kotlin.Long",
+                                    )
+                                ),
+                                Parameter(
+                                    name = "d",
+                                    type = Type(
+                                        name = "kotlin.Float",
+                                    )
+                                ),
+                                Parameter(
+                                    name = "e",
+                                    type = Type(
+                                        name = "kotlin.Double",
+                                    )
+                                ),
+                                Parameter(
+                                    name = "f",
+                                    type = Type(
+                                        name = "kotlin.Char",
+                                    )
+                                ),
+                                Parameter(
+                                    name = "g",
+                                    type = Type(
+                                        name = "kotlin.Short",
                                     )
                                 )
                             ),
                             returnType = Type(
                                 name = "kotlin.String",
-                            )
+                            ),
+                            isSuspend = true,
+                        ),
+                        Method(
+                            name = "doMoreStuff",
+                            parameters = listOf(),
+                            returnType = Type(
+                                name = "kotlin.Unit",
+                            ),
+                            isSuspend = false,
                         )
                     )
                 )
@@ -72,29 +111,64 @@
         val tmpDir = createTempDirectory("test")
         val aidlCompiler = AidlCompiler(aidlPath)
 
-        val javaGeneratedSources = AidlGenerator(aidlCompiler).generate(api, tmpDir)
+        val javaGeneratedSources = AidlGenerator.generate(aidlCompiler, api, tmpDir)
 
         // Check expected java sources were generated.
         assertThat(javaGeneratedSources.map { it.packageName to it.interfaceName })
-            .containsExactly("com.mysdk" to "IMySdk")
+            .containsExactly(
+                "com.mysdk" to "IMySdk",
+                "com.mysdk" to "IStringTransactionCallback",
+                "com.mysdk" to "IUnitTransactionCallback",
+                "com.mysdk" to "ICancellationSignal",
+            )
 
         // Check the contents of the AIDL generated files.
-        val aidlGeneratedFiles = tmpDir.toFile().walk().filter { it.extension == "aidl" }.toList()
-        assertThat(aidlGeneratedFiles).hasSize(1)
-        assertThat(aidlGeneratedFiles.first().readText()).isEqualTo(
-            """
+        val aidlGeneratedFiles = tmpDir.toFile().walk().filter { it.extension == "aidl" }
+            .map { it.name to it.readText() }.toList()
+        assertThat(aidlGeneratedFiles).containsExactly(
+            "IMySdk.aidl" to """
                 package com.mysdk;
-                oneway interface IMySdk {
-                    void doStuff(int x, int y);
+                import com.mysdk.IStringTransactionCallback;
+                import com.mysdk.IUnitTransactionCallback;
+                interface IMySdk {
+                    String doStuff(boolean a, int b, long c, float d, double e, char f, int g, IStringTransactionCallback transactionCallback);
+                    void doMoreStuff(IUnitTransactionCallback transactionCallback);
                 }
-            """.trimIndent()
+            """.trimIndent(),
+            "ICancellationSignal.aidl" to """
+                package com.mysdk;
+                oneway interface ICancellationSignal {
+                    void cancel();
+                }
+            """.trimIndent(),
+            "IStringTransactionCallback.aidl" to """
+                package com.mysdk;
+                import com.mysdk.ICancellationSignal;
+                oneway interface IStringTransactionCallback {
+                    void onCancellable(ICancellationSignal cancellationSignal);
+                    void onSuccess(String result);
+                    void onFailure(int errorCode, String errorMessage);
+                }
+            """.trimIndent(),
+            "IUnitTransactionCallback.aidl" to """
+                package com.mysdk;
+                import com.mysdk.ICancellationSignal;
+                oneway interface IUnitTransactionCallback {
+                    void onCancellable(ICancellationSignal cancellationSignal);
+                    void onSuccess();
+                    void onFailure(int errorCode, String errorMessage);
+                }
+            """.trimIndent(),
         )
 
         // Check that the Java generated sources compile.
         ensureCompiles(
-            listOf(
-                Source.java("com/mysdk/IMySdk", javaGeneratedSources.first().file.readText())
-            )
+            javaGeneratedSources.map {
+                Source.java(
+                    "${it.packageName.replace('.', '/')}/${it.interfaceName}",
+                    it.file.readText()
+                )
+            }.toList()
         )
     }
 
diff --git a/recyclerview/recyclerview/api/current.ignore b/recyclerview/recyclerview/api/current.ignore
deleted file mode 100644
index f5042d6..0000000
--- a/recyclerview/recyclerview/api/current.ignore
+++ /dev/null
@@ -1,135 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.recyclerview.widget.AdapterListUpdateCallback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.AdapterListUpdateCallback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.BatchingListUpdateCallback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.BatchingListUpdateCallback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.DefaultItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, int arg2, int arg3, int arg4, int arg5)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DividerItemDecoration#DividerItemDecoration(android.content.Context, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DividerItemDecoration(android.content.Context arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchHelper.Callback#chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>, int, int):
-    Attempted to remove @Nullable annotation from method androidx.recyclerview.widget.ItemTouchHelper.Callback.chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder,java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>,int,int)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchHelper.Callback#onChildDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchHelper.Callback.onChildDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, androidx.recyclerview.widget.RecyclerView.ViewHolder arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#clearView(android.view.View) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.clearView(android.view.View arg1)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onSelected(android.view.View) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onSelected(android.view.View arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, android.util.AttributeSet, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, android.util.AttributeSet arg2, int arg3, int arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, android.util.AttributeSet, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, android.util.AttributeSet arg2, int arg3, int arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, int arg2, boolean arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#computeScrollVectorForPosition(int):
-    Attempted to remove @Nullable annotation from method androidx.recyclerview.widget.LinearLayoutManager.computeScrollVectorForPosition(int)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#generateDefaultLayoutParams():
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.LinearLayoutManager.generateDefaultLayoutParams()
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#LinearSmoothScroller(android.content.Context) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller(android.content.Context arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateDxToMakeVisible(android.view.View, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateDxToMakeVisible(android.view.View arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateDyToMakeVisible(android.view.View, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateDyToMakeVisible(android.view.View arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateSpeedPerPixel(android.util.DisplayMetrics) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateSpeedPerPixel(android.util.DisplayMetrics arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#mTargetVector:
-    Attempted to remove @Nullable annotation from field androidx.recyclerview.widget.LinearSmoothScroller.mTargetVector
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.LinearSmoothScroller.onSeekTargetStep(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #3:
-    Attempted to remove @NonNull annotation from parameter arg4 in androidx.recyclerview.widget.LinearSmoothScroller.onSeekTargetStep(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg1)
-InvalidNullConversion: androidx.recyclerview.widget.PagerSnapHelper#findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.PagerSnapHelper.findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1)
-InvalidNullConversion: androidx.recyclerview.widget.PagerSnapHelper#findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.PagerSnapHelper.findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1, int arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#assertNotInLayoutOrScroll(String) parameter #0:
-    Attempted to remove @Nullable annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.assertNotInLayoutOrScroll(String arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectAdjacentPrefetchPositions(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg4)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #3:
-    Attempted to remove @NonNull annotation from parameter arg4 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectAdjacentPrefetchPositions(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg4)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectInitialPrefetchPositions(int arg1, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#generateDefaultLayoutParams():
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.RecyclerView.LayoutManager.generateDefaultLayoutParams()
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.Recycler) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.Recycler) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler arg1, androidx.recyclerview.widget.RecyclerView.State arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler arg1, androidx.recyclerview.widget.RecyclerView.State arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onRestoreInstanceState(android.os.Parcelable) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onRestoreInstanceState(android.os.Parcelable arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollHorizontallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollHorizontallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollVerticallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollVerticallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.State arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, int) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.State arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.SimpleItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, int arg2, int arg3, int arg4, int arg5)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#calculateScrollDistance(int, int):
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.SnapHelper.calculateScrollDistance(int,int)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SnapHelper.findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SnapHelper.findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1, int arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SortedList.BatchedCallback#BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2>) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2> arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SortedList.Callback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.SortedList.Callback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SortedListAdapterCallback#SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?>) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?> arg1)
diff --git a/recyclerview/recyclerview/api/restricted_current.ignore b/recyclerview/recyclerview/api/restricted_current.ignore
deleted file mode 100644
index f5042d6..0000000
--- a/recyclerview/recyclerview/api/restricted_current.ignore
+++ /dev/null
@@ -1,135 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.recyclerview.widget.AdapterListUpdateCallback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.AdapterListUpdateCallback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.BatchingListUpdateCallback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.BatchingListUpdateCallback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.DefaultItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, int arg2, int arg3, int arg4, int arg5)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DefaultItemAnimator#endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DefaultItemAnimator.endAnimation(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.DividerItemDecoration#DividerItemDecoration(android.content.Context, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.DividerItemDecoration(android.content.Context arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchHelper.Callback#chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder, java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>, int, int):
-    Attempted to remove @Nullable annotation from method androidx.recyclerview.widget.ItemTouchHelper.Callback.chooseDropTarget(androidx.recyclerview.widget.RecyclerView.ViewHolder,java.util.List<androidx.recyclerview.widget.RecyclerView.ViewHolder>,int,int)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchHelper.Callback#onChildDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.ViewHolder, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchHelper.Callback.onChildDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, androidx.recyclerview.widget.RecyclerView.ViewHolder arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#clearView(android.view.View) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.clearView(android.view.View arg1)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDraw(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchUIUtil.onDraw(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onDrawOver(android.graphics.Canvas, androidx.recyclerview.widget.RecyclerView, android.view.View, float, float, int, boolean) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.ItemTouchUIUtil.onDrawOver(android.graphics.Canvas arg1, androidx.recyclerview.widget.RecyclerView arg2, android.view.View arg3, float arg4, float arg5, int arg6, boolean arg7)
-InvalidNullConversion: androidx.recyclerview.widget.ItemTouchUIUtil#onSelected(android.view.View) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.ItemTouchUIUtil.onSelected(android.view.View arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, android.util.AttributeSet, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, android.util.AttributeSet arg2, int arg3, int arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, android.util.AttributeSet, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, android.util.AttributeSet arg2, int arg3, int arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#LinearLayoutManager(android.content.Context, int, boolean) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearLayoutManager(android.content.Context arg1, int arg2, boolean arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#computeScrollVectorForPosition(int):
-    Attempted to remove @Nullable annotation from method androidx.recyclerview.widget.LinearLayoutManager.computeScrollVectorForPosition(int)
-InvalidNullConversion: androidx.recyclerview.widget.LinearLayoutManager#generateDefaultLayoutParams():
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.LinearLayoutManager.generateDefaultLayoutParams()
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#LinearSmoothScroller(android.content.Context) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller(android.content.Context arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateDxToMakeVisible(android.view.View, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateDxToMakeVisible(android.view.View arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateDyToMakeVisible(android.view.View, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateDyToMakeVisible(android.view.View arg1, int arg2)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#calculateSpeedPerPixel(android.util.DisplayMetrics) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.calculateSpeedPerPixel(android.util.DisplayMetrics arg1)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#mTargetVector:
-    Attempted to remove @Nullable annotation from field androidx.recyclerview.widget.LinearSmoothScroller.mTargetVector
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.LinearSmoothScroller.onSeekTargetStep(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onSeekTargetStep(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #3:
-    Attempted to remove @NonNull annotation from parameter arg4 in androidx.recyclerview.widget.LinearSmoothScroller.onSeekTargetStep(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg4)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#onTargetFound(android.view.View, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.LinearSmoothScroller.onTargetFound(android.view.View arg1, androidx.recyclerview.widget.RecyclerView.State arg2, androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg3)
-InvalidNullConversion: androidx.recyclerview.widget.LinearSmoothScroller#updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.LinearSmoothScroller.updateActionForInterimTarget(androidx.recyclerview.widget.RecyclerView.SmoothScroller.Action arg1)
-InvalidNullConversion: androidx.recyclerview.widget.PagerSnapHelper#findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.PagerSnapHelper.findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1)
-InvalidNullConversion: androidx.recyclerview.widget.PagerSnapHelper#findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.PagerSnapHelper.findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1, int arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#assertNotInLayoutOrScroll(String) parameter #0:
-    Attempted to remove @Nullable annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.assertNotInLayoutOrScroll(String arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectAdjacentPrefetchPositions(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg4)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectAdjacentPrefetchPositions(int, int, androidx.recyclerview.widget.RecyclerView.State, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #3:
-    Attempted to remove @NonNull annotation from parameter arg4 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectAdjacentPrefetchPositions(int arg1, int arg2, androidx.recyclerview.widget.RecyclerView.State arg3, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg4)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#collectInitialPrefetchPositions(int, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.collectInitialPrefetchPositions(int arg1, androidx.recyclerview.widget.RecyclerView.LayoutManager.LayoutPrefetchRegistry arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#generateDefaultLayoutParams():
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.RecyclerView.LayoutManager.generateDefaultLayoutParams()
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.Recycler) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.Recycler) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onDetachedFromWindow(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler arg1, androidx.recyclerview.widget.RecyclerView.State arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutChildren(androidx.recyclerview.widget.RecyclerView.Recycler arg1, androidx.recyclerview.widget.RecyclerView.State arg2)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onLayoutCompleted(androidx.recyclerview.widget.RecyclerView.State arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#onRestoreInstanceState(android.os.Parcelable) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.onRestoreInstanceState(android.os.Parcelable arg1)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollHorizontallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollHorizontallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollHorizontallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollVerticallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#scrollVerticallyBy(int, androidx.recyclerview.widget.RecyclerView.Recycler, androidx.recyclerview.widget.RecyclerView.State) parameter #2:
-    Attempted to remove @NonNull annotation from parameter arg3 in androidx.recyclerview.widget.RecyclerView.LayoutManager.scrollVerticallyBy(int arg1, androidx.recyclerview.widget.RecyclerView.Recycler arg2, androidx.recyclerview.widget.RecyclerView.State arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.RecyclerView.LayoutManager.smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.State arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.RecyclerView.LayoutManager#smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView, androidx.recyclerview.widget.RecyclerView.State, int) parameter #1:
-    Attempted to remove @NonNull annotation from parameter arg2 in androidx.recyclerview.widget.RecyclerView.LayoutManager.smoothScrollToPosition(androidx.recyclerview.widget.RecyclerView arg1, androidx.recyclerview.widget.RecyclerView.State arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateAdd(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder, androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #1:
-    Attempted to remove @Nullable annotation from parameter arg2 in androidx.recyclerview.widget.SimpleItemAnimator.animateChange(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, androidx.recyclerview.widget.RecyclerView.ViewHolder arg2, int arg3, int arg4, int arg5, int arg6)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder, int, int, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateMove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1, int arg2, int arg3, int arg4, int arg5)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.animateRemove(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SimpleItemAnimator#dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SimpleItemAnimator.dispatchRemoveFinished(androidx.recyclerview.widget.RecyclerView.ViewHolder arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#calculateScrollDistance(int, int):
-    Attempted to remove @NonNull annotation from method androidx.recyclerview.widget.SnapHelper.calculateScrollDistance(int,int)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SnapHelper.findSnapView(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SnapHelper#findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager, int, int) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SnapHelper.findTargetSnapPosition(androidx.recyclerview.widget.RecyclerView.LayoutManager arg1, int arg2, int arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SortedList.BatchedCallback#BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2>) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SortedList.BatchedCallback(androidx.recyclerview.widget.SortedList.Callback<T2> arg1)
-InvalidNullConversion: androidx.recyclerview.widget.SortedList.Callback#onChanged(int, int, Object) parameter #2:
-    Attempted to remove @Nullable annotation from parameter arg3 in androidx.recyclerview.widget.SortedList.Callback.onChanged(int arg1, int arg2, Object arg3)
-InvalidNullConversion: androidx.recyclerview.widget.SortedListAdapterCallback#SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?>) parameter #0:
-    Attempted to remove @NonNull annotation from parameter arg1 in androidx.recyclerview.widget.SortedListAdapterCallback(androidx.recyclerview.widget.RecyclerView.Adapter<?> arg1)
diff --git a/recyclerview/recyclerview/build.gradle b/recyclerview/recyclerview/build.gradle
index 6a72c31..d1a017d 100644
--- a/recyclerview/recyclerview/build.gradle
+++ b/recyclerview/recyclerview/build.gradle
@@ -11,7 +11,7 @@
     api "androidx.core:core:1.7.0"
     implementation("androidx.collection:collection:1.0.0")
     api("androidx.customview:customview:1.0.0")
-    implementation("androidx.customview:customview-poolingcontainer:1.0.0-beta02")
+    implementation("androidx.customview:customview-poolingcontainer:1.0.0")
 
     androidTestImplementation(libs.testExtJunit)
     androidTestImplementation(libs.testCore)
diff --git a/room/integration-tests/kotlintestapp/build.gradle b/room/integration-tests/kotlintestapp/build.gradle
index 9daf078..e23da19 100644
--- a/room/integration-tests/kotlintestapp/build.gradle
+++ b/room/integration-tests/kotlintestapp/build.gradle
@@ -114,6 +114,7 @@
     androidTestImplementation("androidx.paging:paging-runtime:3.1.1")
     androidTestImplementation(projectOrArtifact(":lifecycle:lifecycle-runtime-testing"))
     androidTestImplementation(libs.rxjava2)
+    androidTestImplementation(libs.kotlinCoroutinesTest)
     testImplementation(libs.mockitoCore)
 }
 
diff --git a/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
new file mode 100644
index 0000000..157aac5
--- /dev/null
+++ b/room/integration-tests/kotlintestapp/src/androidTest/java/androidx/room/integration/kotlintestapp/test/WriteAheadLoggingKotlinTest.kt
@@ -0,0 +1,103 @@
+/*
+ * 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.room.androidx.room.integration.kotlintestapp.test
+
+import android.content.Context
+import androidx.arch.core.executor.testing.CountingTaskExecutorRule
+import androidx.lifecycle.Observer
+import androidx.room.Room
+import androidx.room.RoomDatabase
+import androidx.room.integration.kotlintestapp.TestDatabase
+import androidx.room.integration.kotlintestapp.dao.BooksDao
+import androidx.room.integration.kotlintestapp.test.TestUtil
+import androidx.room.integration.kotlintestapp.test.TestUtil.Companion.BOOK_1
+import androidx.room.integration.kotlintestapp.vo.Book
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.LargeTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.platform.app.InstrumentationRegistry
+import java.util.concurrent.TimeUnit
+import junit.framework.TestCase.assertEquals
+import kotlin.test.assertTrue
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.filterNotNull
+import kotlinx.coroutines.flow.first
+import kotlinx.coroutines.test.runTest
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.withTimeout
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@OptIn(ExperimentalCoroutinesApi::class)
+@RunWith(AndroidJUnit4::class)
+@LargeTest
+@SdkSuppress(minSdkVersion = 16)
+class WriteAheadLoggingKotlinTest {
+    @get:Rule
+    val countingTaskExecutorRule = CountingTaskExecutorRule()
+
+    private suspend fun withDb(fn: suspend (TestDatabase) -> Unit) {
+        val context = ApplicationProvider.getApplicationContext<Context>()
+        val dbName = "observe.db"
+        context.deleteDatabase(dbName)
+        val db = Room.databaseBuilder(context, TestDatabase::class.java, dbName)
+            .setJournalMode(RoomDatabase.JournalMode.WRITE_AHEAD_LOGGING).build()
+        try {
+            fn(db)
+        } finally {
+            countingTaskExecutorRule.drainTasks(500, TimeUnit.MILLISECONDS)
+            assertTrue(countingTaskExecutorRule.isIdle)
+
+            db.close()
+            context.deleteDatabase(dbName)
+        }
+    }
+
+    private fun runDbTest(fn: suspend (TestDatabase) -> Unit) = runTest { withDb(fn) }
+
+    @Test
+    fun observeLiveData() = runDbTest { db ->
+        val dao: BooksDao = db.booksDao()
+        dao.insertPublisherSuspend(TestUtil.PUBLISHER.publisherId, TestUtil.PUBLISHER.name)
+
+        val booksSeen = MutableStateFlow<Book?>(null)
+        val liveData = dao.getBookLiveData(BOOK_1.bookId)
+
+        val observer = Observer<Book> { booksSeen.value = it }
+
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            liveData.observeForever(observer)
+        }
+
+        dao.insertBookSuspend(BOOK_1)
+
+        val firstBookSeen = withContext(Dispatchers.Default) {
+            withTimeout(3000) {
+                booksSeen.filterNotNull().first()
+            }
+        }
+
+        assertEquals(BOOK_1.bookId, firstBookSeen.bookId)
+        InstrumentationRegistry.getInstrumentation().runOnMainSync {
+            liveData.removeObserver(observer)
+        }
+    }
+}
diff --git a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
index 602708d..285765f 100644
--- a/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
+++ b/room/integration-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/WriteAheadLoggingTest.java
@@ -135,7 +135,7 @@
         }
     }
 
-    @FlakyTest(bugId = 241095868)
+    @Ignore("b/241095868")
     @Test
     public void observeLiveData() {
         UserDao dao = mDatabase.getUserDao();
@@ -147,7 +147,7 @@
         stopObserver(user1, observer);
     }
 
-    @FlakyTest(bugId = 241095868)
+    @Ignore("b/241095868")
     @Test
     public void observeLiveDataWithTransaction() {
         UserDao dao = mDatabase.getUserDao();
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
index 6af7d8d..63c2de1 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/CompilationResultSubject.kt
@@ -29,6 +29,7 @@
 import com.google.common.truth.Subject.Factory
 import com.google.common.truth.Truth
 import com.google.testing.compile.Compilation
+import java.util.regex.Pattern
 import javax.tools.Diagnostic
 
 /**
@@ -203,6 +204,23 @@
         }
 
     /**
+     * Asserts that compilation has a warning containing text that matches the given pattern.
+     *
+     * @see hasErrorContainingMatch
+     * @see hasNoteContainingMatch
+     */
+    fun hasWarningContainingMatch(expectedPattern: String): DiagnosticMessagesSubject {
+        shouldSucceed = false
+        return hasDiagnosticWithPattern(
+            kind = Diagnostic.Kind.WARNING,
+            expectedPattern = expectedPattern,
+            acceptPartialMatch = true
+        ) {
+            "expected warning containing pattern: $expectedPattern"
+        }
+    }
+
+    /**
      * Asserts that compilation has a note with the given text.
      *
      * @see hasError
@@ -233,6 +251,23 @@
         }
 
     /**
+     * Asserts that compilation has a note containing text that matches the given pattern.
+     *
+     * @see hasErrorContainingMatch
+     * @see hasWarningContainingMatch
+     */
+    fun hasNoteContainingMatch(expectedPattern: String): DiagnosticMessagesSubject {
+        shouldSucceed = false
+        return hasDiagnosticWithPattern(
+            kind = Diagnostic.Kind.NOTE,
+            expectedPattern = expectedPattern,
+            acceptPartialMatch = true
+        ) {
+            "expected note containing pattern: $expectedPattern"
+        }
+    }
+
+    /**
      * Asserts that compilation has an error with the given text.
      *
      * @see hasWarning
@@ -267,6 +302,23 @@
     }
 
     /**
+     * Asserts that compilation has an error containing text that matches the given pattern.
+     *
+     * @see hasWarningContainingMatch
+     * @see hasNoteContainingMatch
+     */
+    fun hasErrorContainingMatch(expectedPattern: String): DiagnosticMessagesSubject {
+        shouldSucceed = false
+        return hasDiagnosticWithPattern(
+            kind = Diagnostic.Kind.ERROR,
+            expectedPattern = expectedPattern,
+            acceptPartialMatch = true
+        ) {
+            "expected error containing pattern: $expectedPattern"
+        }
+    }
+
+    /**
      * Asserts that compilation has at least one diagnostics message with kind error.
      *
      * @see compilationDidFail
@@ -375,12 +427,36 @@
         acceptPartialMatch: Boolean,
         buildErrorMessage: () -> String
     ): DiagnosticMessagesSubject {
+        fun String.trimLines() = lines().joinToString(System.lineSeparator()) { it.trim() }
+        val expectedTrimmed = expected.trimLines()
         val diagnostics = compilationResult.diagnosticsOfKind(kind)
         val matches = diagnostics.filter {
             if (acceptPartialMatch) {
-                it.msg.contains(expected)
+                it.msg.trimLines().contains(expectedTrimmed)
             } else {
-                it.msg == expected
+                it.msg.trimLines() == expectedTrimmed
+            }
+        }
+        if (matches.isEmpty()) {
+            failWithActual(simpleFact(buildErrorMessage()))
+        }
+        return DiagnosticMessagesSubject.assertThat(matches)
+    }
+
+    private fun hasDiagnosticWithPattern(
+        kind: Diagnostic.Kind,
+        expectedPattern: String,
+        acceptPartialMatch: Boolean,
+        buildErrorMessage: () -> String
+    ): DiagnosticMessagesSubject {
+        val diagnostics = compilationResult.diagnosticsOfKind(kind)
+        val pattern = Pattern.compile(expectedPattern)
+        val matches = diagnostics.filter {
+            val matcher = pattern.matcher(it.msg)
+            if (acceptPartialMatch) {
+                matcher.find()
+            } else {
+                matcher.matches()
             }
         }
         if (matches.isEmpty()) {
diff --git a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
index a01be29..30e21f1 100644
--- a/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
+++ b/room/room-compiler-processing-testing/src/test/java/androidx/room/compiler/processing/util/DiagnosticsTest.kt
@@ -44,6 +44,12 @@
                 hasNoteContaining("ote")
                 hasWarningContaining("arn")
                 hasErrorContaining("rror")
+                hasNoteContainingMatch("ote")
+                hasWarningContainingMatch("arn")
+                hasErrorContainingMatch("rror")
+                hasNoteContainingMatch("^note \\d$")
+                hasWarningContainingMatch("^warn \\d$")
+                hasErrorContainingMatch("^error \\d$")
                 // these should fail:
                 assertThat(
                     runCatching { hasNote("note") }.isFailure
@@ -63,6 +69,15 @@
                 assertThat(
                     runCatching { hasErrorContaining("warning") }.isFailure
                 ).isTrue()
+                assertThat(
+                    runCatching { hasNoteContainingMatch("error %d") }.isFailure
+                ).isTrue()
+                assertThat(
+                    runCatching { hasWarningContainingMatch("note %d") }.isFailure
+                ).isTrue()
+                assertThat(
+                    runCatching { hasErrorContainingMatch("warning %d") }.isFailure
+                ).isTrue()
             }
         }
     }
@@ -190,6 +205,30 @@
         cleanCompilationHasNoWarnings(source)
     }
 
+    @Test
+    fun diagnoticMessageCompareTrimmedLines() {
+        runTest { invocation ->
+            invocation.processingEnv.messager.run {
+                printMessage(Diagnostic.Kind.ERROR, "error: This is the first line\n" +
+                    "    This is the second line\n" +
+                    "    This is the third line")
+            }
+            invocation.assertCompilationResult {
+                hasError("error: This is the first line\n" +
+                    "      This is the second line\n" +
+                    "      This is the third line")
+
+                hasErrorContaining("   This is the second line  \n This is the third  ")
+
+                assertThat(
+                    runCatching { hasError("error: This is the \nfirst line" +
+                        "This is the \nsecond line" +
+                        "This is the third line") }.isFailure
+                ).isTrue()
+            }
+        }
+    }
+
     private fun cleanCompilationHasNoWarnings(
         vararg source: Source
     ) = cleanCompilationHasNoWarnings(options = emptyMap(), source = source)
diff --git a/savedstate/savedstate-ktx/api/current.ignore b/savedstate/savedstate-ktx/api/current.ignore
deleted file mode 100644
index b576716..0000000
--- a/savedstate/savedstate-ktx/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.savedstate.ViewKt#findViewTreeSavedStateRegistryOwner(android.view.View):
-    Attempted to remove @Nullable annotation from method androidx.savedstate.ViewKt.findViewTreeSavedStateRegistryOwner(android.view.View)
diff --git a/savedstate/savedstate-ktx/api/restricted_current.ignore b/savedstate/savedstate-ktx/api/restricted_current.ignore
deleted file mode 100644
index b576716..0000000
--- a/savedstate/savedstate-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.savedstate.ViewKt#findViewTreeSavedStateRegistryOwner(android.view.View):
-    Attempted to remove @Nullable annotation from method androidx.savedstate.ViewKt.findViewTreeSavedStateRegistryOwner(android.view.View)
diff --git a/settings.gradle b/settings.gradle
index fd24c42..77eea8d 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -383,6 +383,7 @@
 includeProject(":benchmark:benchmark-common")
 includeProject(":benchmark:benchmark-darwin", [BuildType.KMP])
 includeProject(":benchmark:benchmark-darwin-core", [BuildType.KMP])
+includeProject(":benchmark:benchmark-darwin-samples", [BuildType.KMP])
 includeProject(":benchmark:benchmark-gradle-plugin", "benchmark/gradle-plugin", [BuildType.MAIN])
 includeProject(":benchmark:benchmark-junit4")
 includeProject(":benchmark:benchmark-macro", [BuildType.MAIN, BuildType.COMPOSE])
@@ -599,6 +600,7 @@
 includeProject(":core:uwb:uwb", [BuildType.MAIN])
 includeProject(":core:uwb:uwb-rxjava3", [BuildType.MAIN])
 includeProject(":credentials:credentials", [BuildType.MAIN])
+includeProject(":credentials:credentials-play-services-auth", [BuildType.MAIN])
 includeProject(":cursoradapter:cursoradapter", [BuildType.MAIN])
 includeProject(":customview:customview", [BuildType.MAIN])
 includeProject(":customview:customview-poolingcontainer", [BuildType.MAIN, BuildType.COMPOSE])
@@ -623,6 +625,7 @@
 includeProject(":dynamicanimation", [BuildType.MAIN])
 includeProject(":dynamicanimation:dynamicanimation", [BuildType.MAIN])
 includeProject(":dynamicanimation:dynamicanimation-ktx", [BuildType.MAIN])
+includeProject(":emoji2:emoji2-emojipicker", [BuildType.MAIN])
 includeProject(":emoji:emoji", [BuildType.MAIN])
 includeProject(":emoji:emoji-appcompat", [BuildType.MAIN])
 includeProject(":emoji:emoji-bundled", [BuildType.MAIN])
@@ -767,6 +770,7 @@
 includeProject(":paging:paging-rxjava2-ktx", [BuildType.MAIN])
 includeProject(":paging:paging-rxjava3", [BuildType.MAIN])
 includeProject(":paging:paging-samples", "paging/samples", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":paging:paging-testing", [BuildType.MAIN])
 includeProject(":palette:palette", [BuildType.MAIN])
 includeProject(":palette:palette-ktx", [BuildType.MAIN])
 includeProject(":percentlayout:percentlayout", [BuildType.MAIN])
diff --git a/slice/slice-view/src/main/res/values-es-rUS/strings.xml b/slice/slice-view/src/main/res/values-es-rUS/strings.xml
index d89415a..6d9e794 100644
--- a/slice/slice-view/src/main/res/values-es-rUS/strings.xml
+++ b/slice/slice-view/src/main/res/values-es-rUS/strings.xml
@@ -22,14 +22,17 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostrar más"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Actualización: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> años</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> año</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> días</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> día</item>
     </plurals>
diff --git a/slice/slice-view/src/main/res/values-es/strings.xml b/slice/slice-view/src/main/res/values-es/strings.xml
index f479bf1..5cfad8d 100644
--- a/slice/slice-view/src/main/res/values-es/strings.xml
+++ b/slice/slice-view/src/main/res/values-es/strings.xml
@@ -22,14 +22,17 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostrar más"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Última actualización: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> años</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> año</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other">Hace <xliff:g id="ID_2">%d</xliff:g> días</item>
       <item quantity="one">Hace <xliff:g id="ID_1">%d</xliff:g> día</item>
     </plurals>
diff --git a/slice/slice-view/src/main/res/values-fr-rCA/strings.xml b/slice/slice-view/src/main/res/values-fr-rCA/strings.xml
index 497deaec..e5d9fe2 100644
--- a/slice/slice-view/src/main/res/values-fr-rCA/strings.xml
+++ b/slice/slice-view/src/main/res/values-fr-rCA/strings.xml
@@ -23,14 +23,17 @@
     <string name="abc_slice_updated" msgid="7932359091871934205">"Mis à jour : <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> an</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> ans</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> ans</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> j</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> j</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> j</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Impossible de se connecter"</string>
diff --git a/slice/slice-view/src/main/res/values-fr/strings.xml b/slice/slice-view/src/main/res/values-fr/strings.xml
index c708167..fe65cc1 100644
--- a/slice/slice-view/src/main/res/values-fr/strings.xml
+++ b/slice/slice-view/src/main/res/values-fr/strings.xml
@@ -23,14 +23,17 @@
     <string name="abc_slice_updated" msgid="7932359091871934205">"Mis à jour il y a <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> an</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> ans</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> ans</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
       <item quantity="one">Il y a <xliff:g id="ID_2">%d</xliff:g> jour</item>
+      <item quantity="many">Il y a <xliff:g id="ID_2">%d</xliff:g> jours</item>
       <item quantity="other">Il y a <xliff:g id="ID_2">%d</xliff:g> jours</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Impossible de se connecter"</string>
diff --git a/slice/slice-view/src/main/res/values-it/strings.xml b/slice/slice-view/src/main/res/values-it/strings.xml
index 9087d7a..9f4f52b 100644
--- a/slice/slice-view/src/main/res/values-it/strings.xml
+++ b/slice/slice-view/src/main/res/values-it/strings.xml
@@ -22,14 +22,17 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostra altro"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Ultimo aggiornamento: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> min fa</item>
       <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> min fa</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> anni fa</item>
       <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> anno fa</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> giorni fa</item>
       <item quantity="one"><xliff:g id="ID_1">%d</xliff:g> giorno fa</item>
     </plurals>
diff --git a/slice/slice-view/src/main/res/values-pt-rBR/strings.xml b/slice/slice-view/src/main/res/values-pt-rBR/strings.xml
index 4fd3a99..efe4af2 100644
--- a/slice/slice-view/src/main/res/values-pt-rBR/strings.xml
+++ b/slice/slice-view/src/main/res/values-pt-rBR/strings.xml
@@ -23,14 +23,17 @@
     <string name="abc_slice_updated" msgid="7932359091871934205">"Atualizado <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
       <item quantity="one">Há <xliff:g id="ID_2">%d</xliff:g>min</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g>min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
       <item quantity="one">Há <xliff:g id="ID_2">%d</xliff:g> ano</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> anos</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
       <item quantity="one"><xliff:g id="ID_2">%d</xliff:g> dia atrás</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> dias atrás</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Não foi possível conectar"</string>
diff --git a/slice/slice-view/src/main/res/values-pt-rPT/strings.xml b/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
index 34a67ff..d486321 100644
--- a/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
+++ b/slice/slice-view/src/main/res/values-pt-rPT/strings.xml
@@ -22,14 +22,17 @@
     <string name="abc_slice_show_more" msgid="1112789899890391107">"Mostrar mais"</string>
     <string name="abc_slice_updated" msgid="7932359091871934205">"Atualização: <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> min</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> anos</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> ano</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> dias</item>
       <item quantity="one">Há <xliff:g id="ID_1">%d</xliff:g> dia</item>
     </plurals>
diff --git a/slice/slice-view/src/main/res/values-pt/strings.xml b/slice/slice-view/src/main/res/values-pt/strings.xml
index 4fd3a99..efe4af2 100644
--- a/slice/slice-view/src/main/res/values-pt/strings.xml
+++ b/slice/slice-view/src/main/res/values-pt/strings.xml
@@ -23,14 +23,17 @@
     <string name="abc_slice_updated" msgid="7932359091871934205">"Atualizado <xliff:g id="TIME">%1$s</xliff:g>"</string>
     <plurals name="abc_slice_duration_min" formatted="false" msgid="7664017844210142826">
       <item quantity="one">Há <xliff:g id="ID_2">%d</xliff:g>min</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> min ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g>min</item>
     </plurals>
     <plurals name="abc_slice_duration_years" formatted="false" msgid="2628491538787454021">
       <item quantity="one">Há <xliff:g id="ID_2">%d</xliff:g> ano</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> yr ago</item>
       <item quantity="other">Há <xliff:g id="ID_2">%d</xliff:g> anos</item>
     </plurals>
     <plurals name="abc_slice_duration_days" formatted="false" msgid="8356547162075064530">
       <item quantity="one"><xliff:g id="ID_2">%d</xliff:g> dia atrás</item>
+      <item quantity="many"><xliff:g id="ID_2">%d</xliff:g> days ago</item>
       <item quantity="other"><xliff:g id="ID_2">%d</xliff:g> dias atrás</item>
     </plurals>
     <string name="abc_slice_error" msgid="1794214973158263497">"Não foi possível conectar"</string>
diff --git a/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
index 2fbc3da..78bd166 100644
--- a/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
+++ b/sqlite/sqlite-framework/src/androidTest/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabaseTest.kt
@@ -20,12 +20,12 @@
 import android.os.Build
 import androidx.annotation.RequiresApi
 import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.LargeTest
+import androidx.test.filters.SmallTest
 import com.google.common.truth.Truth.assertThat
 import org.junit.Before
 import org.junit.Test
 
-@LargeTest
+@SmallTest
 class FrameworkSQLiteDatabaseTest {
     private val dbName = "test.db"
     private val context: Context = ApplicationProvider.getApplicationContext()
@@ -37,16 +37,14 @@
         allowDataLossOnRecovery = false
     )
 
-    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
     @Before
     fun setup() {
         context.deleteDatabase(dbName)
-        openHelper.setWriteAheadLoggingEnabled(true)
     }
 
     @Test
     @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
-    fun testFrameWorkSQLiteDatabase_deleteWorks() {
+    fun testFrameWorkSQLiteDatabase_simpleDeleteWorks() {
         val db = openHelper.writableDatabase
         db.execSQL("create table user (idk int)")
 
@@ -68,4 +66,25 @@
             assertThat(it.count).isEqualTo(0)
         }
     }
+
+    @Test
+    @RequiresApi(Build.VERSION_CODES.JELLY_BEAN)
+    fun testFrameWorkSQLiteDatabase_deleteWorksWithWhereClause() {
+        val db = openHelper.writableDatabase
+        db.execSQL("create table user (idk int)")
+
+        val statement = db
+            .compileStatement("insert into user (idk) values (1)")
+        statement.executeInsert() // This should succeed
+
+        db.query("select * from user where idk=1").use {
+            assertThat(it.count).isEqualTo(1)
+        }
+
+        db.delete("user", "idk = ?", arrayOf(1))
+
+        db.query("select * from user where idk=1").use {
+            assertThat(it.count).isEqualTo(0)
+        }
+    }
 }
\ No newline at end of file
diff --git a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
index 794403ea..d7ae8cf 100644
--- a/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
+++ b/sqlite/sqlite-framework/src/main/java/androidx/sqlite/db/framework/FrameworkSQLiteDatabase.kt
@@ -189,7 +189,7 @@
         val query = buildString {
             append("DELETE FROM ")
             append(table)
-            if (whereClause?.isEmpty() == true) {
+            if (!whereClause.isNullOrEmpty()) {
                 append(" WHERE ")
                 append(whereClause)
             }
diff --git a/studiow b/studiow
index 35ea756..213da89 100755
--- a/studiow
+++ b/studiow
@@ -45,7 +45,7 @@
   echo
   echo
   echo " native"
-  echo "  Open the project subset for glance projects"
+  echo "  Open the project subset for native projects"
   echo
   echo " a, all"
   echo "  Open the project subset all"
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 795e746..7f42487 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
@@ -17,11 +17,18 @@
 package androidx.test.uiautomator.testapp;
 
 import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertNull;
+import static org.junit.Assert.assertTrue;
+
+import android.graphics.Point;
+import android.widget.TextView;
 
 import androidx.test.filters.LargeTest;
 import androidx.test.uiautomator.By;
 import androidx.test.uiautomator.UiObject2;
+import androidx.test.uiautomator.Until;
 
 import org.junit.Rule;
 import org.junit.Test;
@@ -30,6 +37,9 @@
 import org.w3c.dom.Element;
 
 import java.io.File;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Set;
 
 import javax.xml.parsers.DocumentBuilderFactory;
 import javax.xml.xpath.XPathConstants;
@@ -43,6 +53,115 @@
     public TemporaryFolder mTmpDir = new TemporaryFolder();
 
     @Test
+    public void testHasObject() {
+        launchTestActivity(MainActivity.class);
+
+        assertTrue(mDevice.hasObject(By.text("Accessible button")));
+        assertFalse(mDevice.hasObject(By.text("non-existent text")));
+    }
+
+    @Test
+    public void testFindObjects() {
+        launchTestActivity(MainActivity.class);
+
+        // The package name in the `By` selector needs to be specified, otherwise
+        // `mDevice.findObjects` will grab all the `TextView` in the display screen.
+        // Note that the items in `ListView` will also be `TextView`. So there are 9 `TextView`s
+        // in total.
+        List<UiObject2> results = mDevice.findObjects(By.pkg(TEST_APP).clazz(TextView.class));
+        assertEquals(9, results.size());
+
+        Set<String> resultTexts = new HashSet<>();
+        for (UiObject2 result : results) {
+            resultTexts.add(result.getText());
+        }
+        assertTrue(resultTexts.contains("Third Level"));
+        assertTrue(resultTexts.contains("Second Level"));
+        assertTrue(resultTexts.contains("First Level"));
+        assertTrue(resultTexts.contains("Sample text"));
+        assertTrue(resultTexts.contains("Text View 1"));
+        assertTrue(resultTexts.contains("TextView with an id"));
+        assertTrue(resultTexts.contains("Item1"));
+        assertTrue(resultTexts.contains("Item2"));
+        assertTrue(resultTexts.contains("Item3"));
+
+        assertEquals(0, mDevice.findObjects(By.text("non-existent text")).size());
+    }
+
+    @Test
+    public void testPerformActionAndWait() {
+        launchTestActivity(ClickAndWaitTestActivity.class);
+
+        UiObject2 button = mDevice.findObject(By.res(TEST_APP, "new_window_button"));
+        Point buttonCenter = button.getVisibleCenter();
+
+        assertTrue(mDevice.performActionAndWait(() -> mDevice.click(buttonCenter.x, buttonCenter.y),
+                Until.newWindow(), 10_000));
+    }
+
+    @Test
+    public void testSetCompressedLayoutHeirarchy() { // NOTYPO: already-existing typo
+        launchTestActivity(MainActivity.class);
+
+        mDevice.setCompressedLayoutHeirarchy(true); // NOTYPO
+        assertNull(mDevice.findObject(By.res(TEST_APP, "nested_elements")));
+
+        mDevice.setCompressedLayoutHeirarchy(false); // NOTYPO
+        assertNotNull(mDevice.findObject(By.res(TEST_APP, "nested_elements")));
+    }
+
+    /* TODO(b/235841020): Implement these tests, and the tests for exceptions of each tested method.
+
+    public void testGetInstance() {}
+
+    public void testGetInstance_withInstrumentation() {}
+
+    public void testGetDisplaySizeDp() {}
+
+    public void testGetProductName() {}
+
+    public void testGetLastTraversedText() {}
+
+    public void testClearLastTraversedText() {}
+
+    public void testPressMenu() {}
+
+    public void testPressBack() {}
+
+    public void testPressHome() {}
+
+    public void testPressSearch() {}
+
+    public void testPressDPadCenter() {}
+
+    public void testPressDPadDown() {}
+
+    public void testPressDPadUp() {}
+
+    public void testPressDPadLeft() {}
+
+    public void testPressDPadRight() {}
+
+    public void testPressDelete() {}
+
+    public void testPressEnter() {}
+
+    public void testPressKeyCode() {}
+
+    public void testPressKeyCode_withMetaState() {}
+
+    public void testPressRecentApps() {}
+
+    public void testOpenNotification() {}
+
+    public void testOpenQuickSettings() {}
+
+    public void testGetDisplayWidth() {}
+
+    public void testGetDisplayHeight() {}
+     */
+
+    @Test
     public void testClick() {
         launchTestActivity(UiDeviceTestClickActivity.class);
 
@@ -57,6 +176,61 @@
         assertEquals("I've been clicked!", button.getText());
     }
 
+    /* TODO(b/235841020): Implement these tests, and the tests for exceptions of each tested method.
+
+    public void testSwipe() {}
+
+    public void testDrag() {}
+
+    public void testSwipe_withPointArray() {}
+
+    public void testWaitForIdle() {}
+
+    public void testWaitForIdle_withTimeout() {}
+
+    public void testGetCurrentActivityName() {}
+
+    public void testGetCurrentPackageName() {}
+
+    public void testRegisterWatcher() {}
+
+    public void testRemoveWatcher() {}
+
+    public void testRunWatchers() {}
+
+    public void testResetWatcherTriggers() {}
+
+    public void testHasWatcherTriggered() {}
+
+    public void testHasAnyWatcherTriggered() {}
+
+    public void testIsNaturalOrientation() {}
+
+    public void testGetDisplayRotation() {}
+
+    public void testFreezeRotation() {}
+
+    public void testUnfreezeRotation() {}
+
+    public void testSetOrientationLeft() {}
+
+    public void testSetOrientationRight() {}
+
+    public void testSetOrientationNatural() {}
+
+    public void testWakeUp() {}
+
+    public void testIsScreenOn() {}
+
+    public void testSleep() {}
+
+    public void testDumpWindowHierarchy_withString() {}
+
+    public void testDumpWindowHierarchy_withFile() {} // already added
+
+    public void testDumpWindowHierarchy_withOutputStream() {}
+    */
+
     @Test
     public void testDumpWindowHierarchy() throws Exception {
         launchTestActivity(MainActivity.class);
@@ -88,4 +262,17 @@
         assertEquals("true", element.getAttribute("visible-to-user"));
         assertNotNull(element.getAttribute("bounds"));
     }
+
+    /* TODO(b/235841020): Implement these tests, and the tests for exceptions of each tested method.
+
+    public void testWaitForWindowUpdate() {}
+
+    public void testTakeScreenshot() {} // already added
+
+    public void testTakeScreenshot_withScaleAndQuality() {} // already added
+
+    public void testGetLauncherPackageName() {}
+
+    public void testExecuteShellCommand() {} // already added
+    */
 }
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 6c12c5e..b30e677 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,9 +28,32 @@
 import androidx.test.uiautomator.UiScrollable;
 import androidx.test.uiautomator.UiSelector;
 
+import org.junit.After;
+import org.junit.Before;
 import org.junit.Test;
 
 public class UiScrollableTest extends BaseTest {
+
+    private int mDefaultMaxSearchSwipes = 0;
+
+    private double mDefaultSwipeDeadZonePercentage = 0.0;
+
+    @Before
+    public void setUp() throws Exception {
+        super.setUp();
+
+        mDefaultMaxSearchSwipes = new UiScrollable(new UiSelector()).getMaxSearchSwipes();
+        mDefaultSwipeDeadZonePercentage =
+                new UiScrollable(new UiSelector()).getSwipeDeadZonePercentage();
+    }
+
+    @After
+    public void tearDown() {
+        new UiScrollable(new UiSelector()).setMaxSearchSwipes(mDefaultMaxSearchSwipes);
+        new UiScrollable(new UiSelector()).setSwipeDeadZonePercentage(
+                mDefaultSwipeDeadZonePercentage);
+    }
+
     @Test
     public void testGetChildByDescription() throws Exception {
         launchTestActivity(VerticalScrollTestActivity.class);
diff --git a/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTests.java b/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
similarity index 99%
rename from test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTests.java
rename to test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
index 6ff0436b..a619406 100644
--- a/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTests.java
+++ b/test/uiautomator/uiautomator/src/androidTest/java/androidx/test/uiautomator/UiDeviceTest.java
@@ -44,7 +44,7 @@
 import java.io.File;
 import java.io.IOException;
 
-public class UiDeviceTests {
+public class UiDeviceTest {
 
     @Rule
     public TemporaryFolder mTmpDir = new TemporaryFolder();
diff --git a/tracing/tracing-ktx/api/current.ignore b/tracing/tracing-ktx/api/current.ignore
deleted file mode 100644
index 12e4a8a..0000000
--- a/tracing/tracing-ktx/api/current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.tracing.TraceKt#traceAsync(String, int, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.tracing.TraceKt.traceAsync
diff --git a/tracing/tracing-ktx/api/restricted_current.ignore b/tracing/tracing-ktx/api/restricted_current.ignore
deleted file mode 100644
index 12e4a8a..0000000
--- a/tracing/tracing-ktx/api/restricted_current.ignore
+++ /dev/null
@@ -1,3 +0,0 @@
-// Baseline format: 1.0
-ParameterNameChange: androidx.tracing.TraceKt#traceAsync(String, int, kotlin.jvm.functions.Function1<? super kotlin.coroutines.Continuation<? super T>,?>, kotlin.coroutines.Continuation<? super T>) parameter #3:
-    Attempted to remove parameter name from parameter arg4 in androidx.tracing.TraceKt.traceAsync
diff --git a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
index 589db34..66d0320 100644
--- a/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
+++ b/tracing/tracing-perfetto-binary/src/main/cpp/tracing_perfetto.cc
@@ -25,7 +25,7 @@
 // Concept of version useful e.g. for human-readable error messages, and stable once released.
 // Does not replace the need for a binary verification mechanism (e.g. checksum check).
 // TODO: populate using CMake
-#define VERSION "1.0.0-alpha03"
+#define VERSION "1.0.0-alpha04"
 
 namespace tracing_perfetto {
     void RegisterWithPerfetto() {
diff --git a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
index 49335ed..e9c1b85 100644
--- a/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
+++ b/tracing/tracing-perfetto/src/androidTest/java/androidx/tracing/perfetto/jni/test/PerfettoNativeTest.kt
@@ -30,7 +30,7 @@
         init {
             PerfettoNative.loadLib()
         }
-        const val libraryVersion = "1.0.0-alpha03" // TODO: get using reflection
+        const val libraryVersion = "1.0.0-alpha04" // TODO: get using reflection
     }
 
     @Test
diff --git a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
index 4b55570..6ebfe16 100644
--- a/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
+++ b/tracing/tracing-perfetto/src/main/java/androidx/tracing/perfetto/jni/PerfettoNative.kt
@@ -23,12 +23,12 @@
 
     // TODO(224510255): load from a file produced at build time
     object Metadata {
-        const val version = "1.0.0-alpha03"
+        const val version = "1.0.0-alpha04"
         val checksums = mapOf(
-            "arm64-v8a" to "388c9770a0a1690bac0e79e8d90c109b20ad46aa0bdb7ee6d8a7b87cf914474a",
-            "armeabi-v7a" to "062f5b16ab1ae5ff1498d84ceeb6a6b8f1efa98b49350de2c1ff2abb53a7e954",
-            "x86" to "0aeec233848db85805387d37f432db67520cb9f094997df66b0cb82f10d188b6",
-            "x86_64" to "c416fab9db04d323d46fdcfc228fb74b26ab308d91afa3a7c1ee4ed2aa44b7df",
+            "arm64-v8a" to "3dbf1db7dfa7e4099f671255983ee7bd8fc9dfd95f4772179b791f6dd88d783a",
+            "armeabi-v7a" to "4806a3e5b2cf23ea03b4a87fbcff03dfc76c4ef8eb8e7358b8ce2e20a7c0f86f",
+            "x86" to "5ddb57d45bcd16325259330f7ea3f9b5803b394d10c9f57757f8ed1441507e10",
+            "x86_64" to "3c50eac377e7285c5f729e674d2140f296067835ca57eb43c67813a57f681a48",
         )
     }
 
diff --git a/tv/OWNERS b/tv/OWNERS
index eaea6d1..d611280 100644
--- a/tv/OWNERS
+++ b/tv/OWNERS
@@ -1,3 +1,4 @@
+# Bug component: 1223795
 apptica@google.com
 raharora@google.com
 rvighnesh@google.com
diff --git a/wear/compose/compose-material/api/restricted_current.ignore b/wear/compose/compose-material/api/restricted_current.ignore
deleted file mode 100644
index 6a5066c..0000000
--- a/wear/compose/compose-material/api/restricted_current.ignore
+++ /dev/null
@@ -1,5 +0,0 @@
-// Baseline format: 1.0
-InvalidNullConversion: androidx.wear.compose.material.ButtonDefaults#buttonColors(long, long, long, long):
-    Attempted to remove @NonNull annotation from method androidx.wear.compose.material.ButtonDefaults.buttonColors(long,long,long,long)
-InvalidNullConversion: androidx.wear.compose.material.ChipDefaults#chipColors(long, long, long, long, long, long, long, long):
-    Attempted to remove @NonNull annotation from method androidx.wear.compose.material.ChipDefaults.chipColors(long,long,long,long,long,long,long,long)
diff --git a/wear/watchface/watchface-client-guava/api/current.txt b/wear/watchface/watchface-client-guava/api/current.txt
index a6423d7..4c88746 100644
--- a/wear/watchface/watchface-client-guava/api/current.txt
+++ b/wear/watchface/watchface-client-guava/api/current.txt
@@ -11,9 +11,9 @@
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method @Deprecated public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
-    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
+    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
     method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener);
     field public static final androidx.wear.watchface.client.ListenableWatchFaceControlClient.Companion Companion;
   }
 
diff --git a/wear/watchface/watchface-client-guava/api/public_plus_experimental_current.txt b/wear/watchface/watchface-client-guava/api/public_plus_experimental_current.txt
index a6423d7..4c88746 100644
--- a/wear/watchface/watchface-client-guava/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-client-guava/api/public_plus_experimental_current.txt
@@ -11,9 +11,9 @@
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method @Deprecated public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
-    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
+    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
     method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener);
     field public static final androidx.wear.watchface.client.ListenableWatchFaceControlClient.Companion Companion;
   }
 
diff --git a/wear/watchface/watchface-client-guava/api/restricted_current.txt b/wear/watchface/watchface-client-guava/api/restricted_current.txt
index a6423d7..4c88746 100644
--- a/wear/watchface/watchface-client-guava/api/restricted_current.txt
+++ b/wear/watchface/watchface-client-guava/api/restricted_current.txt
@@ -11,9 +11,9 @@
     method public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient();
     method public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId);
     method @Deprecated public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
-    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
+    method public suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>);
     method @Deprecated public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData);
-    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener);
+    method public com.google.common.util.concurrent.ListenableFuture<androidx.wear.watchface.client.InteractiveWatchFaceClient> listenableGetOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener);
     field public static final androidx.wear.watchface.client.ListenableWatchFaceControlClient.Companion Companion;
   }
 
diff --git a/wear/watchface/watchface-client-guava/src/androidTest/java/androidx/wear/watchface/client/guava/ListenableWatchFaceControlClientTest.kt b/wear/watchface/watchface-client-guava/src/androidTest/java/androidx/wear/watchface/client/guava/ListenableWatchFaceControlClientTest.kt
index bdd322c..50b506e 100644
--- a/wear/watchface/watchface-client-guava/src/androidTest/java/androidx/wear/watchface/client/guava/ListenableWatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client-guava/src/androidTest/java/androidx/wear/watchface/client/guava/ListenableWatchFaceControlClientTest.kt
@@ -34,7 +34,6 @@
 import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.client.DeviceConfig
 import androidx.wear.watchface.client.ListenableWatchFaceControlClient
-import androidx.wear.watchface.client.WatchFaceControlClient
 import androidx.wear.watchface.client.WatchUiState
 import androidx.wear.watchface.samples.ExampleCanvasAnalogWatchFaceService
 import androidx.wear.watchface.style.CurrentUserStyleRepository
@@ -383,11 +382,7 @@
                 null,
                 null,
                 { runnable -> runnable.run() },
-                object : WatchFaceControlClient.PreviewImageUpdateRequestedListener {
-                    override fun onPreviewImageUpdateRequested(instanceId: String) {
-                        lastPreviewImageUpdateRequestedId = instanceId
-                    }
-                }
+                { lastPreviewImageUpdateRequestedId = it }
             )
 
         val service = TestWatchFaceServiceWithPreviewImageUpdateRequest(context, surfaceHolder)
diff --git a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
index 38d07eb..48ded1d 100644
--- a/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
+++ b/wear/watchface/watchface-client-guava/src/main/java/androidx/wear/watchface/client/ListenableWatchFaceControlClient.kt
@@ -20,13 +20,13 @@
 import android.content.Context
 import androidx.concurrent.futures.ResolvableFuture
 import androidx.wear.watchface.Renderer
-import androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener
 import androidx.wear.watchface.complications.data.ComplicationData
 import androidx.wear.watchface.utility.AsyncTraceEvent
 import androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException
 import androidx.wear.watchface.style.UserStyleData
 import com.google.common.util.concurrent.ListenableFuture
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Runnable
@@ -167,11 +167,11 @@
      */
     @Suppress("AsyncSuffixFuture")
     @Deprecated(
-        "Use an overload that specifies PreviewImageUpdateRequestedListener",
+        "Use an overload that specifies Consumer<String>",
         ReplaceWith(
             "listenableGetOrCreateInteractiveWatchFaceClient(" +
                 "String, DeviceConfig, WatchUiState, UserStyleData?, Map<Int, ComplicationData>?," +
-                " Executor, PreviewImageUpdateRequestedListener)"
+                " Executor, Consumer<String>)"
         )
     )
     public open fun listenableGetOrCreateInteractiveWatchFaceClient(
@@ -210,8 +210,15 @@
      * @param previewImageUpdateRequestedExecutor The [Executor] on which to run
      * [previewImageUpdateRequestedListener] if the watch face calls
      * [Renderer.sendPreviewImageNeedsUpdateRequest].
-     * @param previewImageUpdateRequestedListener The [PreviewImageUpdateRequestedListener]
-     * which is fired if the watch face calls [Renderer.sendPreviewImageNeedsUpdateRequest].
+     * @param previewImageUpdateRequestedListener The [Consumer] fires when the watch face calls
+     * [Renderer.sendPreviewImageNeedsUpdateRequest], indicating that it now looks visually
+     * different. The string passed to the [Consumer] is the ID of the watch face (see [id] passed
+     * into [getOrCreateInteractiveWatchFaceClient]) requesting the update. This will usually
+     * match the current watch face but it could also be from a previous watch face if
+     * [InteractiveWatchFaceClient.updateWatchFaceInstance] is called shortly after
+     * [Renderer.sendPreviewImageNeedsUpdateRequest].
+     * The [Consumer] should Schedule creation of a headless instance to render a new preview image
+     * for the instanceId. This is likely an expensive operation and should be rate limited.
      * @return a [ListenableFuture] for the [InteractiveWatchFaceClient].
      */
     @Suppress("AsyncSuffixFuture")
@@ -222,7 +229,7 @@
         userStyle: UserStyleData?,
         slotIdToComplicationData: Map<Int, ComplicationData>?,
         previewImageUpdateRequestedExecutor: Executor,
-        previewImageUpdateRequestedListener: PreviewImageUpdateRequestedListener
+        previewImageUpdateRequestedListener: Consumer<String>
     ): ListenableFuture<InteractiveWatchFaceClient> =
         launchFutureCoroutine(
             "ListenableWatchFaceControlClient.listenableGetOrCreateInteractiveWatchFaceClient",
@@ -239,11 +246,11 @@
         }
 
     @Deprecated(
-        "Use an overload that specifies PreviewImageUpdateRequestedListener",
+        "Use an overload that specifies a Consumer<String>",
         replaceWith = ReplaceWith(
             "getOrCreateInteractiveWatchFaceClient(String, DeviceConfig, WatchUiState," +
                 " UserStyleData?, Map<Int, ComplicationData>?, Executor, " +
-                "PreviewImageUpdateRequestedListener)")
+                "Consumer<String>)")
     )
     @Suppress("deprecation")
     override suspend fun getOrCreateInteractiveWatchFaceClient(
@@ -268,7 +275,7 @@
         userStyle: UserStyleData?,
         slotIdToComplicationData: Map<Int, ComplicationData>?,
         previewImageUpdateRequestedExecutor: Executor,
-        previewImageUpdateRequestedListener: PreviewImageUpdateRequestedListener
+        previewImageUpdateRequestedListener: Consumer<String>
     ): InteractiveWatchFaceClient =
         watchFaceControlClient.getOrCreateInteractiveWatchFaceClient(
             instanceId,
diff --git a/wear/watchface/watchface-client/api/current.txt b/wear/watchface/watchface-client/api/current.txt
index d45b0ec2..cd45be3 100644
--- a/wear/watchface/watchface-client/api/current.txt
+++ b/wear/watchface/watchface-client/api/current.txt
@@ -127,7 +127,7 @@
 
   public interface InteractiveWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
-    method public default void addOnWatchFaceColorsListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method 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();
@@ -139,7 +139,7 @@
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener);
-    method public default void removeOnWatchFaceColorsListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method public default void removeOnWatchFaceColorsListener(java.util.function.Consumer<androidx.wear.watchface.WatchFaceColors> listener);
     method public void removeOnWatchFaceReadyListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @RequiresApi(27) @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? idAndComplicationData) throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void sendTouchEvent(@Px int xPosition, @Px int yPosition, @androidx.wear.watchface.TapType int tapType) throws android.os.RemoteException;
@@ -160,7 +160,8 @@
   }
 
   public static interface InteractiveWatchFaceClient.ClientDisconnectListener {
-    method public void onClientDisconnected();
+    method @Deprecated public default void onClientDisconnected();
+    method public default void onClientDisconnected(int disconnectReason);
   }
 
   public static final class InteractiveWatchFaceClient.Companion {
@@ -169,10 +170,6 @@
     field public static final int TAP_TYPE_UP = 2; // 0x2
   }
 
-  public static fun interface InteractiveWatchFaceClient.OnWatchFaceColorsListener {
-    method public void onWatchFaceColors(androidx.wear.watchface.WatchFaceColors? watchFaceColors);
-  }
-
   public static fun interface InteractiveWatchFaceClient.OnWatchFaceReadyListener {
     method public void onWatchFaceReady();
   }
@@ -194,7 +191,7 @@
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId) throws android.os.RemoteException;
     method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
     method public default boolean hasComplicationDataCache();
     field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
   }
@@ -203,10 +200,6 @@
     method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException;
   }
 
-  public static interface WatchFaceControlClient.PreviewImageUpdateRequestedListener {
-    method public void onPreviewImageUpdateRequested(String instanceId);
-  }
-
   public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
     ctor public WatchFaceControlClient.ServiceNotBoundException();
   }
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 422874d..557c819 100644
--- a/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
+++ b/wear/watchface/watchface-client/api/public_plus_experimental_current.txt
@@ -131,7 +131,7 @@
 
   public interface InteractiveWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
-    method public default void addOnWatchFaceColorsListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method 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();
@@ -143,7 +143,7 @@
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener);
-    method public default void removeOnWatchFaceColorsListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method public default void removeOnWatchFaceColorsListener(java.util.function.Consumer<androidx.wear.watchface.WatchFaceColors> listener);
     method public void removeOnWatchFaceReadyListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @RequiresApi(27) @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? idAndComplicationData) throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void sendTouchEvent(@Px int xPosition, @Px int yPosition, @androidx.wear.watchface.TapType int tapType) throws android.os.RemoteException;
@@ -164,7 +164,8 @@
   }
 
   public static interface InteractiveWatchFaceClient.ClientDisconnectListener {
-    method public void onClientDisconnected();
+    method @Deprecated public default void onClientDisconnected();
+    method public default void onClientDisconnected(int disconnectReason);
   }
 
   public static final class InteractiveWatchFaceClient.Companion {
@@ -173,10 +174,6 @@
     field public static final int TAP_TYPE_UP = 2; // 0x2
   }
 
-  public static fun interface InteractiveWatchFaceClient.OnWatchFaceColorsListener {
-    method public void onWatchFaceColors(androidx.wear.watchface.WatchFaceColors? watchFaceColors);
-  }
-
   public static fun interface InteractiveWatchFaceClient.OnWatchFaceReadyListener {
     method public void onWatchFaceReady();
   }
@@ -201,7 +198,7 @@
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId) throws android.os.RemoteException;
     method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
     method public default boolean hasComplicationDataCache();
     field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
   }
@@ -210,10 +207,6 @@
     method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException;
   }
 
-  public static interface WatchFaceControlClient.PreviewImageUpdateRequestedListener {
-    method public void onPreviewImageUpdateRequested(String instanceId);
-  }
-
   public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
     ctor public WatchFaceControlClient.ServiceNotBoundException();
   }
diff --git a/wear/watchface/watchface-client/api/restricted_current.txt b/wear/watchface/watchface-client/api/restricted_current.txt
index d45b0ec2..cd45be3 100644
--- a/wear/watchface/watchface-client/api/restricted_current.txt
+++ b/wear/watchface/watchface-client/api/restricted_current.txt
@@ -127,7 +127,7 @@
 
   public interface InteractiveWatchFaceClient extends java.lang.AutoCloseable {
     method @AnyThread public void addClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener, java.util.concurrent.Executor executor);
-    method public default void addOnWatchFaceColorsListener(java.util.concurrent.Executor executor, androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method 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();
@@ -139,7 +139,7 @@
     method @AnyThread public boolean isConnectionAlive();
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void performAmbientTick() throws android.os.RemoteException;
     method @AnyThread public void removeClientDisconnectListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.ClientDisconnectListener listener);
-    method public default void removeOnWatchFaceColorsListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceColorsListener listener);
+    method public default void removeOnWatchFaceColorsListener(java.util.function.Consumer<androidx.wear.watchface.WatchFaceColors> listener);
     method public void removeOnWatchFaceReadyListener(androidx.wear.watchface.client.InteractiveWatchFaceClient.OnWatchFaceReadyListener listener);
     method @RequiresApi(27) @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public android.graphics.Bitmap renderWatchFaceToBitmap(androidx.wear.watchface.RenderParameters renderParameters, java.time.Instant instant, androidx.wear.watchface.style.UserStyle? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? idAndComplicationData) throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public void sendTouchEvent(@Px int xPosition, @Px int yPosition, @androidx.wear.watchface.TapType int tapType) throws android.os.RemoteException;
@@ -160,7 +160,8 @@
   }
 
   public static interface InteractiveWatchFaceClient.ClientDisconnectListener {
-    method public void onClientDisconnected();
+    method @Deprecated public default void onClientDisconnected();
+    method public default void onClientDisconnected(int disconnectReason);
   }
 
   public static final class InteractiveWatchFaceClient.Companion {
@@ -169,10 +170,6 @@
     field public static final int TAP_TYPE_UP = 2; // 0x2
   }
 
-  public static fun interface InteractiveWatchFaceClient.OnWatchFaceColorsListener {
-    method public void onWatchFaceColors(androidx.wear.watchface.WatchFaceColors? watchFaceColors);
-  }
-
   public static fun interface InteractiveWatchFaceClient.OnWatchFaceReadyListener {
     method public void onWatchFaceReady();
   }
@@ -194,7 +191,7 @@
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.EditorServiceClient getEditorServiceClient() throws android.os.RemoteException;
     method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public androidx.wear.watchface.client.InteractiveWatchFaceClient? getInteractiveWatchFaceClientInstance(String instanceId) throws android.os.RemoteException;
     method @Deprecated @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public suspend Object? getOrCreateInteractiveWatchFaceClient(String id, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
-    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, androidx.wear.watchface.client.WatchFaceControlClient.PreviewImageUpdateRequestedListener previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
+    method @kotlin.jvm.Throws(exceptionClasses=RemoteException::class) public default suspend Object? getOrCreateInteractiveWatchFaceClient(String instanceId, androidx.wear.watchface.client.DeviceConfig deviceConfig, androidx.wear.watchface.client.WatchUiState watchUiState, androidx.wear.watchface.style.UserStyleData? userStyle, java.util.Map<java.lang.Integer,? extends androidx.wear.watchface.complications.data.ComplicationData>? slotIdToComplicationData, java.util.concurrent.Executor previewImageUpdateRequestedExecutor, java.util.function.Consumer<java.lang.String> previewImageUpdateRequestedListener, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.InteractiveWatchFaceClient>) throws android.os.RemoteException;
     method public default boolean hasComplicationDataCache();
     field public static final androidx.wear.watchface.client.WatchFaceControlClient.Companion Companion;
   }
@@ -203,10 +200,6 @@
     method @kotlin.jvm.Throws(exceptionClasses={ServiceNotBoundException::class, ServiceStartFailureException::class}) public suspend Object? createWatchFaceControlClient(android.content.Context context, String watchFacePackageName, kotlin.coroutines.Continuation<? super androidx.wear.watchface.client.WatchFaceControlClient>) throws androidx.wear.watchface.client.WatchFaceControlClient.ServiceNotBoundException, androidx.wear.watchface.client.WatchFaceControlClient.ServiceStartFailureException;
   }
 
-  public static interface WatchFaceControlClient.PreviewImageUpdateRequestedListener {
-    method public void onPreviewImageUpdateRequested(String instanceId);
-  }
-
   public static final class WatchFaceControlClient.ServiceNotBoundException extends java.lang.Exception {
     ctor public WatchFaceControlClient.ServiceNotBoundException();
   }
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestNopCanvasWatchFaceService.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestNopCanvasWatchFaceService.kt
index d1aa5b9..31bca95 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestNopCanvasWatchFaceService.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/TestNopCanvasWatchFaceService.kt
@@ -59,9 +59,11 @@
                 // Intentionally empty.
             }
         }
-    ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
+    )
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
         override fun getSystemTimeMillis() = 123456789L
 
         override fun getSystemTimeZoneId() = ZoneId.of("UTC")
-    })
+    }
 }
\ No newline at end of file
diff --git a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
index 53b7ae7..4b8947d 100644
--- a/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
+++ b/wear/watchface/watchface-client/src/androidTest/java/androidx/wear/watchface/client/test/WatchFaceControlClientTest.kt
@@ -65,6 +65,7 @@
 import androidx.wear.watchface.WatchFaceType
 import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.client.DeviceConfig
+import androidx.wear.watchface.client.DisconnectReason
 import androidx.wear.watchface.client.HeadlessWatchFaceClient
 import androidx.wear.watchface.client.InteractiveWatchFaceClient
 import androidx.wear.watchface.client.WatchFaceControlClient
@@ -1060,7 +1061,7 @@
         // We need to wait for watch face init to have completed before lateinit
         // wallpaperService.watchFace will be assigned. To do this we issue an arbitrary API
         // call which by necessity awaits full initialization.
-        interactiveInstance.complicationSlotsState
+        interactiveInstance.previewReferenceInstant
 
         // Add some additional ContentDescriptionLabels
         val pendingIntent1 = PendingIntent.getActivity(
@@ -1847,6 +1848,7 @@
         val wallpaperService =
             TestWatchFaceServiceWithPreviewImageUpdateRequest(context, surfaceHolder)
         var lastPreviewImageUpdateRequestedId = ""
+
         val deferredInteractiveInstance = handlerCoroutineScope.async {
             service.getOrCreateInteractiveWatchFaceClient(
                 "wfId-1",
@@ -1855,11 +1857,7 @@
                 null,
                 complications,
                 { runnable -> runnable.run() },
-                object : WatchFaceControlClient.PreviewImageUpdateRequestedListener {
-                    override fun onPreviewImageUpdateRequested(instanceId: String) {
-                        lastPreviewImageUpdateRequestedId = instanceId
-                    }
-                }
+                { lastPreviewImageUpdateRequestedId = it }
             )
         }
 
@@ -1884,6 +1882,46 @@
 
         interactiveInstance.close()
     }
+
+    @Test
+    fun engineDetached() {
+        val wallpaperService = TestComplicationProviderDefaultsWatchFaceService(
+            context,
+            surfaceHolder
+        )
+        val deferredInteractiveInstance = handlerCoroutineScope.async {
+            @Suppress("deprecation")
+            service.getOrCreateInteractiveWatchFaceClient(
+                "testId",
+                deviceConfig,
+                systemState,
+                null,
+                complications
+            )
+        }
+        // Create the engine which triggers construction of the interactive instance.
+        handler.post {
+            engine = wallpaperService.onCreateEngine() as WatchFaceService.EngineWrapper
+        }
+
+        // Wait for the instance to be created.
+        val interactiveInstance = awaitWithTimeout(deferredInteractiveInstance)
+
+        var lastDisconnectReason = 0
+        interactiveInstance.addClientDisconnectListener(
+            object : InteractiveWatchFaceClient.ClientDisconnectListener {
+                override fun onClientDisconnected(@DisconnectReason disconnectReason: Int) {
+                    lastDisconnectReason = disconnectReason
+                }
+            },
+            { it.run() }
+        )
+
+        // Simulate detach.
+        engine.onDestroy()
+
+        assertThat(lastDisconnectReason).isEqualTo(DisconnectReason.ENGINE_DETACHED)
+    }
 }
 
 internal class TestExampleCanvasAnalogWatchFaceService(
@@ -2063,11 +2101,13 @@
                 TODO("Not yet implemented")
             }
         }
-    ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
+    )
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
         override fun getSystemTimeMillis() = 123456789L
 
         override fun getSystemTimeZoneId() = ZoneId.of("UTC")
-    })
+    }
 }
 
 internal class TestAsyncGlesRenderInitWatchFaceService(
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
index d7870fe..6d925c6 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/InteractiveWatchFaceClient.kt
@@ -22,6 +22,7 @@
 import android.os.RemoteException
 import android.support.wearable.watchface.SharedMemoryImage
 import androidx.annotation.AnyThread
+import androidx.annotation.IntDef
 import androidx.annotation.Px
 import androidx.annotation.RequiresApi
 import androidx.wear.watchface.complications.data.ComplicationData
@@ -49,6 +50,35 @@
 import androidx.wear.watchface.toApiFormat
 import java.time.Instant
 import java.util.concurrent.Executor
+import java.util.function.Consumer
+
+/** @hide */
+@IntDef(
+    value = [
+        DisconnectReason.ENGINE_DIED,
+        DisconnectReason.ENGINE_DETACHED
+    ]
+)
+public annotation class DisconnectReason {
+    public companion object {
+        /**
+         * The underlying engine died, probably because the watch face was killed or crashed.
+         * Sometimes this is due to memory pressure and it's not the watch face's fault. Usually in
+         * response a new [InteractiveWatchFaceClient] should be created (see
+         * [WatchFaceControlClient.getOrCreateInteractiveWatchFaceClient]), however if this new
+         * client also disconnects due to [ENGINE_DIED] within a few seconds the watchface is
+         * probably bad and it's recommended to switch to a safe system default watch face.
+         */
+        public const val ENGINE_DIED: Int = 1
+
+        /**
+         * Wallpaper service detached from the engine, which is now defunct. The watch face itself
+         * has no control over this. Usually in response a new [InteractiveWatchFaceClient]
+         * should be created (see [WatchFaceControlClient.getOrCreateInteractiveWatchFaceClient]).
+         */
+        public const val ENGINE_DETACHED: Int = 2
+    }
+}
 
 /**
  * Controls a stateful remote interactive watch face. Typically this will be used for the current
@@ -215,7 +245,19 @@
          * The client disconnected, typically due to the server side crashing. Note this is not
          * called in response to [close] being called on [InteractiveWatchFaceClient].
          */
-        public fun onClientDisconnected()
+        @Deprecated(
+            "Deprecated, use an overload that passes the disconnectReason",
+            ReplaceWith("onClientDisconnected(Int)")
+        )
+        public fun onClientDisconnected() {}
+
+        /**
+         * The client disconnected, due to [disconnectReason].
+         */
+        public fun onClientDisconnected(@DisconnectReason disconnectReason: Int) {
+            @Suppress("DEPRECATION")
+            onClientDisconnected()
+        }
     }
 
     /** Registers a [ClientDisconnectListener]. */
@@ -265,45 +307,30 @@
     public fun removeOnWatchFaceReadyListener(listener: OnWatchFaceReadyListener)
 
     /**
-     * Interface passed to [addOnWatchFaceReadyListener] which calls
-     * [OnWatchFaceColorsListener.onWatchFaceColors] initially with the current
-     * [Renderer.watchfaceColors] if known or `null` if not, and subsequently whenever the watch
-     * face's [Renderer.watchfaceColors] change.
-     */
-    public fun interface OnWatchFaceColorsListener {
-        /**
-         * Called initially with the current [Renderer.watchfaceColors] if known or `null` if not,
-         * and subsequently whenever the watch face's [Renderer.watchfaceColors] change.
-         */
-        public fun onWatchFaceColors(watchFaceColors: WatchFaceColors?)
-    }
-
-    /**
-     * Registers a [OnWatchFaceColorsListener] which gets called initially with the current
+     * Registers a [Consumer] which gets called initially with the current
      * [Renderer.watchfaceColors] if known or `null` if not, and subsequently whenever the watch
      * face's [Renderer.watchfaceColors] change.
      *
      * @param executor The [Executor] on which to run [listener].
-     * @param listener The [OnWatchFaceColorsListener] to run whenever the watch face's
+     * @param listener The [Consumer] to run whenever the watch face's
      * [Renderer.watchfaceColors] change.
      */
     public fun addOnWatchFaceColorsListener(
         executor: Executor,
-        listener: OnWatchFaceColorsListener
+        listener: Consumer<WatchFaceColors?>
     ) {}
 
     /**
      * Stops listening for events registered by [addOnWatchFaceColorsListener].
      */
-    public fun removeOnWatchFaceColorsListener(listener: OnWatchFaceColorsListener) {}
+    public fun removeOnWatchFaceColorsListener(listener: Consumer<WatchFaceColors?>) {}
 }
 
 /** Controls a stateful remote interactive watch face. */
 internal class InteractiveWatchFaceClientImpl internal constructor(
     private val iInteractiveWatchFace: IInteractiveWatchFace,
     private val previewImageUpdateRequestedExecutor: Executor?,
-    private val previewImageUpdateRequestedListener:
-        WatchFaceControlClient.PreviewImageUpdateRequestedListener?
+    private val previewImageUpdateRequestedListener: Consumer<String>?
 ) : InteractiveWatchFaceClient {
 
     private val lock = Any()
@@ -312,9 +339,10 @@
     private val readyListeners =
         HashMap<InteractiveWatchFaceClient.OnWatchFaceReadyListener, Executor>()
     private val watchFaceColorsChangeListeners =
-        HashMap<InteractiveWatchFaceClient.OnWatchFaceColorsListener, Executor>()
+        HashMap<Consumer<WatchFaceColors?>, Executor>()
     private var watchfaceReadyListenerRegistered = false
     private var lastWatchFaceColors: WatchFaceColors? = null
+    private var disconnectReason: Int? = null
     private var closed = false
 
     private val iWatchFaceListener = object : IWatchfaceListener.Stub() {
@@ -325,8 +353,7 @@
         }
 
         override fun onWatchfaceColorsChanged(watchFaceColors: WatchFaceColorsWireFormat?) {
-            var listenerCopy:
-                HashMap<InteractiveWatchFaceClient.OnWatchFaceColorsListener, Executor>
+            var listenerCopy: HashMap<Consumer<WatchFaceColors?>, Executor>
 
             synchronized(lock) {
                 listenerCopy = HashMap(watchFaceColorsChangeListeners)
@@ -335,33 +362,26 @@
 
             for ((listener, executor) in listenerCopy) {
                 executor.execute {
-                    listener.onWatchFaceColors(lastWatchFaceColors)
+                    listener.accept(lastWatchFaceColors)
                 }
             }
         }
 
         override fun onPreviewImageUpdateRequested(watchFaceId: String) {
             previewImageUpdateRequestedExecutor?.execute {
-                previewImageUpdateRequestedListener!!.onPreviewImageUpdateRequested(watchFaceId)
+                previewImageUpdateRequestedListener!!.accept(watchFaceId)
             }
         }
+
+        override fun onEngineDetached() {
+            sendDisconnectNotification(DisconnectReason.ENGINE_DETACHED)
+        }
     }
 
     init {
         iInteractiveWatchFace.asBinder().linkToDeath(
             {
-                var listenerCopy:
-                    HashMap<InteractiveWatchFaceClient.ClientDisconnectListener, Executor>
-
-                synchronized(lock) {
-                    listenerCopy = HashMap(disconnectListeners)
-                }
-
-                for ((listener, executor) in listenerCopy) {
-                    executor.execute {
-                        listener.onClientDisconnected()
-                    }
-                }
+                sendDisconnectNotification(DisconnectReason.ENGINE_DIED)
             },
             0
         )
@@ -371,6 +391,22 @@
         }
     }
 
+    internal fun sendDisconnectNotification(reason: Int) {
+        val listenersCopy = synchronized(lock) {
+            // Don't send more than one notification.
+            if (disconnectReason != null) {
+                return
+            }
+            disconnectReason = reason
+            HashMap(disconnectListeners)
+        }
+        for ((listener, executor) in listenersCopy) {
+            executor.execute {
+                listener.onClientDisconnected(reason)
+            }
+        }
+    }
+
     override fun updateComplicationData(
         slotIdToComplicationData: Map<Int, ComplicationData>
     ) = TraceEvent("InteractiveWatchFaceClientImpl.updateComplicationData").use {
@@ -498,11 +534,15 @@
         listener: InteractiveWatchFaceClient.ClientDisconnectListener,
         executor: Executor
     ) {
-        synchronized(lock) {
+        val disconnectReasonCopy = synchronized(lock) {
             require(!disconnectListeners.contains(listener)) {
                 "Don't call addClientDisconnectListener multiple times for the same listener"
             }
             disconnectListeners.put(listener, executor)
+            disconnectReason
+        }
+        disconnectReasonCopy?.let {
+            listener.onClientDisconnected(it)
         }
     }
 
@@ -591,7 +631,7 @@
 
     override fun addOnWatchFaceColorsListener(
         executor: Executor,
-        listener: InteractiveWatchFaceClient.OnWatchFaceColorsListener
+        listener: Consumer<WatchFaceColors?>
     ) {
         val colors = synchronized(lock) {
             require(!watchFaceColorsChangeListeners.contains(listener)) {
@@ -603,11 +643,11 @@
             lastWatchFaceColors
         }
 
-        listener.onWatchFaceColors(colors)
+        listener.accept(colors)
     }
 
     override fun removeOnWatchFaceColorsListener(
-        listener: InteractiveWatchFaceClient.OnWatchFaceColorsListener
+        listener: Consumer<WatchFaceColors?>
     ) {
         synchronized(lock) {
             watchFaceColorsChangeListeners.remove(listener)
diff --git a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
index 1bbabb6..f96d1b2 100644
--- a/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
+++ b/wear/watchface/watchface-client/src/main/java/androidx/wear/watchface/client/WatchFaceControlClient.kt
@@ -44,6 +44,7 @@
 import androidx.wear.watchface.style.UserStyleData
 import androidx.wear.watchface.style.data.UserStyleWireFormat
 import java.util.concurrent.Executor
+import java.util.function.Consumer
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.suspendCancellableCoroutine
 import kotlin.coroutines.resume
@@ -215,11 +216,11 @@
      */
     @Throws(RemoteException::class)
     @Deprecated(
-        "Use an overload that specifies PreviewImageUpdateRequestedListener",
+        "Use an overload that specifies Consumer<String>",
         ReplaceWith(
             "getOrCreateInteractiveWatchFaceClient(" +
                 "String, DeviceConfig, WatchUiState, UserStyleData?, Map<Int, ComplicationData>?," +
-                " Executor, PreviewImageUpdateRequestedListener)"
+                " Executor, Consumer<String>)"
         )
     )
     public suspend fun getOrCreateInteractiveWatchFaceClient(
@@ -230,23 +231,6 @@
         slotIdToComplicationData: Map<Int, ComplicationData>?
     ): InteractiveWatchFaceClient
 
-    /** Listener for observing calls to [Renderer.sendPreviewImageNeedsUpdateRequest]. */
-    public interface PreviewImageUpdateRequestedListener {
-        /**
-         * The watch face called [Renderer.sendPreviewImageNeedsUpdateRequest], indicating that it
-         * looks visually different. Schedule creation of a headless instance to render a new
-         * preview image for [instanceId]. This is likely an expensive operation and should be rate
-         * limited.
-         *
-         * @param instanceId The ID of the watch face (see id passed into
-         * getOrCreateInteractiveWatchFaceClient)) requesting the update. This will usually
-         * match the current watch face but it could also be from a previous watch face if
-         * [InteractiveWatchFaceClient.updateWatchFaceInstance] is called shortly after
-         * [Renderer.sendPreviewImageNeedsUpdateRequest].
-         */
-        public fun onPreviewImageUpdateRequested(instanceId: String)
-    }
-
     /**
      * Requests either an existing [InteractiveWatchFaceClient] with the specified [instanceId] or
      * schedules creation of an [InteractiveWatchFaceClient] for the next time the
@@ -266,8 +250,15 @@
      * @param previewImageUpdateRequestedExecutor The [Executor] on which to run
      * [previewImageUpdateRequestedListener] if the watch face calls
      * [Renderer.sendPreviewImageNeedsUpdateRequest].
-     * @param previewImageUpdateRequestedListener The [PreviewImageUpdateRequestedListener]
-     * which is fired if the watch face calls [Renderer.sendPreviewImageNeedsUpdateRequest].
+     * @param previewImageUpdateRequestedListener The [Consumer] fires when the watch face calls
+     * [Renderer.sendPreviewImageNeedsUpdateRequest], indicating that it now looks visually
+     * different. The string passed to the [Consumer] is the ID of the watch face (see [instanceId]
+     * passed into [getOrCreateInteractiveWatchFaceClient]) requesting the update. This will usually
+     * match the current watch face but it could also be from a previous watch face if
+     * [InteractiveWatchFaceClient.updateWatchFaceInstance] is called shortly after
+     * [Renderer.sendPreviewImageNeedsUpdateRequest].
+     * The [Consumer] should Schedule creation of a headless instance to render a new preview image
+     * for the instanceId. This is likely an expensive operation and should be rate limited.
      *
      * @return The [InteractiveWatchFaceClient], this should be closed when finished.
      * @throws [ServiceStartFailureException] if the watchface dies during startup.
@@ -281,7 +272,7 @@
         userStyle: UserStyleData?,
         slotIdToComplicationData: Map<Int, ComplicationData>?,
         previewImageUpdateRequestedExecutor: Executor,
-        previewImageUpdateRequestedListener: PreviewImageUpdateRequestedListener
+        previewImageUpdateRequestedListener: Consumer<String>
     ): InteractiveWatchFaceClient = getOrCreateInteractiveWatchFaceClient(
         instanceId,
         deviceConfig,
@@ -425,11 +416,11 @@
     }
 
     @Deprecated(
-        "Use an overload that specifies PreviewImageUpdateRequestedListener",
+        "Use an overload that specifies Consumer<String>",
         replaceWith = ReplaceWith(
             "getOrCreateInteractiveWatchFaceClient(String, DeviceConfig, WatchUiState, " +
                 "UserStyleData?, Map<Int, ComplicationData>?, Executor, " +
-                "PreviewImageUpdateRequestedListener)")
+                "Consumer<String>)")
     )
     override suspend fun getOrCreateInteractiveWatchFaceClient(
         id: String,
@@ -454,8 +445,7 @@
         userStyle: UserStyleData?,
         slotIdToComplicationData: Map<Int, ComplicationData>?,
         previewImageUpdateRequestedExecutor: Executor,
-        previewImageUpdateRequestedListener:
-            WatchFaceControlClient.PreviewImageUpdateRequestedListener
+        previewImageUpdateRequestedListener: Consumer<String>
     ): InteractiveWatchFaceClient = getOrCreateInteractiveWatchFaceClientImpl(
         instanceId,
         deviceConfig,
@@ -463,7 +453,7 @@
         userStyle,
         slotIdToComplicationData,
         previewImageUpdateRequestedExecutor,
-            previewImageUpdateRequestedListener
+        previewImageUpdateRequestedListener
     )
 
     private suspend fun getOrCreateInteractiveWatchFaceClientImpl(
@@ -473,8 +463,7 @@
         userStyle: UserStyleData?,
         slotIdToComplicationData: Map<Int, ComplicationData>?,
         previewImageUpdateRequestedExecutor: Executor?,
-        previewImageUpdateRequestedListener:
-            WatchFaceControlClient.PreviewImageUpdateRequestedListener?
+        previewImageUpdateRequestedListener: Consumer<String>?
     ): InteractiveWatchFaceClient {
         requireNotClosed()
         val traceEvent = AsyncTraceEvent(
diff --git a/wear/watchface/watchface-client/src/test/java/androidx/wear/watchface/client/InteractiveWatchFaceClientTest.kt b/wear/watchface/watchface-client/src/test/java/androidx/wear/watchface/client/InteractiveWatchFaceClientTest.kt
new file mode 100644
index 0000000..784c538
--- /dev/null
+++ b/wear/watchface/watchface-client/src/test/java/androidx/wear/watchface/client/InteractiveWatchFaceClientTest.kt
@@ -0,0 +1,56 @@
+/*
+ * 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.client
+
+import android.os.IBinder
+import androidx.wear.watchface.control.IInteractiveWatchFace
+import com.nhaarman.mockitokotlin2.any
+import com.nhaarman.mockitokotlin2.mock
+import com.nhaarman.mockitokotlin2.times
+import com.nhaarman.mockitokotlin2.verify
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.mockito.Mockito.`when`
+
+@RunWith(ClientTestRunner::class)
+class InteractiveWatchFaceClientTest {
+    private val iInteractiveWatchFace = mock<IInteractiveWatchFace>()
+    private val iBinder = mock<IBinder>()
+
+    init {
+        `when`(iInteractiveWatchFace.asBinder()).thenReturn(iBinder)
+    }
+
+    @Test
+    public fun sendDisconnectNotification() {
+        val client = InteractiveWatchFaceClientImpl(
+            iInteractiveWatchFace,
+            previewImageUpdateRequestedExecutor = null,
+            previewImageUpdateRequestedListener = null
+        )
+
+        val listener = mock<InteractiveWatchFaceClient.ClientDisconnectListener>()
+        client.addClientDisconnectListener(listener, { it.run() })
+
+        // Simulate multiple disconnect notifications.
+        client.sendDisconnectNotification(DisconnectReason.ENGINE_DETACHED)
+        client.sendDisconnectNotification(DisconnectReason.ENGINE_DIED)
+
+        // But only one should be sent to the listener.
+        verify(listener, times(1)).onClientDisconnected(any())
+    }
+}
\ No newline at end of file
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-es-rUS/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-es-rUS/complication_strings.xml
index cac9204..6a7b049 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-es-rUS/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-es-rUS/complication_strings.xml
@@ -2,28 +2,34 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> d</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> d</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> m</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> días</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> día</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> horas</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> hora</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> min</item>
     </plurals>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-es/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-es/complication_strings.xml
index 2d7ad7e..78df7ea 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-es/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-es/complication_strings.xml
@@ -2,28 +2,34 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g>d</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g>h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g>m</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> días</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> día</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> horas</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> hora</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> minutos</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> minuto</item>
     </plurals>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-fr-rCA/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-fr-rCA/complication_strings.xml
index b0aaa5f..764f019 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-fr-rCA/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-fr-rCA/complication_strings.xml
@@ -3,28 +3,34 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jour</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jours</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heure</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heures</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heures</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
     </plurals>
     <string name="time_difference_now" msgid="2068359133884605223">"Maint."</string>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-fr/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-fr/complication_strings.xml
index 14472d7..ecadf7b 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-fr/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-fr/complication_strings.xml
@@ -3,28 +3,34 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> j</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jour</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> jours</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heure</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heures</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> heures</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
     </plurals>
     <string name="time_difference_now" msgid="2068359133884605223">"Maintenant"</string>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-it/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-it/complication_strings.xml
index 3e268a1..b7f280d9 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-it/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-it/complication_strings.xml
@@ -2,28 +2,34 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> g</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> g</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> m</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> m</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> giorni</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> giorno</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> ore</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> ora</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> min</item>
     </plurals>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-pt-rBR/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-pt-rBR/complication_strings.xml
index 826d6c6..02899c2 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-pt-rBR/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-pt-rBR/complication_strings.xml
@@ -3,28 +3,34 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>min</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>min</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g><xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> dia</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> dias</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hora</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> horas</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> minuto</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> minutos</item>
     </plurals>
     <string name="time_difference_now" msgid="2068359133884605223">"Agora"</string>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-pt-rPT/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-pt-rPT/complication_strings.xml
index 5e18f1f..43be2b3 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-pt-rPT/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-pt-rPT/complication_strings.xml
@@ -2,28 +2,34 @@
 <resources xmlns:android="http://schemas.android.com/apk/res/android"
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> d</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> d</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> h</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> min</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g> <xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> dias</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_0">%d</xliff:g> dia</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> horas</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_0">%d</xliff:g> hora</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> min</item>
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_0">%d</xliff:g> min</item>
     </plurals>
diff --git a/wear/watchface/watchface-complications-data/src/main/res/values-pt/complication_strings.xml b/wear/watchface/watchface-complications-data/src/main/res/values-pt/complication_strings.xml
index 826d6c6..02899c2 100644
--- a/wear/watchface/watchface-complications-data/src/main/res/values-pt/complication_strings.xml
+++ b/wear/watchface/watchface-complications-data/src/main/res/values-pt/complication_strings.xml
@@ -3,28 +3,34 @@
     xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
     <plurals name="time_difference_short_days" formatted="false" msgid="3878057769320887026">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g>d</item>
     </plurals>
     <plurals name="time_difference_short_hours" formatted="false" msgid="6016687406802669982">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g>h</item>
     </plurals>
     <plurals name="time_difference_short_minutes" formatted="false" msgid="6752732458902810711">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>min</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>m</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g>min</item>
     </plurals>
     <string name="time_difference_short_days_and_hours" msgid="2384500200491672870">"<xliff:g id="SHORT_DAYS">%1$s</xliff:g> <xliff:g id="SHORT_HOURS">%2$s</xliff:g>"</string>
     <string name="time_difference_short_hours_and_minutes" msgid="8030280938566792191">"<xliff:g id="SHORT_HOURS">%1$s</xliff:g><xliff:g id="SHORT_MINUTES">%2$s</xliff:g>"</string>
     <plurals name="time_difference_words_days" formatted="false" msgid="5109682345086392533">
       <item quantity="one"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> dia</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> days</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_DAYS_1">%d</xliff:g> dias</item>
     </plurals>
     <plurals name="time_difference_words_hours" formatted="false" msgid="3172220157267000186">
       <item quantity="one"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hora</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> hours</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_HOURS_1">%d</xliff:g> horas</item>
     </plurals>
     <plurals name="time_difference_words_minutes" formatted="false" msgid="529404827937478243">
       <item quantity="one"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> minuto</item>
+      <item quantity="many"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> mins</item>
       <item quantity="other"><xliff:g id="NUMBER_OF_MINUTES_1">%d</xliff:g> minutos</item>
     </plurals>
     <string name="time_difference_now" msgid="2068359133884605223">"Agora"</string>
diff --git a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IWatchfaceListener.aidl b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IWatchfaceListener.aidl
index d6ead5d..e3522e5 100644
--- a/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IWatchfaceListener.aidl
+++ b/wear/watchface/watchface-data/src/main/aidl/androidx/wear/watchface/control/IWatchfaceListener.aidl
@@ -26,12 +26,12 @@
 interface IWatchfaceListener {
     // IMPORTANT NOTE: All methods must be given an explicit transaction id that must never change
     // in the future to remain binary backwards compatible.
-    // Next Id: 5
+    // Next Id: 6
 
     /**
      * API version number. This should be incremented every time a new method is added.
      */
-    const int API_VERSION = 1;
+    const int API_VERSION = 2;
 
     /**
      * Returns the version number for this API which the client can use to determine which methods
@@ -61,4 +61,12 @@
      * @since API version 1.
      */
     oneway void onPreviewImageUpdateRequested(in String watchFaceId) = 4;
+
+    /**
+     * Signals that the watch face engine has detached meaning this instance is defunct and should
+     * be closed.
+     *
+     * @since API version 2.
+     */
+    oneway void onEngineDetached() = 5;
 }
diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt
index 580847e..a7e832d 100644
--- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt
+++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRenderer2Test.kt
@@ -110,11 +110,13 @@
                 // NOP
             }
         }
-    ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
+    )
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
         override fun getSystemTimeMillis() = 123456789L
 
         override fun getSystemTimeZoneId() = ZoneId.of("UTC")
-    })
+    }
 }
 
 @MediumTest
diff --git a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt
index 2581dbc..1f371f0 100644
--- a/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt
+++ b/wear/watchface/watchface-guava/src/androidTest/java/androidx/wear/watchface/AsyncListenableCanvasRendererTest.kt
@@ -89,11 +89,13 @@
                 // NOP
             }
         }
-    ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
+    )
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
         override fun getSystemTimeMillis() = 123456789L
 
         override fun getSystemTimeZoneId() = ZoneId.of("UTC")
-    })
+    }
 }
 
 @MediumTest
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
index 11b7290..12966a3 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestCanvasAnalogWatchFaceService.kt
@@ -75,11 +75,7 @@
             watchState,
             complicationSlotsManager,
             currentUserStyleRepository
-        ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
-            override fun getSystemTimeMillis() = mockSystemTimeMillis
-
-            override fun getSystemTimeZoneId() = mockZoneId
-        })
+        )
     }
 
     override fun getMutableWatchState() = mutableWatchState
@@ -118,4 +114,10 @@
     override fun onInvalidate() {
         onInvalidateCountDownLatch?.countDown()
     }
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
+        override fun getSystemTimeMillis() = mockSystemTimeMillis
+
+        override fun getSystemTimeZoneId() = mockZoneId
+    }
 }
diff --git a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestGlesWatchFaceService.kt b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestGlesWatchFaceService.kt
index 7bd2ba7..548327a 100644
--- a/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestGlesWatchFaceService.kt
+++ b/wear/watchface/watchface/src/androidTest/java/androidx/wear/watchface/test/TestGlesWatchFaceService.kt
@@ -72,11 +72,7 @@
             watchState,
             complicationSlotsManager,
             currentUserStyleRepository
-        ).setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
-            override fun getSystemTimeMillis() = mockSystemTimeMillis
-
-            override fun getSystemTimeZoneId() = mockZoneId
-        })
+        )
     }
 
     override fun getMutableWatchState() = mutableWatchState
@@ -109,4 +105,10 @@
         fileName: String,
         byteArray: ByteArray
     ) {}
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
+        override fun getSystemTimeMillis() = mockSystemTimeMillis
+
+        override fun getSystemTimeZoneId() = mockZoneId
+    }
 }
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 78b4c90..b5f49c4 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -270,19 +270,6 @@
         public fun onComplicationSlotConfigExtrasChanged()
     }
 
-    /**
-     * Interface for getting the current system time.
-     * @hide
-     */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public interface SystemTimeProvider {
-        /** Returns the current system time in milliseconds. */
-        public fun getSystemTimeMillis(): Long
-
-        /** Returns the current system [ZoneId]. */
-        public fun getSystemTimeZoneId(): ZoneId
-    }
-
     /** Listens for taps on the watchface. */
     public interface TapListener {
         /**
@@ -460,12 +447,6 @@
     public var overridePreviewReferenceInstant: Instant? = null
         private set
 
-    internal var systemTimeProvider: SystemTimeProvider = object : SystemTimeProvider {
-        override fun getSystemTimeMillis() = System.currentTimeMillis()
-
-        override fun getSystemTimeZoneId() = ZoneId.systemDefault()
-    }
-
     /**
      * Overrides the reference time for editor preview images.
      *
@@ -483,12 +464,6 @@
         this.tapListener = tapListener
     }
 
-    /** @hide */
-    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
-    public fun setSystemTimeProvider(systemTimeProvider: SystemTimeProvider): WatchFace = apply {
-        this.systemTimeProvider = systemTimeProvider
-    }
-
     /**
      * Sets the [Intent] to launch an activity which explains the watch face needs permission to
      * display complications. It is recommended the activity have a button which launches an intent
@@ -616,7 +591,7 @@
                 ),
         )
 
-    internal val systemTimeProvider = watchface.systemTimeProvider
+    internal val systemTimeProvider = watchFaceHostApi.systemTimeProvider
     private val legacyWatchFaceStyle = watchface.legacyWatchFaceStyle
     internal val renderer = watchface.renderer
     private val tapListener = watchface.tapListener
@@ -1069,19 +1044,6 @@
     }
 
     /**
-     * Called when new complication data is received.
-     *
-     * @param complicationSlotId The id of the [ComplicationSlot] that the data relates to.
-     * @param data The [ComplicationData] that should be displayed in the complication.
-     */
-    @UiThread
-    internal fun onComplicationSlotDataUpdate(complicationSlotId: Int, data: ComplicationData) {
-        complicationSlotsManager.onComplicationDataUpdate(complicationSlotId, data, getNow())
-        watchFaceHostApi.invalidate()
-        watchFaceHostApi.scheduleWriteComplicationDataCache()
-    }
-
-    /**
      * Called when a tap or touch related event occurs. Detects taps on [ComplicationSlot]s and
      * triggers the associated action.
      *
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
index dad9abf..4b752ed 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceHostApi.kt
@@ -33,6 +33,9 @@
  */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
 public interface WatchFaceHostApi {
+    /** The [WatchFaceService.SystemTimeProvider]. */
+    public val systemTimeProvider: WatchFaceService.SystemTimeProvider
+
     /** Returns the watch face's [Context]. */
     public fun getContext(): Context
 
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index d9ed858..5bebc58 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -16,7 +16,6 @@
 
 package androidx.wear.watchface
 
-import android.annotation.SuppressLint
 import android.content.ComponentName
 import android.content.Context
 import android.content.Intent
@@ -32,6 +31,7 @@
 import android.os.Looper
 import android.os.PowerManager
 import android.os.Process
+import android.os.RemoteCallbackList
 import android.os.RemoteException
 import android.os.Trace
 import android.service.wallpaper.WallpaperService
@@ -92,6 +92,7 @@
 import java.io.ObjectOutputStream
 import java.io.PrintWriter
 import java.time.Instant
+import java.time.ZoneId
 import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CompletableDeferred
 import kotlinx.coroutines.CoroutineScope
@@ -641,6 +642,27 @@
 
     internal var backgroundThread: HandlerThread? = null
 
+    /**
+     * Interface for getting the current system time.
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public interface SystemTimeProvider {
+        /** Returns the current system time in milliseconds. */
+        public fun getSystemTimeMillis(): Long
+
+        /** Returns the current system [ZoneId]. */
+        public fun getSystemTimeZoneId(): ZoneId
+    }
+
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public open fun getSystemTimeProvider(): SystemTimeProvider = object : SystemTimeProvider {
+        override fun getSystemTimeMillis() = System.currentTimeMillis()
+
+        override fun getSystemTimeZoneId() = ZoneId.systemDefault()
+    }
+
     /** This is open for testing. The background thread is used for watch face initialization. */
     internal open fun getBackgroundThreadHandlerImpl(): Handler {
         synchronized(this) {
@@ -867,7 +889,6 @@
         var pendingProperties: Bundle? = null
         var pendingSetWatchFaceStyle = false
         var pendingVisibilityChanged: Boolean? = null
-        var pendingComplicationDataUpdates = ArrayList<PendingComplicationData>()
         var complicationsActivated = false
         var watchFaceInitStarted = false
         var lastActiveComplicationSlots: IntArray? = null
@@ -907,7 +928,6 @@
             @DataSourceId fallbackSystemDataSource: Int,
             type: Int
         ) {
-
             // For android R flow iWatchFaceService won't have been set.
             if (!iWatchFaceServiceInitialized()) {
                 return
@@ -1002,11 +1022,11 @@
         @Suppress("DEPRECATION")
         fun onComplicationSlotDataUpdate(extras: Bundle) {
             extras.classLoader = WireComplicationData::class.java.classLoader
-            val complicationData: WireComplicationData =
-                extras.getParcelable(Constants.EXTRA_COMPLICATION_DATA)!!
-            engineWrapper.setComplicationSlotData(
-                extras.getInt(Constants.EXTRA_COMPLICATION_ID),
-                complicationData.toApiComplicationData()
+            val complicationData =
+                extras.getParcelable<WireComplicationData>(Constants.EXTRA_COMPLICATION_DATA)!!
+            val complicationSlotId = extras.getInt(Constants.EXTRA_COMPLICATION_ID)
+            engineWrapper.setComplicationDataList(
+                listOf(IdAndComplicationDataWireFormat(complicationSlotId, complicationData))
             )
         }
 
@@ -1083,12 +1103,6 @@
                     engineWrapper.onVisibilityChanged(visibility)
                     pendingVisibilityChanged = null
                 }
-                for (complicationDataUpdate in pendingComplicationDataUpdates) {
-                    watchFaceImpl.onComplicationSlotDataUpdate(
-                        complicationDataUpdate.complicationSlotId,
-                        complicationDataUpdate.data
-                    )
-                }
                 watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
             }
         }
@@ -1173,6 +1187,7 @@
         internal lateinit var ambientUpdateWakelock: PowerManager.WakeLock
 
         private lateinit var choreographer: ChoreographerWrapper
+        override val systemTimeProvider = getSystemTimeProvider()
 
         /**
          * Whether we already have a [frameCallback] posted and waiting in the [Choreographer]
@@ -1240,11 +1255,10 @@
 
         private val mainThreadPriorityDelegate = getMainThreadPriorityDelegate()
 
-        // Members after this are protected by the lock.
         private val lock = Any()
 
         /** Protected by [lock]. */
-        private val listeners = HashSet<IWatchfaceListener>()
+        private val listeners = RemoteCallbackList<IWatchfaceListener>()
         private var lastWatchFaceColors: WatchFaceColors? = null
         private var lastPreviewImageNeedsUpdateRequest: String? = null
 
@@ -1269,6 +1283,20 @@
             }
         }
 
+        /**
+         * Returns the [EarlyInitDetails] if [deferredEarlyInitDetails] has completed successfully
+         * or `null` otherwise.
+         */
+        internal fun getEarlyInitDetailsOrNull(): EarlyInitDetails? {
+            return if (deferredEarlyInitDetails.isCompleted) {
+                runBlocking {
+                    deferredEarlyInitDetails.await()
+                }
+            } else {
+                null
+            }
+        }
+
         init {
             maybeCreateWCSApi()
         }
@@ -1457,37 +1485,23 @@
             }
         }
 
-        @SuppressLint("SyntheticAccessor")
-        internal fun setComplicationSlotData(
-            complicationSlotId: Int,
-            data: ComplicationData
-        ): Unit = TraceEvent("EngineWrapper.setComplicationSlotData").use {
-            val watchFaceImpl = getWatchFaceImplOrNull()
-            if (watchFaceImpl != null) {
-                watchFaceImpl.onComplicationSlotDataUpdate(complicationSlotId, data)
-                watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
-            } else {
-                // If the watch face hasn't loaded yet then we append
-                // pendingComplicationDataUpdates so it can be applied later.
-                wslFlow.pendingComplicationDataUpdates.add(
-                    WslFlow.PendingComplicationData(complicationSlotId, data)
-                )
-            }
-        }
-
         @UiThread
         internal fun setComplicationDataList(
             complicationDataWireFormats: List<IdAndComplicationDataWireFormat>
         ): Unit = TraceEvent("EngineWrapper.setComplicationDataList").use {
-            val watchFaceImpl = getWatchFaceImplOrNull()
-            if (watchFaceImpl != null) {
+            val earlyInitDetails = getEarlyInitDetailsOrNull()
+            if (earlyInitDetails != null) {
+                val now = Instant.ofEpochMilli(systemTimeProvider.getSystemTimeMillis())
                 for (idAndComplicationData in complicationDataWireFormats) {
-                    watchFaceImpl.onComplicationSlotDataUpdate(
+                    earlyInitDetails.complicationSlotsManager.onComplicationDataUpdate(
                         idAndComplicationData.id,
-                        idAndComplicationData.complicationData.toApiComplicationData()
+                        idAndComplicationData.complicationData.toApiComplicationData(),
+                        now
                     )
                 }
-                watchFaceImpl.complicationSlotsManager.onComplicationsUpdated()
+                earlyInitDetails.complicationSlotsManager.onComplicationsUpdated()
+                invalidate()
+                scheduleWriteComplicationDataCache()
             } else {
                 setPendingInitialComplications(complicationDataWireFormats)
             }
@@ -1728,7 +1742,7 @@
                                 x,
                                 y,
                                 Instant.ofEpochMilli(
-                                    watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
+                                    systemTimeProvider.getSystemTimeMillis()
                                 )
                             )
                         )
@@ -1742,7 +1756,7 @@
                                 x,
                                 y,
                                 Instant.ofEpochMilli(
-                                    watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
+                                    systemTimeProvider.getSystemTimeMillis()
                                 )
                             )
                         )
@@ -1758,7 +1772,7 @@
                                 x,
                                 y,
                                 Instant.ofEpochMilli(
-                                    watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
+                                    systemTimeProvider.getSystemTimeMillis()
                                 )
                             )
                         )
@@ -2021,6 +2035,14 @@
                         )
                     )
 
+                    // Apply any pendingInitialComplications, this must be done after
+                    // deferredWatchFaceImpl has completed or there's a window in which complication
+                    // updates get lost.
+                    pendingInitialComplications?.let {
+                        setComplicationDataList(it)
+                    }
+                    pendingInitialComplications = null
+
                     val watchFace = TraceEvent("WatchFaceService.createWatchFace").use {
                         // Note by awaiting deferredSurfaceHolder we ensure onSurfaceChanged has
                         // been called and we're passing the correct updated surface holder. This is
@@ -2143,14 +2165,6 @@
                 }
                 deferredWatchFaceImpl.complete(watchFaceImpl)
 
-                // Apply any pendingInitialComplications, this must be done after
-                // deferredWatchFaceImpl has completed or there's a window in which complication
-                // updates get lost.
-                pendingInitialComplications?.let {
-                    setComplicationDataList(it)
-                }
-                pendingInitialComplications = null
-
                 asyncWatchFaceConstructionPending = false
                 watchFaceImpl.initComplete = true
 
@@ -2225,7 +2239,6 @@
                     override fun onInvalidate() {
                         // This could be called on any thread.
                         uiThreadHandler.runOnHandlerWithTracing("onInvalidate") {
-                            this@WatchFaceService.onInvalidate()
                             if (initFinished) {
                                 getWatchFaceImplOrNull()?.invalidateIfNotAnimating()
                             }
@@ -2282,6 +2295,7 @@
         }
 
         override fun invalidate() {
+            this@WatchFaceService.onInvalidate()
             if (!allowWatchfaceToAnimate) {
                 return
             }
@@ -2339,23 +2353,62 @@
             }
         }
 
+        /**
+         * @param taskName The name to use when logging any exception
+         * @param listenerCallback The callback to invoke for each registered [IWatchfaceListener]
+         * @return the number of calls to [listenerCallback] that did not throw an exception.
+         */
+        private fun forEachListener(
+            taskName: String,
+            listenerCallback: (listener: IWatchfaceListener) -> Unit
+        ): Int {
+            var i = listeners.beginBroadcast()
+            var successCount = 0
+            while (i > 0) {
+                i--
+                val listener = listeners.getBroadcastItem(i)
+                try {
+                    listenerCallback(listener)
+                    successCount++
+                } catch (e: Exception) {
+                    Log.e(
+                        TAG,
+                        "In $taskName broadcastToListeners failed for ${listener.asBinder()}",
+                        e
+                    )
+                }
+            }
+            listeners.finishBroadcast()
+            return successCount
+        }
+
         override fun sendPreviewImageNeedsUpdateRequest() {
             synchronized(lock) {
                 lastPreviewImageNeedsUpdateRequest = interactiveInstanceId
-                HashSet<IWatchfaceListener>(listeners)
-            }.forEach {
-                it.onPreviewImageUpdateRequested(interactiveInstanceId)
+
+                forEachListener("sendPreviewImageNeedsUpdateRequest") {
+                    it.onPreviewImageUpdateRequested(interactiveInstanceId)
+                }
             }
         }
 
         override fun onWatchFaceColorsChanged(watchFaceColors: WatchFaceColors?) {
-            val listenersCopy = synchronized(lock) {
+            synchronized(lock) {
                 lastWatchFaceColors = watchFaceColors
-                HashSet<IWatchfaceListener>(listeners)
-            }
 
-            listenersCopy.forEach {
-                it.onWatchfaceColorsChanged(lastWatchFaceColors?.toWireFormat())
+                forEachListener("onWatchFaceColorsChanged") {
+                    it.onWatchfaceColorsChanged(lastWatchFaceColors?.toWireFormat())
+                }
+            }
+        }
+
+        internal fun onEngineDetached() {
+            synchronized(lock) {
+                forEachListener("onWatchFaceColorsChanged") {
+                    if (it.apiVersion >= 2) {
+                        it.onEngineDetached()
+                    }
+                }
             }
         }
 
@@ -2540,17 +2593,20 @@
             _context.getSystemService(Context.ACCESSIBILITY_SERVICE) as AccessibilityManager
 
         fun addWatchFaceListener(listener: IWatchfaceListener) {
-            val previewImageNeedsUpdateRequest: String?
-            val colors = synchronized(lock) {
-                listeners.add(listener)
-                previewImageNeedsUpdateRequest = lastPreviewImageNeedsUpdateRequest
-                lastWatchFaceColors
-            }
-
-            listener.onWatchfaceColorsChanged(colors?.toWireFormat())
-
-            previewImageNeedsUpdateRequest?.let {
-                listener.onPreviewImageUpdateRequested(it)
+            synchronized(lock) {
+                if (listeners.register(listener)) {
+                    Log.d(TAG, "addWatchFaceListener $listener")
+                } else {
+                    Log.w(
+                        TAG,
+                        "addWatchFaceListener $listener failed because its already registered"
+                    )
+                    return
+                }
+                lastPreviewImageNeedsUpdateRequest?.let {
+                    listener.onPreviewImageUpdateRequested(it)
+                }
+                listener.onWatchfaceColorsChanged(lastWatchFaceColors?.toWireFormat())
             }
 
             uiThreadCoroutineScope.launch {
@@ -2561,7 +2617,14 @@
 
         fun removeWatchFaceListener(listener: IWatchfaceListener) {
             synchronized(lock) {
-                listeners.remove(listener)
+                if (listeners.unregister(listener)) {
+                    Log.d(TAG, "removeWatchFaceListener $listener")
+                } else {
+                    Log.w(
+                        TAG,
+                        "removeWatchFaceListener $listener failed because it's not registered"
+                    )
+                }
             }
         }
 
@@ -2611,7 +2674,9 @@
             )
 
             synchronized(lock) {
-                writer.println("listeners = " + listeners.joinToString(", "))
+                forEachListener("dump") {
+                    writer.println("listener = " + it.asBinder())
+                }
             }
 
             if (!destroyed) {
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
index c277639..badd1a8 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
@@ -86,10 +86,10 @@
         ) { it.previewReferenceInstant.toEpochMilli() } ?: 0
 
     override fun getComplicationState() =
-        WatchFaceService.awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking(
+        WatchFaceService.awaitDeferredEarlyInitDetailsThenRunOnBinderThread(
             engine,
             "HeadlessWatchFaceImpl.getComplicationState"
-        ) { it.complicationSlotsManager.getComplicationsState(it.renderer.screenBounds) }
+        ) { it.complicationSlotsManager.getComplicationsState(it.surfaceHolder.surfaceFrame) }
 
     override fun renderComplicationToBitmap(params: ComplicationRenderParams) =
         WatchFaceService.awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking(
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
index 5677746..fda644c 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
@@ -19,6 +19,7 @@
 import android.annotation.SuppressLint
 import android.util.Log
 import androidx.annotation.UiThread
+import androidx.annotation.VisibleForTesting
 import androidx.wear.watchface.utility.TraceEvent
 import androidx.wear.watchface.IndentingPrintWriter
 import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
@@ -56,6 +57,11 @@
         private var pendingWallpaperInteractiveWatchFaceInstance:
             PendingWallpaperInteractiveWatchFaceInstance? = null
 
+        @VisibleForTesting
+        fun getInstances() = synchronized(pendingWallpaperInteractiveWatchFaceInstanceLock) {
+            instances.map { it.key }
+        }
+
         @SuppressLint("SyntheticAccessor")
         fun addInstance(impl: InteractiveWatchFaceImpl) {
             synchronized(pendingWallpaperInteractiveWatchFaceInstanceLock) {
diff --git a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
index cecf0a4..1eae0b4 100644
--- a/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
+++ b/wear/watchface/watchface/src/main/java/androidx/wear/watchface/control/InteractiveWatchFaceImpl.kt
@@ -47,6 +47,7 @@
     }
 
     private val uiThreadCoroutineScope = engine!!.uiThreadCoroutineScope
+    private val systemTimeProvider = engine!!.systemTimeProvider
 
     override fun getApiVersion() = IInteractiveWatchFace.API_VERSION
 
@@ -61,9 +62,7 @@
                 TapEvent(
                     xPos,
                     yPos,
-                    Instant.ofEpochMilli(
-                        watchFaceImpl.systemTimeProvider.getSystemTimeMillis()
-                    )
+                    Instant.ofEpochMilli(systemTimeProvider.getSystemTimeMillis())
                 )
             )
         }
@@ -145,16 +144,16 @@
         // Note this is a one way method called on a binder thread, so it shouldn't matter if we
         // block.
         runBlocking {
-            withContext(uiThreadCoroutineScope.coroutineContext) {
-                engine?.let {
-                    try {
+            try {
+                withContext(uiThreadCoroutineScope.coroutineContext) {
+                    engine?.let {
                         it.deferredWatchFaceImpl.await()
-                    } catch (e: Exception) {
-                        // deferredWatchFaceImpl may have completed with an exception. This will
-                        // have already been reported so we can ignore it.
                     }
                     InteractiveInstanceManager.releaseInstance(instanceId)
                 }
+            } catch (e: Exception) {
+                // deferredWatchFaceImpl may have completed with an exception. This will
+                // have already been reported so we can ignore it.
             }
         }
     }
@@ -192,10 +191,10 @@
     }
 
     override fun getComplicationDetails(): List<IdAndComplicationStateWireFormat>? {
-        return WatchFaceService.awaitDeferredWatchFaceImplThenRunOnUiThreadBlocking(
+        return WatchFaceService.awaitDeferredEarlyInitDetailsThenRunOnBinderThread(
             engine,
             "InteractiveWatchFaceImpl.getComplicationDetails"
-        ) { it.complicationSlotsManager.getComplicationsState(it.renderer.screenBounds) }
+        ) { it.complicationSlotsManager.getComplicationsState(it.surfaceHolder.surfaceFrame) }
     }
 
     override fun getUserStyleSchema(): UserStyleSchemaWireFormat? {
@@ -219,9 +218,14 @@
     fun onDestroy() {
         // Note this is almost certainly called on the ui thread, from release() above.
         runBlocking {
-            withContext(uiThreadCoroutineScope.coroutineContext) {
-                Log.d(TAG, "onDestroy id $instanceId")
-                engine = null
+            try {
+                withContext(uiThreadCoroutineScope.coroutineContext) {
+                    Log.d(TAG, "onDestroy id $instanceId")
+                    engine?.onEngineDetached()
+                    engine = null
+                }
+            } catch (e: Exception) {
+                Log.w(TAG, "onDestroy failed to call onEngineDetached", e)
             }
         }
     }
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
index 85a9a02..d3ef0be7 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/AsyncWatchFaceInitTest.kt
@@ -50,6 +50,7 @@
 import org.robolectric.annotation.Config
 import java.util.ArrayDeque
 import java.util.PriorityQueue
+import org.junit.After
 
 internal class TestAsyncWatchFaceService(
     private val handler: Handler,
@@ -188,6 +189,11 @@
         }.`when`(handler).removeCallbacks(ArgumentMatchers.any())
     }
 
+    @After
+    fun tearDown() {
+        assertThat(InteractiveInstanceManager.getInstances()).isEmpty()
+    }
+
     @RequiresApi(Build.VERSION_CODES.O_MR1)
     @Test
     public fun createInteractiveInstanceFailsIfDirectBootWatchFaceCreationIsInProgress() {
@@ -229,6 +235,8 @@
         runPostedTasksFor(0)
 
         assertThat(pendingException.message).startsWith("WatchFace already exists!")
+
+        InteractiveInstanceManager.releaseInstance(initParams.instanceId)
     }
 
     @RequiresApi(Build.VERSION_CODES.O_MR1)
@@ -313,5 +321,6 @@
         runPostedTasksFor(0)
 
         assertNotNull(pendingInteractiveWatchFaceWcs)
+        pendingInteractiveWatchFaceWcs?.release()
     }
 }
diff --git a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 96d57ef..a183f9c 100644
--- a/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/watchface/watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -129,11 +129,6 @@
     ): WatchFace {
         renderer = rendererFactory(surfaceHolder, currentUserStyleRepository, watchState)
         val watchFace = WatchFace(watchFaceType, renderer!!)
-            .setSystemTimeProvider(object : WatchFace.SystemTimeProvider {
-                override fun getSystemTimeMillis() = mockSystemTimeMillis
-
-                override fun getSystemTimeZoneId() = mockZoneId
-            })
         tapListener?.let {
             watchFace.setTapListener(it)
         }
@@ -175,6 +170,12 @@
     }
 
     override fun isPreAndroidR() = preAndroidR
+
+    override fun getSystemTimeProvider() = object : SystemTimeProvider {
+        override fun getSystemTimeMillis() = mockSystemTimeMillis
+
+        override fun getSystemTimeZoneId() = mockZoneId
+    }
 }
 
 /**
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 8f72f1d..83c4949 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
@@ -64,7 +64,6 @@
 import androidx.wear.watchface.complications.data.ShortTextComplicationData
 import androidx.wear.watchface.complications.data.TimeDifferenceComplicationText
 import androidx.wear.watchface.complications.data.TimeDifferenceStyle
-import androidx.wear.watchface.complications.data.toApiComplicationData
 import androidx.wear.watchface.complications.rendering.CanvasComplicationDrawable
 import androidx.wear.watchface.complications.rendering.ComplicationDrawable
 import androidx.wear.watchface.control.IInteractiveWatchFace
@@ -117,7 +116,6 @@
 import org.junit.Assert.fail
 import org.junit.Assume
 import org.junit.Before
-import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.mockito.ArgumentCaptor
@@ -727,6 +725,7 @@
             engineWrapper.onDestroy()
         }
 
+        assertThat(InteractiveInstanceManager.getInstances()).isEmpty()
         validateMockitoUsage()
     }
 
@@ -1484,15 +1483,19 @@
             UserStyleSchema(emptyList())
         )
 
-        watchFaceImpl.onComplicationSlotDataUpdate(
-            LEFT_COMPLICATION_ID,
-            ShortTextComplicationData.Builder(
-                TimeDifferenceComplicationText.Builder(
-                    TimeDifferenceStyle.STOPWATCH,
-                    CountUpTimeReference(Instant.parse("2022-10-30T10:15:30.001Z"))
-                ).setMinimumTimeUnit(TimeUnit.MINUTES).build(),
-                androidx.wear.watchface.complications.data.ComplicationText.EMPTY
-            ).build()
+        engineWrapper.setComplicationDataList(
+            listOf(
+                IdAndComplicationDataWireFormat(
+                    LEFT_COMPLICATION_ID,
+                    ShortTextComplicationData.Builder(
+                        TimeDifferenceComplicationText.Builder(
+                            TimeDifferenceStyle.STOPWATCH,
+                            CountUpTimeReference(Instant.parse("2022-10-30T10:15:30.001Z"))
+                        ).setMinimumTimeUnit(TimeUnit.MINUTES).build(),
+                        androidx.wear.watchface.complications.data.ComplicationText.EMPTY
+                    ).build().asWireComplicationData()
+                )
+            )
         )
 
         renderer.interactiveDrawModeUpdateDelayMillis = 60000
@@ -1523,15 +1526,19 @@
 
         // Sending a complication with a scheduled update alters the result of getNextChangeInstant.
         val referenceInstant = Instant.parse("2022-10-30T10:15:30.001Z")
-        watchFaceImpl.onComplicationSlotDataUpdate(
-            LEFT_COMPLICATION_ID,
-            ShortTextComplicationData.Builder(
-                TimeDifferenceComplicationText.Builder(
-                    TimeDifferenceStyle.STOPWATCH,
-                    CountUpTimeReference(referenceInstant)
-                ).setMinimumTimeUnit(TimeUnit.HOURS).build(),
-                androidx.wear.watchface.complications.data.ComplicationText.EMPTY
-            ).build()
+        engineWrapper.setComplicationDataList(
+            listOf(
+                IdAndComplicationDataWireFormat(
+                    LEFT_COMPLICATION_ID,
+                    ShortTextComplicationData.Builder(
+                        TimeDifferenceComplicationText.Builder(
+                            TimeDifferenceStyle.STOPWATCH,
+                            CountUpTimeReference(referenceInstant)
+                        ).setMinimumTimeUnit(TimeUnit.HOURS).build(),
+                        androidx.wear.watchface.complications.data.ComplicationText.EMPTY
+                    ).build().asWireComplicationData()
+                )
+            )
         )
 
         val nowInstant = Instant.EPOCH.plusSeconds(10)
@@ -1540,15 +1547,19 @@
 
         // Sending another complication with an earlier scheduled update alters the result of
         // getNextChangeInstant again.
-        watchFaceImpl.onComplicationSlotDataUpdate(
-            RIGHT_COMPLICATION_ID,
-            ShortTextComplicationData.Builder(
-                TimeDifferenceComplicationText.Builder(
-                    TimeDifferenceStyle.STOPWATCH,
-                    CountUpTimeReference(referenceInstant)
-                ).setMinimumTimeUnit(TimeUnit.SECONDS).build(),
-                androidx.wear.watchface.complications.data.ComplicationText.EMPTY
-            ).build()
+        engineWrapper.setComplicationDataList(
+            listOf(
+                IdAndComplicationDataWireFormat(
+                    RIGHT_COMPLICATION_ID,
+                    ShortTextComplicationData.Builder(
+                        TimeDifferenceComplicationText.Builder(
+                            TimeDifferenceStyle.STOPWATCH,
+                            CountUpTimeReference(referenceInstant)
+                        ).setMinimumTimeUnit(TimeUnit.SECONDS).build(),
+                        androidx.wear.watchface.complications.data.ComplicationText.EMPTY
+                    ).build().asWireComplicationData()
+                )
+            )
         )
 
         assertThat(complicationSlotsManager.getNextChangeInstant(nowInstant))
@@ -1873,12 +1884,12 @@
 
         runPostedTasksFor(0)
 
-        // setContentDescriptionLabels gets called twice in the legacy WSL flow, once initially and
-        // once in response to the complication data wallpaper commands.
+        // setContentDescriptionLabels gets called once in the legacy WSL flow in response to the
+        // complication data wallpaper commands.
         val arguments = ArgumentCaptor.forClass(Array<ContentDescriptionLabel>::class.java)
-        verify(iWatchFaceService, times(2)).setContentDescriptionLabels(arguments.capture())
+        verify(iWatchFaceService, times(1)).setContentDescriptionLabels(arguments.capture())
 
-        val argument = arguments.allValues[1]
+        val argument = arguments.allValues[0]
         assertThat(argument.size).isEqualTo(3)
         assertThat(argument[0].bounds).isEqualTo(Rect(25, 25, 75, 75)) // Clock element.
         assertThat(argument[1].bounds).isEqualTo(Rect(20, 40, 40, 60)) // Left complication.
@@ -2357,7 +2368,6 @@
     }
 
     @Test
-    @Ignore // Temporarily broken. b/222673768
     public fun getComplicationDetails_early_init_with_styleOverrides() {
         val complicationsStyleSetting = ComplicationSlotsUserStyleSetting(
             UserStyleSetting.Id("complications_style_setting"),
@@ -2794,6 +2804,7 @@
         assertThat(complicationCache).containsKey(INTERACTIVE_INSTANCE_ID)
 
         engineWrapper.onDestroy()
+        InteractiveInstanceManager.releaseInstance(INTERACTIVE_INSTANCE_ID)
 
         val service2 = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -2816,6 +2827,7 @@
             complicationCache = complicationCache
         )
 
+        lateinit var instance2: IInteractiveWatchFace
         InteractiveInstanceManager
             .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
                 InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
@@ -2827,6 +2839,7 @@
                         override fun onInteractiveWatchFaceCreated(
                             iInteractiveWatchFace: IInteractiveWatchFace
                         ) {
+                            instance2 = iInteractiveWatchFace
                         }
 
                         override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel?) {
@@ -2865,7 +2878,7 @@
             assertThat(rightComplicationData.tapActionLostDueToSerialization).isFalse()
         }
 
-        engineWrapper2.onDestroy()
+        instance2.release()
     }
 
     @Test
@@ -2910,7 +2923,7 @@
         assertThat(complicationCache.size).isEqualTo(1)
         assertThat(complicationCache).containsKey(INTERACTIVE_INSTANCE_ID)
 
-        engineWrapper.onDestroy()
+        interactiveWatchFaceInstance.release()
 
         val service2 = TestWatchFaceService(
             WatchFaceType.ANALOG,
@@ -2933,6 +2946,7 @@
             complicationCache = complicationCache
         )
 
+        lateinit var instance2: IInteractiveWatchFace
         InteractiveInstanceManager
             .getExistingInstanceOrSetPendingWallpaperInteractiveWatchFaceInstance(
                 InteractiveInstanceManager.PendingWallpaperInteractiveWatchFaceInstance(
@@ -2944,6 +2958,7 @@
                         override fun onInteractiveWatchFaceCreated(
                             iInteractiveWatchFace: IInteractiveWatchFace
                         ) {
+                            instance2 = iInteractiveWatchFace
                         }
 
                         override fun onInteractiveWatchFaceCrashed(exception: CrashInfoParcel?) {
@@ -2990,7 +3005,7 @@
                 .isEqualTo("B")
         }
 
-        engineWrapper2.onDestroy()
+        instance2.release()
     }
 
     @Test
@@ -3555,14 +3570,16 @@
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
         engineWrapper.onSurfaceChanged(surfaceHolder, 0, 100, 100)
 
+        // This increments refcount to 2
         val instance = InteractiveInstanceManager.getAndRetainInstance(instanceId)
         assertThat(instance).isNotNull()
         watchFaceImpl = engineWrapper.getWatchFaceImplOrNull()!!
         val userStyle = watchFaceImpl.currentUserStyleRepository.userStyle.value
         assertThat(userStyle[colorStyleSetting]).isEqualTo(blueStyleOption)
         assertThat(userStyle[watchHandStyleSetting]).isEqualTo(gothicStyleOption)
+        instance!!.release()
 
-        InteractiveInstanceManager.deleteInstance(instanceId)
+        InteractiveInstanceManager.releaseInstance(instanceId)
     }
 
     @Test
@@ -4030,6 +4047,7 @@
         ).isEqualTo("RIGHT!")
         assertThat(engineWrapper.contentDescriptionLabels[2].tapAction)
             .isEqualTo(rightPendingIntent)
+        interactiveInstance.release()
     }
 
     @Test
@@ -4421,6 +4439,7 @@
         )
 
         headlessEngineWrapper.onDestroy()
+        interactiveWatchFaceInstance.release()
         engineWrapper.onDestroy()
 
         assertThat(eventLog).containsExactly(
@@ -4550,6 +4569,7 @@
         )
 
         headlessEngineWrapper.onDestroy()
+        interactiveWatchFaceInstance.release()
         engineWrapper.onDestroy()
 
         assertThat(eventLog).containsExactly(
@@ -4640,6 +4660,8 @@
 
         engineWrapper.onDestroy()
         assertThat(onDestroyCalled).isTrue()
+
+        InteractiveInstanceManager.releaseInstance("TestID")
     }
 
     @Test
@@ -4769,6 +4791,7 @@
         engineWrapper.onVisibilityChanged(true)
         assertThat(mainThreadPriorityDelegate.priority).isEqualTo(Priority.Interactive)
 
+        interactiveWatchFaceInstance.release()
         engineWrapper.onDestroy()
         assertThat(mainThreadPriorityDelegate.priority).isEqualTo(Priority.Normal)
     }
@@ -4920,7 +4943,9 @@
             UserStyleSchema(emptyList())
         )
 
-        watchFaceImpl.onComplicationSlotDataUpdate(LEFT_COMPLICATION_ID, a.toApiComplicationData())
+        engineWrapper.setComplicationDataList(
+            listOf(IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, a))
+        )
 
         complicationSlotsManager.selectComplicationDataForInstant(Instant.ofEpochSecond(999))
         assertThat(getLeftShortTextComplicationDataText()).isEqualTo("A")
@@ -4972,7 +4997,9 @@
             UserStyleSchema(emptyList())
         )
 
-        watchFaceImpl.onComplicationSlotDataUpdate(LEFT_COMPLICATION_ID, a.toApiComplicationData())
+        engineWrapper.setComplicationDataList(
+            listOf(IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, a))
+        )
 
         complicationSlotsManager.selectComplicationDataForInstant(Instant.ofEpochSecond(999))
         assertThat(getLeftShortTextComplicationDataText()).isEqualTo("A")
@@ -5031,9 +5058,10 @@
             UserStyleSchema(emptyList())
         )
 
-        watchFaceImpl.onComplicationSlotDataUpdate(
-            LEFT_COMPLICATION_ID,
-            wireLongTextComplication.toApiComplicationData()
+        engineWrapper.setComplicationDataList(
+            listOf(
+                IdAndComplicationDataWireFormat(LEFT_COMPLICATION_ID, wireLongTextComplication)
+            )
         )
 
         complicationSlotsManager.selectComplicationDataForInstant(Instant.ofEpochSecond(0))
@@ -5556,7 +5584,10 @@
 
         val NEW_ID = SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX + "New"
         runBlocking {
-            engineWrapper.updateInstance(NEW_ID)
+            interactiveWatchFaceInstance.updateWatchfaceInstance(
+                NEW_ID,
+                UserStyleWireFormat(emptyMap())
+            )
         }
 
         assertThat(engineWrapper.interactiveInstanceId).isEqualTo(NEW_ID)
@@ -5658,6 +5689,8 @@
             }
 
             override fun onPreviewImageUpdateRequested(watchFaceId: String) {}
+
+            override fun onEngineDetached() {}
         }
 
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
@@ -5773,6 +5806,8 @@
             override fun onPreviewImageUpdateRequested(watchFaceId: String) {
                 lastPreviewImageUpdateRequestedWatchFaceId = watchFaceId
             }
+
+            override fun onEngineDetached() {}
         }
 
         engineWrapper = testWatchFaceService.onCreateEngine() as WatchFaceService.EngineWrapper
@@ -5801,6 +5836,7 @@
             SYSTEM_SUPPORTS_CONSISTENT_IDS_PREFIX + "Interactive2"
         )
 
+        interactiveWatchFaceInstance.release()
         engineWrapper.onDestroy()
     }
 
diff --git a/work/work-runtime-ktx/api/current.ignore b/work/work-runtime-ktx/api/current.ignore
index fb0fcc1..342948d 100644
--- a/work/work-runtime-ktx/api/current.ignore
+++ b/work/work-runtime-ktx/api/current.ignore
@@ -1,9 +1,6 @@
 // Baseline format: 1.0
-
 ParameterNameChange: androidx.work.CoroutineWorker#doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result>) parameter #0:
     Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.doWork
-ParameterNameChange: androidx.work.CoroutineWorker#getForegroundInfo(kotlin.coroutines.Continuation<? super androidx.work.ForegroundInfo>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.getForegroundInfo
 ParameterNameChange: androidx.work.CoroutineWorker#setForeground(androidx.work.ForegroundInfo, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.work.CoroutineWorker.setForeground
 ParameterNameChange: androidx.work.CoroutineWorker#setProgress(androidx.work.Data, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
@@ -11,6 +8,7 @@
 ParameterNameChange: androidx.work.OperationKt#await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.work.OperationKt.await
 
+
 RemovedClass: androidx.work.OneTimeWorkRequestKt:
     Removed class androidx.work.OneTimeWorkRequestKt
 RemovedClass: androidx.work.PeriodicWorkRequestKt:
diff --git a/work/work-runtime-ktx/api/restricted_current.ignore b/work/work-runtime-ktx/api/restricted_current.ignore
index fb0fcc1..342948d 100644
--- a/work/work-runtime-ktx/api/restricted_current.ignore
+++ b/work/work-runtime-ktx/api/restricted_current.ignore
@@ -1,9 +1,6 @@
 // Baseline format: 1.0
-
 ParameterNameChange: androidx.work.CoroutineWorker#doWork(kotlin.coroutines.Continuation<? super androidx.work.ListenableWorker.Result>) parameter #0:
     Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.doWork
-ParameterNameChange: androidx.work.CoroutineWorker#getForegroundInfo(kotlin.coroutines.Continuation<? super androidx.work.ForegroundInfo>) parameter #0:
-    Attempted to remove parameter name from parameter arg1 in androidx.work.CoroutineWorker.getForegroundInfo
 ParameterNameChange: androidx.work.CoroutineWorker#setForeground(androidx.work.ForegroundInfo, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.work.CoroutineWorker.setForeground
 ParameterNameChange: androidx.work.CoroutineWorker#setProgress(androidx.work.Data, kotlin.coroutines.Continuation<? super kotlin.Unit>) parameter #1:
@@ -11,6 +8,7 @@
 ParameterNameChange: androidx.work.OperationKt#await(androidx.work.Operation, kotlin.coroutines.Continuation<? super androidx.work.Operation.State.SUCCESS>) parameter #1:
     Attempted to remove parameter name from parameter arg2 in androidx.work.OperationKt.await
 
+
 RemovedClass: androidx.work.OneTimeWorkRequestKt:
     Removed class androidx.work.OneTimeWorkRequestKt
 RemovedClass: androidx.work.PeriodicWorkRequestKt:
diff --git a/work/work-runtime/api/current.ignore b/work/work-runtime/api/current.ignore
index ec6f497..325f65fc 100644
--- a/work/work-runtime/api/current.ignore
+++ b/work/work-runtime/api/current.ignore
@@ -19,8 +19,6 @@
     Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setConstraints(androidx.work.Constraints):
     Method androidx.work.WorkRequest.Builder.setConstraints has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
-ChangedType: androidx.work.WorkRequest.Builder#setExpedited(androidx.work.OutOfQuotaPolicy):
-    Method androidx.work.WorkRequest.Builder.setExpedited has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(java.time.Duration):
     Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(long, java.util.concurrent.TimeUnit):
diff --git a/work/work-runtime/api/restricted_current.ignore b/work/work-runtime/api/restricted_current.ignore
index ec6f497..325f65fc 100644
--- a/work/work-runtime/api/restricted_current.ignore
+++ b/work/work-runtime/api/restricted_current.ignore
@@ -19,8 +19,6 @@
     Method androidx.work.WorkRequest.Builder.setBackoffCriteria has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setConstraints(androidx.work.Constraints):
     Method androidx.work.WorkRequest.Builder.setConstraints has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
-ChangedType: androidx.work.WorkRequest.Builder#setExpedited(androidx.work.OutOfQuotaPolicy):
-    Method androidx.work.WorkRequest.Builder.setExpedited has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(java.time.Duration):
     Method androidx.work.WorkRequest.Builder.setInitialDelay has changed return type from B (extends androidx.work.WorkRequest.Builder<?, ?>) to B (extends androidx.work.WorkRequest.Builder<B, ?>)
 ChangedType: androidx.work.WorkRequest.Builder#setInitialDelay(long, java.util.concurrent.TimeUnit):