Merge "Implementing a check for verifying that equals() and hashCode() are implemented by the key of a multimap query result." into androidx-main
diff --git a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
index 9da389c..68e1f87 100644
--- a/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
+++ b/activity/activity-compose/src/androidTest/java/androidx/activity/compose/ActivityResultRegistryTest.kt
@@ -18,6 +18,7 @@
 
 import android.app.Activity.RESULT_OK
 import android.content.Intent
+import android.os.Bundle
 import androidx.activity.ComponentActivity
 import androidx.activity.result.ActivityResult
 import androidx.activity.result.ActivityResultLauncher
@@ -155,9 +156,39 @@
             }
         }
 
+        val savedState = Bundle()
+        registryOwner.activityResultRegistry.onSaveInstanceState(savedState)
+
         activityScenario.recreate()
 
-        activityScenario.onActivity {
+        val restoredOwner = ActivityResultRegistryOwner {
+            object : ActivityResultRegistry() {
+                override fun <I : Any?, O : Any?> onLaunch(
+                    requestCode: Int,
+                    contract: ActivityResultContract<I, O>,
+                    input: I,
+                    options: ActivityOptionsCompat?
+                ) {
+                    launchCount++
+                }
+            }
+        }
+
+        restoredOwner.activityResultRegistry.onRestoreInstanceState(savedState)
+
+        activityScenario.onActivity { activity ->
+            (activity as ComponentActivity).setContent {
+                CompositionLocalProvider(
+                    LocalActivityResultRegistryOwner provides restoredOwner
+                ) {
+                    launcher = rememberLauncherForActivityResult(
+                        ActivityResultContracts.StartActivityForResult()
+                    ) {}
+                }
+            }
+        }
+
+        composeTestRule.runOnIdle {
             launcher?.launch(Intent()) ?: fail("launcher was not composed")
             assertWithMessage("the registry was not invoked")
                 .that(launchCount)
diff --git a/activity/activity/src/androidTest/java/androidx/activity/result/ActivityResultRegistryTest.kt b/activity/activity/src/androidTest/java/androidx/activity/result/ActivityResultRegistryTest.kt
index 3c8c327..03474b9 100644
--- a/activity/activity/src/androidTest/java/androidx/activity/result/ActivityResultRegistryTest.kt
+++ b/activity/activity/src/androidTest/java/androidx/activity/result/ActivityResultRegistryTest.kt
@@ -447,6 +447,24 @@
     }
 
     @Test
+    fun testLaunchUnregistered() {
+        val contract = StartActivityForResult()
+        val activityResult = registry.register("key", contract) { }
+
+        activityResult.unregister()
+
+        try {
+            activityResult.launch(null)
+        } catch (e: IllegalStateException) {
+            assertThat(e).hasMessageThat().contains(
+                "Attempting to launch an unregistered ActivityResultLauncher with contract " +
+                    contract + " and input null. You must ensure the ActivityResultLauncher is " +
+                    "registered before calling launch()."
+            )
+        }
+    }
+
+    @Test
     fun testSavePendingOnRestore() {
         var code = 0
         val noDispatchRegistry = object : ActivityResultRegistry() {
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.java b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.java
index b60d58e..65d3aa2 100644
--- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.java
+++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultCaller.java
@@ -33,7 +33,7 @@
      * Register a request to {@link Activity#startActivityForResult start an activity for result},
      * designated by the given {@link ActivityResultContract contract}.
      *
-     * This creates a record in the {@link ActivityResultRegistry registry} associated wit this
+     * This creates a record in the {@link ActivityResultRegistry registry} associated with this
      * caller, managing request code, as well as conversions to/from {@link Intent} under the hood.
      *
      * This *must* be called unconditionally, as part of initialization path, typically as a field
diff --git a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
index d7fb56a..f91f4cc 100644
--- a/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
+++ b/activity/activity/src/main/java/androidx/activity/result/ActivityResultRegistry.java
@@ -124,7 +124,7 @@
                     + "they are STARTED.");
         }
 
-        final int requestCode = registerKey(key);
+        registerKey(key);
         LifecycleContainer lifecycleContainer = mKeyToLifecycleContainers.get(key);
         if (lifecycleContainer == null) {
             lifecycleContainer = new LifecycleContainer(lifecycle);
@@ -162,9 +162,15 @@
         return new ActivityResultLauncher<I>() {
             @Override
             public void launch(I input, @Nullable ActivityOptionsCompat options) {
-                mLaunchedKeys.add(key);
                 Integer innerCode = mKeyToRc.get(key);
-                onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
+                if (innerCode == null) {
+                    throw new IllegalStateException("Attempting to launch an unregistered "
+                            + "ActivityResultLauncher with contract " + contract + " and input "
+                            + input + ". You must ensure the ActivityResultLauncher is registered "
+                            + "before calling launch().");
+                }
+                mLaunchedKeys.add(key);
+                onLaunch(innerCode, contract, input, options);
             }
 
             @Override
@@ -201,7 +207,7 @@
             @NonNull final String key,
             @NonNull final ActivityResultContract<I, O> contract,
             @NonNull final ActivityResultCallback<O> callback) {
-        final int requestCode = registerKey(key);
+        registerKey(key);
         mKeyToCallback.put(key, new CallbackAndContract<>(callback, contract));
 
         if (mParsedPendingResults.containsKey(key)) {
@@ -221,9 +227,15 @@
         return new ActivityResultLauncher<I>() {
             @Override
             public void launch(I input, @Nullable ActivityOptionsCompat options) {
-                mLaunchedKeys.add(key);
                 Integer innerCode = mKeyToRc.get(key);
-                onLaunch((innerCode != null) ? innerCode : requestCode, contract, input, options);
+                if (innerCode == null) {
+                    throw new IllegalStateException("Attempting to launch an unregistered "
+                            + "ActivityResultLauncher with contract " + contract + " and input "
+                            + input + ". You must ensure the ActivityResultLauncher is registered "
+                            + "before calling launch().");
+                }
+                mLaunchedKeys.add(key);
+                onLaunch(innerCode, contract, input, options);
             }
 
             @Override
@@ -398,14 +410,13 @@
         }
     }
 
-    private int registerKey(String key) {
+    private void registerKey(String key) {
         Integer existing = mKeyToRc.get(key);
         if (existing != null) {
-            return existing;
+            return;
         }
         int rc = generateRandomNumber();
         bindRcKey(rc, key);
-        return rc;
     }
 
     /**
diff --git a/benchmark/integration-tests/crystalball-experiment/build.gradle b/benchmark/integration-tests/crystalball-experiment/build.gradle
deleted file mode 100644
index 068b678..0000000
--- a/benchmark/integration-tests/crystalball-experiment/build.gradle
+++ /dev/null
@@ -1,96 +0,0 @@
-/*
- * Copyright (C) 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.
- */
-
-import androidx.build.LibraryGroups
-import androidx.build.Publish
-
-plugins {
-    id("AndroidXPlugin")
-    id("com.android.library")
-    id("kotlin-android")
-}
-
-android {
-    defaultConfig {
-        minSdkVersion 18
-        multiDexEnabled true
-        // testInstrumentationRunner 'androidx.test.runner.AndroidJUnitRunner'
-        // Listener List
-        testInstrumentationRunnerArgument "listener",
-                        "android.device.collectors.CpuUsageListener," +
-                        "android.device.collectors.ProcLoadListener," +
-                        "android.device.collectors.PerfettoListener," +
-                        "android.device.collectors.AppStartupListener," +
-                        "android.device.collectors.JankListener," +
-                        "android.device.collectors.CrashListener," +
-                        "android.device.collectors.ScreenshotOnFailureCollector," +
-                        "android.device.collectors.LogcatOnFailureCollector," +
-                        "android.device.collectors.IncidentReportListener," +
-                        "android.device.collectors.TotalPssMetricListener"
-
-        // ProcLoadListener
-        testInstrumentationRunnerArgument "procload-collector:proc-loadavg-interval", "2000"
-        testInstrumentationRunnerArgument "procload-collector:proc-loadavg-threshold", "0.5"
-        testInstrumentationRunnerArgument "procload-collector:proc-loadavg-timeout", "90000"
-
-        // CpuUsageListener
-        testInstrumentationRunnerArgument "cpuusage-collector:disable_per_freq", "true"
-        testInstrumentationRunnerArgument "cpuusage-collector:disable_per_pkg", "true"
-
-        // TotalPssMetricListener
-        testInstrumentationRunnerArgument "totalpss-collector:process-names", "androidx.compose.integration.demos"
-
-        // JankListener (disable)
-        testInstrumentationRunnerArgument "jank-listener:log", "true"
-
-        // Microbenchmark runner configuration
-        testInstrumentationRunnerArgument "iterations", "1"
-    }
-}
-
-dependencies {
-    api(libs.junit)
-    api(libs.kotlinStdlib)
-    api("androidx.annotation:annotation:1.1.0")
-    // TODO: remove, once we remove the minor usages in CrystalBall
-    implementation(libs.guavaAndroid)
-    androidTestImplementation("com.android:collector-device-lib:0.1.0")
-    androidTestImplementation("com.android:collector-device-lib-platform:0.1.0")
-    androidTestImplementation("com.android:collector-helper-utilities:0.1.0")
-    androidTestImplementation("com.android:jank-helper:0.1.0")
-    androidTestImplementation("com.android:memory-helper:0.1.0")
-    androidTestImplementation("com.android:perfetto-helper:0.1.0")
-    androidTestImplementation("com.android:platform-test-composers:0.1.0")
-    androidTestImplementation("com.android:power-helper:0.1.0")
-    androidTestImplementation("com.android:simpleperf-helper:0.1.0")
-    androidTestImplementation("com.android:statsd-helper:0.1.0")
-    androidTestImplementation("com.android:system-metric-helper:0.1.0")
-    androidTestImplementation("com.android:test-composers:0.1.0")
-    androidTestImplementation("com.android:platform-test-rules:0.1.0")
-    androidTestImplementation("com.android:microbenchmark-device-lib:0.1.0")
-    androidTestImplementation("androidx.test:rules:1.3.0")
-    androidTestImplementation("androidx.test:runner:1.3.0")
-    implementation(libs.testExtJunit)
-    implementation(libs.testUiautomator)
-}
-
-androidx {
-    name = "Android Benchmark - Crystalball experiment"
-    publish = Publish.NONE
-    mavenGroup = LibraryGroups.BENCHMARK
-    inceptionYear = "2020"
-    description = "Android Benchmark - Crystalball experiment"
-}
diff --git a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/JankCollectionHelperTest.kt b/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/JankCollectionHelperTest.kt
deleted file mode 100644
index af49322..0000000
--- a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/JankCollectionHelperTest.kt
+++ /dev/null
@@ -1,36 +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
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import androidx.test.filters.SdkSuppress
-import com.android.helpers.JankCollectionHelper
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class JankCollectionHelperTest {
-    // Setting a minSdkVersion of 27 because JankHelper fails with an error on API 26.
-    // Needs a fix in JankHelper and re-import of prebults.
-    // https://android-build.googleplex.com/builds/tests/view?invocationId=I00300005943166534&redirect=http://sponge2/025964b6-d278-44a7-805c-56d8010935a8
-    @Test
-    @SdkSuppress(minSdkVersion = 27)
-    fun trivialTest() {
-        JankCollectionHelper()
-    }
-}
diff --git a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/OpenAppMicrobenchmark.kt b/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/OpenAppMicrobenchmark.kt
deleted file mode 100644
index d975384..0000000
--- a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/OpenAppMicrobenchmark.kt
+++ /dev/null
@@ -1,86 +0,0 @@
-/*
- * Copyright 2019 The Android Open Source Project
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- *      http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
-
-package androidx.benchmark.macro
-
-import android.annotation.SuppressLint
-import android.content.Context
-import android.content.Intent
-import android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK
-import android.content.Intent.FLAG_ACTIVITY_NEW_TASK
-import android.platform.test.microbenchmark.Microbenchmark
-import android.platform.test.rule.CompilationFilterRule
-import android.platform.test.rule.DropCachesRule
-import android.platform.test.rule.KillAppsRule
-import android.platform.test.rule.NaturalOrientationRule
-import android.platform.test.rule.PressHomeRule
-import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.uiautomator.By
-import androidx.test.uiautomator.UiDevice
-import androidx.test.uiautomator.Until
-import org.junit.AfterClass
-import org.junit.ClassRule
-import org.junit.Ignore
-import org.junit.Rule
-import org.junit.Test
-import org.junit.rules.RuleChain
-import org.junit.runner.RunWith
-
-@SuppressLint("UnsupportedTestRunner")
-@RunWith(Microbenchmark::class)
-class OpenAppMicrobenchmark {
-
-    @Test
-    @LargeTest
-    @Ignore("Gradle arguments are not passed to the TestRunner")
-    fun open() {
-        // Launch the app
-        val context: Context = InstrumentationRegistry.getInstrumentation().context
-        val intent: Intent = context.packageManager.getLaunchIntentForPackage(thePackage)!!
-        // Clear out any previous instances
-        intent.flags = FLAG_ACTIVITY_NEW_TASK or FLAG_ACTIVITY_CLEAR_TASK
-        context.startActivity(intent)
-        device.wait(
-            Until.hasObject(By.pkg(thePackage).depth(0)),
-            5000
-        )
-    }
-
-    // Method-level rules
-    @get:Rule
-    var rules: RuleChain = RuleChain.outerRule(KillAppsRule(thePackage))
-        .around(DropCachesRule())
-        .around(CompilationFilterRule(thePackage))
-        .around(PressHomeRule())
-
-    companion object {
-        // Pixel Settings App
-        const val thePackage = "com.android.settings"
-        private val device = UiDevice.getInstance(InstrumentationRegistry.getInstrumentation())
-
-        @JvmStatic
-        @AfterClass
-        fun close() {
-            device.pressHome()
-        }
-
-        // Class-level rules
-        @JvmField
-        @ClassRule
-        var orientationRule = NaturalOrientationRule()
-    }
-}
diff --git a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/TrivialTest.kt b/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/TrivialTest.kt
deleted file mode 100644
index 4c660b6..0000000
--- a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/TrivialTest.kt
+++ /dev/null
@@ -1,31 +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
-
-import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.MediumTest
-import org.junit.Test
-import org.junit.runner.RunWith
-
-@MediumTest
-@RunWith(AndroidJUnit4::class)
-class TrivialTest {
-    @Test
-    fun foo() {
-        BenchmarkClass()
-    }
-}
diff --git a/benchmark/integration-tests/crystalball-experiment/src/main/AndroidManifest.xml b/benchmark/integration-tests/crystalball-experiment/src/main/AndroidManifest.xml
deleted file mode 100644
index a599d46..0000000
--- a/benchmark/integration-tests/crystalball-experiment/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1,17 +0,0 @@
-<?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");
-  ~ you may not use this file except in compliance with the License.
-  ~ You may obtain a copy of the License at
-  ~
-  ~      http://www.apache.org/licenses/LICENSE-2.0
-  ~
-  ~ Unless required by applicable law or agreed to in writing, software
-  ~ distributed under the License is distributed on an "AS IS" BASIS,
-  ~ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
-  ~ See the License for the specific language governing permissions and
-  ~ limitations under the License.
-  -->
-<manifest package="androidx.benchmark.macro"/>
diff --git a/benchmark/integration-tests/crystalball-experiment/src/scripts/copy_crystalball_prebuilts.py b/benchmark/integration-tests/crystalball-experiment/src/scripts/copy_crystalball_prebuilts.py
deleted file mode 100755
index e3880a7..0000000
--- a/benchmark/integration-tests/crystalball-experiment/src/scripts/copy_crystalball_prebuilts.py
+++ /dev/null
@@ -1,77 +0,0 @@
-#!/usr/bin/python3
-
-import argparse
-import os
-import shutil
-
-
-
-SOONG_SUFFIX = '/android_common/javac'
-VERSION = '0.1.0'
-
-
-def main():
-    parser = argparse.ArgumentParser(description='Copy Crystalball Prebuilts')
-    parser.add_argument('soong_path', action="store", help='Looks like "/usr/local/google/home/rahulrav/Workspace/internal_android/out/soong/.intermediates"')
-    parser.add_argument('prebuilts_directory', action="store", help='Looks like "/mnt/Android/Flatfoot/androidx_main/prebuilts/androidx/external/com/android/"')
-    parser.add_argument('--verbose', action="store_true", default=False)
-    parse_result = parser.parse_args()
-
-    soong_path = parse_result.soong_path
-    soong_output_directory = f'{soong_path}/platform_testing/libraries/'
-    prebuilts_directory = parse_result.prebuilts_directory
-
-    mapping = dict([
-        # Device Collectors
-        (f'{soong_output_directory}/device-collectors/src/main/collector-device-lib/{SOONG_SUFFIX}/collector-device-lib.jar', f'{prebuilts_directory}/collector-device-lib/{VERSION}/collector-device-lib-{VERSION}.jar'),
-        # Collectors Device Lib Platform
-        (f'{soong_output_directory}/device-collectors/src/main/platform-collectors/collector-device-lib-platform/{SOONG_SUFFIX}/collector-device-lib-platform.jar', f'{prebuilts_directory}/collector-device-lib-platform/{VERSION}/collector-device-lib-platform-{VERSION}.jar'),
-        # Collector Helpers
-        (f'{soong_output_directory}/collectors-helper/utilities/collector-helper-utilities/{SOONG_SUFFIX}/collector-helper-utilities.jar', f'{prebuilts_directory}/collector-helper-utilities/{VERSION}/collector-helper-utilities-{VERSION}.jar'),
-        (f'{soong_output_directory}/collectors-helper/jank/jank-helper/{SOONG_SUFFIX}/jank-helper.jar', f'{prebuilts_directory}/jank-helper/{VERSION}/jank-helper-{VERSION}.jar'),
-        (f'{soong_output_directory}/collectors-helper/memory/memory-helper/{SOONG_SUFFIX}/memory-helper.jar', f'{prebuilts_directory}/memory-helper/{VERSION}/memory-helper-{VERSION}.jar'),
-        # Microbenchmarks
-        (f'{soong_output_directory}/health/runners/microbenchmark/microbenchmark-device-lib/{SOONG_SUFFIX}/microbenchmark-device-lib.jar', f'{prebuilts_directory}/microbenchmark-device-lib/{VERSION}/microbenchmark-device-lib-{VERSION}.jar'),
-        # Perfetto Helper
-        (f'{soong_output_directory}/collectors-helper/perfetto/perfetto-helper/{SOONG_SUFFIX}/perfetto-helper.jar', f'{prebuilts_directory}/perfetto-helper/{VERSION}/perfetto-helper-{VERSION}.jar'),
-        # Platform Protos
-        (f'{soong_path}/frameworks/base/platformprotoslite/{SOONG_SUFFIX}/platformprotoslite.jar', f'{prebuilts_directory}/platformprotoslite/{VERSION}/platformprotoslite-{VERSION}.jar'),
-        (f'{soong_path}/frameworks/base/platformprotosnano/{SOONG_SUFFIX}/platformprotosnano.jar', f'{prebuilts_directory}/platformprotosnano/{VERSION}/platformprotosnano-{VERSION}.jar'),
-        # Platform Test Composers
-        (f'{soong_output_directory}/health/composers/platform/platform-test-composers/{SOONG_SUFFIX}/platform-test-composers.jar', f'{prebuilts_directory}/platform-test-composers/{VERSION}/platform-test-composers-{VERSION}.jar'),
-        # Platform Test Rules
-        (f'{soong_output_directory}/health/rules/platform-test-rules/{SOONG_SUFFIX}/platform-test-rules.jar', f'{prebuilts_directory}/platform-test-rules/{VERSION}/platform-test-rules-{VERSION}.jar'),
-        # Power Helper
-        (f'{soong_output_directory}/collectors-helper/power/power-helper/{SOONG_SUFFIX}/power-helper.jar', f'{prebuilts_directory}/power-helper/{VERSION}/power-helper-{VERSION}.jar'),
-        # Simple Perf Helper
-        (f'{soong_output_directory}/collectors-helper/simpleperf/simpleperf-helper/{SOONG_SUFFIX}/simpleperf-helper.jar', f'{prebuilts_directory}/simpleperf-helper/{VERSION}/simpleperf-helper-{VERSION}.jar'),
-        # Statsd Helper
-        (f'{soong_output_directory}/collectors-helper/statsd/statsd-helper/{SOONG_SUFFIX}/statsd-helper.jar', f'{prebuilts_directory}/statsd-helper/{VERSION}/statsd-helper-{VERSION}.jar'),
-        # Statsd Protos
-        (f'{soong_path}/frameworks/base/cmds/statsd/statsdprotonano/{SOONG_SUFFIX}/statsdprotonano.jar', f'{prebuilts_directory}/statsdprotonano/{VERSION}/statsdprotonano-{VERSION}.jar'),
-        # System Metric Helpers
-        (f'{soong_output_directory}/collectors-helper/system/system-metric-helper/{SOONG_SUFFIX}/system-metric-helper.jar', f'{prebuilts_directory}/system-metric-helper/{VERSION}/system-metric-helper-{VERSION}.jar'),
-        # Test Composers
-        (f'{soong_output_directory}/health/composers/host/test-composers/{SOONG_SUFFIX}/test-composers.jar', f'{prebuilts_directory}/test-composers/{VERSION}/test-composers-{VERSION}.jar'),
-    ])
-
-    size = len(mapping)
-    if parse_result.verbose:
-        print(f'Total number of entries = {size}')
-
-    for key in mapping:
-        if not os.path.exists(key):
-            print(f'Artifact at {key} does not exist. You may have forgotten to build the necessary artifacts.')
-
-        if os.path.exists(mapping[key]) and parse_result.verbose:
-            print(f'Artifact at {mapping[key]} will be overwritten')
-
-        shutil.copyfile(key, mapping[key])
-
-        if parse_result.verbose:
-            print(f'Copied from {key} to {mapping[key]}\n')
-
-    print('All done.')
-
-if __name__ == "__main__":
-    main()
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/JankCollectionHelper.java b/benchmark/macro/src/main/java/androidx/benchmark/macro/JankCollectionHelper.java
index 47db363..d0b11e8 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/JankCollectionHelper.java
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/JankCollectionHelper.java
@@ -26,7 +26,7 @@
 import androidx.test.uiautomator.UiDevice;
 
 import java.io.IOException;
-import java.util.Arrays;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Map;
@@ -37,7 +37,7 @@
 /** Collects jank metrics for all or a list of processes. */
 class JankCollectionHelper {
 
-    private static final String LOG_TAG = "JankCollectionHelper";
+    private static final String LOG_TAG = JankCollectionHelper.class.getSimpleName();
 
     // Prefix for all output metrics that come from the gfxinfo dump.
     @VisibleForTesting static final String GFXINFO_METRICS_PREFIX = "gfxinfo";
@@ -59,34 +59,50 @@
                 "total_frames"),
         // Example: "Janky frames: 785 (3.85%)"
         JANKY_FRAMES_COUNT(
-                Pattern.compile(".*Janky frames: (\\d+) \\((.+)\\%\\).*", Pattern.DOTALL),
+                Pattern.compile(
+                        ".*Janky frames: (\\d+) \\(([0-9]+[\\.]?[0-9]+)\\%\\).*", Pattern.DOTALL),
                 1,
                 "janky_frames_count"),
         // Example: "Janky frames: 785 (3.85%)"
         JANKY_FRAMES_PRCNT(
-                Pattern.compile(".*Janky frames: (\\d+) \\((.+)\\%\\).*", Pattern.DOTALL),
+                Pattern.compile(
+                        ".*Janky frames: (\\d+) \\(([0-9]+[\\.]?[0-9]+)\\%\\).*", Pattern.DOTALL),
                 2,
                 "janky_frames_percent"),
+        // Example: "Janky frames (legacy): 785 (3.85%)"
+        JANKY_FRAMES_LEGACY_COUNT(
+                Pattern.compile(
+                        ".*Janky frames \\(legacy\\): (\\d+) \\(([0-9]+[\\.]?[0-9]+)\\%\\).*",
+                        Pattern.DOTALL),
+                1,
+                "janky_frames_legacy_count"),
+        // Example: "Janky frames (legacy): 785 (3.85%)"
+        JANKY_FRAMES_LEGACY_PRCNT(
+                Pattern.compile(
+                        ".*Janky frames \\(legacy\\): (\\d+) \\(([0-9]+[\\.]?[0-9]+)\\%\\).*",
+                        Pattern.DOTALL),
+                2,
+                "janky_frames_legacy_percent"),
         // Example: "50th percentile: 9ms"
         FRAME_TIME_50TH(
                 Pattern.compile(".*50th percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "jank_percentile_50"),
+                "frame_render_time_percentile_50"),
         // Example: "90th percentile: 9ms"
         FRAME_TIME_90TH(
                 Pattern.compile(".*90th percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "jank_percentile_90"),
+                "frame_render_time_percentile_90"),
         // Example: "95th percentile: 9ms"
         FRAME_TIME_95TH(
                 Pattern.compile(".*95th percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "jank_percentile_95"),
+                "frame_render_time_percentile_95"),
         // Example: "99th percentile: 9ms"
         FRAME_TIME_99TH(
                 Pattern.compile(".*99th percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "jank_percentile_99"),
+                "frame_render_time_percentile_99"),
         // Example: "Number Missed Vsync: 0"
         NUM_MISSED_VSYNC(
                 Pattern.compile(".*Number Missed Vsync: (\\d+).*", Pattern.DOTALL),
@@ -117,26 +133,32 @@
                 Pattern.compile(".*Number Frame deadline missed: (\\d+).*", Pattern.DOTALL),
                 1,
                 "deadline_missed"),
+        // Number Frame deadline missed (legacy): 0
+        NUM_FRAME_DEADLINE_MISSED_LEGACY(
+                Pattern.compile(
+                        ".*Number Frame deadline missed \\(legacy\\): (\\d+).*", Pattern.DOTALL),
+                1,
+                "deadline_missed_legacy"),
         // Example: "50th gpu percentile: 9ms"
         GPU_FRAME_TIME_50TH(
                 Pattern.compile(".*50th gpu percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "gpu_jank_percentile_50"),
+                "gpu_frame_render_time_percentile_50"),
         // Example: "90th gpu percentile: 9ms"
         GPU_FRAME_TIME_90TH(
                 Pattern.compile(".*90th gpu percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "gpu_jank_percentile_90"),
+                "gpu_frame_render_time_percentile_90"),
         // Example: "95th gpu percentile: 9ms"
         GPU_FRAME_TIME_95TH(
                 Pattern.compile(".*95th gpu percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "gpu_jank_percentile_95"),
+                "gpu_frame_render_time_percentile_95"),
         // Example: "99th gpu percentile: 9ms"
         GPU_FRAME_TIME_99TH(
                 Pattern.compile(".*99th gpu percentile: (\\d+)ms.*", Pattern.DOTALL),
                 1,
-                "gpu_jank_percentile_99");
+                "gpu_frame_render_time_percentile_99");
 
         private final Pattern mPattern;
         private final int mGroupIndex;
@@ -237,7 +259,7 @@
 
     /** Add a package or list of packages to be tracked. */
     public void addTrackedPackages(@NonNull String... packages) {
-        mTrackedPackages.addAll(Arrays.asList(packages));
+        Collections.addAll(mTrackedPackages, packages);
     }
 
     /** Clear the {@code gfxinfo} for all packages. */
diff --git a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
index 8a23f8a..8657fea 100644
--- a/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
+++ b/benchmark/macro/src/main/java/androidx/benchmark/macro/Metric.kt
@@ -84,23 +84,26 @@
      * via [androidx.benchmark.Stats.putInBundle].
      */
     private val keyRenameMap = mapOf(
-        "jank_percentile_50" to "frameTime50thPercentileMs",
-        "jank_percentile_90" to "frameTime90thPercentileMs",
-        "jank_percentile_95" to "frameTime95thPercentileMs",
-        "jank_percentile_99" to "frameTime99thPercentileMs",
-        "gpu_jank_percentile_50" to "gpuFrameTime50thPercentileMs",
-        "gpu_jank_percentile_90" to "gpuFrameTime90thPercentileMs",
-        "gpu_jank_percentile_95" to "gpuFrameTime95thPercentileMs",
-        "gpu_jank_percentile_99" to "gpuFrameTime99thPercentileMs",
+        "frame_render_time_percentile_50" to "frameTime50thPercentileMs",
+        "frame_render_time_percentile_90" to "frameTime90thPercentileMs",
+        "frame_render_time_percentile_95" to "frameTime95thPercentileMs",
+        "frame_render_time_percentile_99" to "frameTime99thPercentileMs",
+        "gpu_frame_render_time_percentile_50" to "gpuFrameTime50thPercentileMs",
+        "gpu_frame_render_time_percentile_90" to "gpuFrameTime90thPercentileMs",
+        "gpu_frame_render_time_percentile_95" to "gpuFrameTime95thPercentileMs",
+        "gpu_frame_render_time_percentile_99" to "gpuFrameTime99thPercentileMs",
         "missed_vsync" to "vsyncMissedFrameCount",
         "deadline_missed" to "deadlineMissedFrameCount",
+        "deadline_missed_legacy" to "deadlineMissedFrameCountLegacy",
         "janky_frames_count" to "jankyFrameCount",
+        "janky_frames_legacy_count" to "jankyFrameCountLegacy",
         "high_input_latency" to "highInputLatencyFrameCount",
         "slow_ui_thread" to "slowUiThreadFrameCount",
         "slow_bmp_upload" to "slowBitmapUploadFrameCount",
         "slow_issue_draw_cmds" to "slowIssueDrawCommandsFrameCount",
         "total_frames" to "totalFrameCount",
-        "janky_frames_percent" to "jankyFramePercent"
+        "janky_frames_percent" to "jankyFramePercent",
+        "janky_frames_legacy_percent" to "jankyFramePercentLegacy"
     )
 
     /**
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index b7e9cf1..3e175c1 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -25,7 +25,8 @@
 import androidx.build.studio.StudioTask.Companion.registerStudioTask
 import androidx.build.testConfiguration.registerOwnersServiceTasks
 import androidx.build.uptodatedness.TaskUpToDateValidator
-import com.android.build.gradle.api.AndroidBasePlugin
+import com.android.build.gradle.AppPlugin
+import com.android.build.gradle.LibraryPlugin
 import org.gradle.api.GradleException
 import org.gradle.api.Plugin
 import org.gradle.api.Project
@@ -114,10 +115,13 @@
                     }
                 )
             )
-            project.plugins.withType(AndroidBasePlugin::class.java) {
-                buildOnServerTask.dependsOn("${project.path}:assembleRelease")
-                if (!project.usingMaxDepVersions()) {
-                    project.afterEvaluate {
+            project.afterEvaluate {
+                if (project.plugins.hasPlugin(LibraryPlugin::class.java) ||
+                    project.plugins.hasPlugin(AppPlugin::class.java)
+                ) {
+
+                    buildOnServerTask.dependsOn("${project.path}:assembleRelease")
+                    if (!project.usingMaxDepVersions()) {
                         project.agpVariants.all { variant ->
                             // in AndroidX, release and debug variants are essentially the same,
                             // so we don't run the lintRelease task on the build server
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index c25342a..556ee60 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -56,7 +56,7 @@
     val CORE_ROLE = Version("1.1.0-alpha02")
     val CURSORADAPTER = Version("1.1.0-alpha01")
     val CUSTOMVIEW = Version("1.2.0-alpha01")
-    val DATASTORE = Version("1.0.0-rc01")
+    val DATASTORE = Version("1.0.0-rc02")
     val DOCUMENTFILE = Version("1.1.0-alpha01")
     val DRAWERLAYOUT = Version("1.2.0-alpha01")
     val DYNAMICANIMATION = Version("1.1.0-alpha04")
@@ -65,7 +65,7 @@
     val EMOJI2 = Version("1.0.0-alpha03")
     val ENTERPRISE = Version("1.1.0-rc01")
     val EXIFINTERFACE = Version("1.4.0-alpha01")
-    val FRAGMENT = Version("1.4.0-alpha04")
+    val FRAGMENT = Version("1.4.0-alpha05")
     val FUTURES = Version("1.2.0-alpha01")
     val GRIDLAYOUT = Version("1.1.0-alpha01")
     val HEALTH_SERVICES_CLIENT = Version("1.0.0-alpha02")
@@ -88,8 +88,8 @@
     val MEDIA = Version("1.5.0-alpha01")
     val MEDIA2 = Version("1.2.0-beta01")
     val MEDIAROUTER = Version("1.3.0-alpha01")
-    val NAVIGATION = Version("2.4.0-alpha04")
-    val PAGING = Version("3.1.0-alpha02")
+    val NAVIGATION = Version("2.4.0-alpha05")
+    val PAGING = Version("3.1.0-alpha03")
     val PAGING_COMPOSE = Version("1.0.0-alpha12")
     val PALETTE = Version("1.1.0-alpha01")
     val PRINT = Version("1.1.0-beta01")
@@ -130,24 +130,24 @@
     val VERSIONED_PARCELABLE = Version("1.2.0-alpha01")
     val VIEWPAGER = Version("1.1.0-alpha01")
     val VIEWPAGER2 = Version("1.1.0-alpha02")
-    val WEAR = Version("1.2.0-alpha11")
-    val WEAR_COMPLICATIONS_DATA = Version("1.0.0-alpha17")
-    val WEAR_COMPLICATIONS_DATA_SOURCE = Version("1.0.0-alpha17")
-    val WEAR_COMPOSE = Version("1.0.0-alpha01")
-    val WEAR_INPUT = Version("1.1.0-alpha03")
+    val WEAR = Version("1.2.0-alpha12")
+    val WEAR_COMPLICATIONS_DATA = Version("1.0.0-alpha18")
+    val WEAR_COMPLICATIONS_DATA_SOURCE = Version("1.0.0-alpha18")
+    val WEAR_COMPOSE = Version("1.0.0-alpha02")
+    val WEAR_INPUT = Version("1.1.0-beta01")
     val WEAR_INPUT_TESTING = WEAR_INPUT
-    val WEAR_ONGOING = Version("1.0.0-alpha08")
-    val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha05")
-    val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha04")
-    val WEAR_TILES = Version("1.0.0-alpha08")
-    val WEAR_WATCHFACE = Version("1.0.0-alpha17")
-    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha17")
+    val WEAR_ONGOING = Version("1.0.0-beta01")
+    val WEAR_PHONE_INTERACTIONS = Version("1.0.0-alpha06")
+    val WEAR_REMOTE_INTERACTIONS = Version("1.0.0-alpha05")
+    val WEAR_TILES = Version("1.0.0-alpha09")
+    val WEAR_WATCHFACE = Version("1.0.0-alpha18")
+    val WEAR_WATCHFACE_CLIENT = Version("1.0.0-alpha18")
     val WEAR_WATCHFACE_CLIENT_GUAVA = WEAR_WATCHFACE_CLIENT
-    val WEAR_WATCHFACE_COMPLICATIONS_RENDERING = Version("1.0.0-alpha17")
-    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha17")
-    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha17")
+    val WEAR_WATCHFACE_COMPLICATIONS_RENDERING = Version("1.0.0-alpha18")
+    val WEAR_WATCHFACE_DATA = Version("1.0.0-alpha18")
+    val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha18")
     val WEAR_WATCHFACE_EDITOR_GUAVA = WEAR_WATCHFACE_EDITOR
-    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha17")
+    val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha18")
     val WEBKIT = Version("1.5.0-alpha01")
     val WINDOW = Version("1.0.0-alpha09")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/dackka/DackkaTask.kt b/buildSrc/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
index 0166db9..9b12bef 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dackka/DackkaTask.kt
@@ -103,6 +103,10 @@
             "-globalLinks",
             linksConfiguration,
 
+            // Set logging level to only show warnings and errors
+            "-loggingLevel",
+            "WARN",
+
             // Configuration of sources. The generated string looks like this:
             // "-sourceSet -src /path/to/src -samples /path/to/samples ..."
             "-sourceSet",
diff --git a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
index 99e2728..e993e52 100644
--- a/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/dependencyTracker/AffectedModuleDetector.kt
@@ -508,6 +508,10 @@
                 ":emoji2:integration-tests:init-enabled-macrobenchmark-target",
             ),
             setOf(
+                ":wear:benchmark:integration-tests:macrobenchmark",
+                ":wear:benchmark:integration-tests:macrobenchmark-target"
+            ),
+            setOf(
                 ":wear:compose:integration-tests:macrobenchmark",
                 ":wear:compose:integration-tests:macrobenchmark-target"
             ),
diff --git a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
index 1947caa..5b3177d 100644
--- a/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/docs/AndroidXDocsPlugin.kt
@@ -560,7 +560,7 @@
     }
 }
 
-private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.7"
+private const val DACKKA_DEPENDENCY = "com.google.devsite:dackka:0.0.8"
 private const val DOCLAVA_DEPENDENCY = "com.android:doclava:1.0.6"
 
 // Allowlist for directories that should be processed by Dackka
@@ -576,13 +576,13 @@
     "androidx/benchmark/**",
     "androidx/biometric/**",
     "androidx/browser/**",
-//    "androidx/camera/**",
+    "androidx/camera/**",
     "androidx/car/**",
     "androidx/cardview/**",
     "androidx/collection/**",
     "androidx/compose/**",
     "androidx/concurrent/**",
-//    "androidx/contentpager/**",
+    "androidx/contentpager/**",
     "androidx/coordinatorlayout/**",
 //    "androidx/core/**",
     "androidx/cursoradapter/**",
@@ -599,7 +599,7 @@
     "androidx/gridlayout/**",
     "androidx/health/**",
     "androidx/heifwriter/**",
-//    "androidx/hilt/**",
+    "androidx/hilt/**",
     "androidx/interpolator/**",
 //    "androidx/leanback/**",
     "androidx/legacy/**",
@@ -607,7 +607,7 @@
     "androidx/loader/**",
     "androidx/localbroadcastmanager/**",
     "androidx/media/**",
-//    "androidx/media2/**",
+    "androidx/media2/**",
     "androidx/mediarouter/**",
     "androidx/navigation/**",
     "androidx/paging/**",
@@ -632,12 +632,12 @@
     "androidx/tracing/**",
     "androidx/transition/**",
     "androidx/tvprovider/**",
-//    "androidx/vectordrawable/**",
+    "androidx/vectordrawable/**",
     "androidx/versionedparcelable/**",
     "androidx/viewpager/**",
     "androidx/viewpager2/**",
     "androidx/wear/**",
-//    "androidx/webkit/**",
+    "androidx/webkit/**",
     "androidx/window/**",
     "androidx/work/**"
 )
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index f723ca4..0fb51b6 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -119,18 +119,24 @@
  * alternative project. Default is for the project to register the new config task to itself
  */
 fun Project.addAppApkToTestConfigGeneration(overrideProject: Project = this) {
+    if (project.isMacrobenchmarkTarget()) {
+        return
+    }
     // TODO(aurimas): migrate away from this when upgrading to AGP 7.1.0-alpha03 or newer
     @Suppress("DEPRECATION")
     extensions.getByType<
         com.android.build.api.extension.ApplicationAndroidComponentsExtension
         >().apply {
-        onVariants(selector().withBuildType("debug")) { debugVariant ->
-            overrideProject.tasks.withType(GenerateTestConfigurationTask::class.java)
-                .configureEach {
-                    it.appFolder.set(debugVariant.artifacts.get(SingleArtifact.APK))
-                    it.appLoader.set(debugVariant.artifacts.getBuiltArtifactsLoader())
-                    it.appProjectPath.set(overrideProject.path)
-                }
+        onVariants(selector().withBuildType("debug")) { appVariant ->
+            overrideProject.tasks.named(
+                "${AndroidXPlugin.GENERATE_TEST_CONFIGURATION_TASK}" +
+                    "${appVariant.name}AndroidTest"
+            ) { configTask ->
+                configTask as GenerateTestConfigurationTask
+                configTask.appFolder.set(appVariant.artifacts.get(SingleArtifact.APK))
+                configTask.appLoader.set(appVariant.artifacts.getBuiltArtifactsLoader())
+                configTask.appProjectPath.set(overrideProject.path)
+            }
         }
     }
 }
@@ -340,7 +346,7 @@
         this.rootProject.tasks.findByName(
             AndroidXPlugin.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
         )!!.dependsOn(configTask)
-    } else if (path.endsWith("macrobenchmark-target")) {
+    } else if (isMacrobenchmarkTarget()) {
         configTask.configure { task ->
             task.appFolder.set(artifacts.get(SingleArtifact.APK))
             task.appLoader.set(artifacts.getBuiltArtifactsLoader())
@@ -349,6 +355,13 @@
     }
 }
 
+/**
+ * Tells whether this project is the macrobenchmark-target project
+ */
+fun Project.isMacrobenchmarkTarget(): Boolean {
+    return path.endsWith("macrobenchmark-target")
+}
+
 fun Project.configureTestConfigGeneration(baseExtension: BaseExtension) {
     // TODO(aurimas): migrate away from this when upgrading to AGP 7.1.0-alpha03 or newer
     @Suppress("DEPRECATION")
@@ -381,7 +394,7 @@
                     )
                 }
                 path.endsWith("macrobenchmark") ||
-                    path.endsWith("macrobenchmark-target") -> {
+                    isMacrobenchmarkTarget() -> {
                     configureMacrobenchmarkConfigTask(
                         androidTest.name,
                         androidTest.artifacts,
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index 14f8dc9..4ba41d1 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -47,7 +47,8 @@
 function buildAndroidx() {
   LOG_PROCESSOR="$SCRIPT_DIR/../development/build_log_processor.sh"
   properties="-Pandroidx.summarizeStderr --no-daemon -Pandroidx.allWarningsAsErrors"
-  "$LOG_PROCESSOR"                   $gw $properties -p frameworks/support    listTaskOutputs bOS -x verifyDependencyVersions --stacktrace -PverifyUpToDate --profile
+  # runErrorProne is disabled due to I77d9800990e2a46648f7ed2713c54398cd798a0d in AGP
+  "$LOG_PROCESSOR"                   $gw $properties -p frameworks/support    listTaskOutputs bOS -x verifyDependencyVersions -x runErrorProne --stacktrace -PverifyUpToDate --profile
   $SCRIPT_DIR/impl/parse_profile_htmls.sh
 }
 
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplCameraReopenTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplCameraReopenTest.java
index 929a264..fdfd9dc 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplCameraReopenTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplCameraReopenTest.java
@@ -225,8 +225,8 @@
         final CameraManagerCompat cameraManagerCompat = CameraManagerCompat.from(cameraManagerImpl);
 
         // Build camera info
-        final Camera2CameraInfoImpl camera2CameraInfo = new Camera2CameraInfoImpl(mCameraId,
-                cameraManagerCompat.getCameraCharacteristicsCompat(mCameraId));
+        final Camera2CameraInfoImpl camera2CameraInfo =
+                new Camera2CameraInfoImpl(mCameraId, cameraManagerCompat);
 
         // Initialize camera instance
         mCamera2CameraImpl = new Camera2CameraImpl(cameraManagerCompat, mCameraId,
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplForceOpenCameraTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplForceOpenCameraTest.kt
index d30d2f2..debe2f1 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplForceOpenCameraTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplForceOpenCameraTest.kt
@@ -167,7 +167,7 @@
         // Build camera info from cameraId
         val camera2CameraInfo = Camera2CameraInfoImpl(
             cameraId,
-            cameraManagerCompat.getCameraCharacteristicsCompat(cameraId)
+            cameraManagerCompat
         )
 
         // Initialize camera instance
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt
index 49a77ea..edf597d 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplStateTest.kt
@@ -333,7 +333,7 @@
         // Build camera info
         val camera2CameraInfo = Camera2CameraInfoImpl(
             cameraId,
-            cameraManagerCompat.getCameraCharacteristicsCompat(cameraId)
+            cameraManagerCompat
         )
 
         // Initialize camera state registry and only allow 1 open camera at most inside CameraX
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
index 16f48a1..13b1c6a 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/Camera2CameraImplTest.java
@@ -155,7 +155,7 @@
         CameraManagerCompat cameraManagerCompat =
                 CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext());
         Camera2CameraInfoImpl camera2CameraInfo = new Camera2CameraInfoImpl(
-                mCameraId, cameraManagerCompat.getCameraCharacteristicsCompat(mCameraId));
+                mCameraId, cameraManagerCompat);
         mCamera2CameraImpl = new Camera2CameraImpl(cameraManagerCompat, mCameraId,
                 camera2CameraInfo,
                 mCameraStateRegistry, sCameraExecutor, sCameraHandler);
diff --git a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
index 46ab8cd..0453b3f 100644
--- a/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
+++ b/camera/camera-camera2/src/androidTest/java/androidx/camera/camera2/internal/ExposureDeviceTest.java
@@ -147,7 +147,7 @@
         CameraManagerCompat cameraManagerCompat =
                 CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext());
         Camera2CameraInfoImpl camera2CameraInfo = new Camera2CameraInfoImpl(
-                mCameraId, cameraManagerCompat.getCameraCharacteristicsCompat(mCameraId));
+                mCameraId, cameraManagerCompat);
         mCamera2CameraImpl = new Camera2CameraImpl(
                 CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext()),
                 mCameraId,
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
index 61c4069..51454e5 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraFactory.java
@@ -80,7 +80,7 @@
             Camera2CameraInfoImpl camera2CameraInfoImpl = mCameraInfos.get(cameraId);
             if (camera2CameraInfoImpl == null) {
                 camera2CameraInfoImpl = new Camera2CameraInfoImpl(
-                        cameraId, mCameraManager.getCameraCharacteristicsCompat(cameraId));
+                        cameraId, mCameraManager);
                 mCameraInfos.put(cameraId, camera2CameraInfoImpl);
             }
             return camera2CameraInfoImpl;
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
index c0f9b4a..178f4ee 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/Camera2CameraInfoImpl.java
@@ -25,7 +25,9 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.OptIn;
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.internal.compat.CameraManagerCompat;
 import androidx.camera.camera2.internal.compat.quirk.CameraQuirks;
 import androidx.camera.camera2.interop.Camera2CameraInfo;
 import androidx.camera.camera2.interop.ExperimentalCamera2Interop;
@@ -48,7 +50,10 @@
 
 import java.util.ArrayList;
 import java.util.Iterator;
+import java.util.LinkedHashMap;
 import java.util.List;
+import java.util.Map;
+import java.util.Objects;
 import java.util.concurrent.Executor;
 
 /**
@@ -90,19 +95,23 @@
     private final Quirks mCameraQuirks;
     @NonNull
     private final CamcorderProfileProvider mCamera2CamcorderProfileProvider;
+    @NonNull
+    private final CameraManagerCompat mCameraManager;
 
     /**
      * Constructs an instance. Before {@link #linkWithCameraControl(Camera2CameraControlImpl)} is
      * called, camera control related API (torch/exposure/zoom) will return default values.
      */
     Camera2CameraInfoImpl(@NonNull String cameraId,
-            @NonNull CameraCharacteristicsCompat cameraCharacteristicsCompat) {
+            @NonNull CameraManagerCompat cameraManager) throws CameraAccessExceptionCompat {
         mCameraId = Preconditions.checkNotNull(cameraId);
-        mCameraCharacteristicsCompat = cameraCharacteristicsCompat;
+        mCameraManager = cameraManager;
+
+        mCameraCharacteristicsCompat = cameraManager.getCameraCharacteristicsCompat(mCameraId);
         mCamera2CameraInfo = new Camera2CameraInfo(this);
-        mCameraQuirks = CameraQuirks.get(cameraId, cameraCharacteristicsCompat);
+        mCameraQuirks = CameraQuirks.get(cameraId, mCameraCharacteristicsCompat);
         mCamera2CamcorderProfileProvider = new Camera2CamcorderProfileProvider(cameraId,
-                cameraCharacteristicsCompat);
+                mCameraCharacteristicsCompat);
         mCameraStateLiveData = new RedirectableLiveData<>(
                 CameraState.create(CameraState.Type.CLOSED));
     }
@@ -399,6 +408,36 @@
     }
 
     /**
+     * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
+     *
+     * <p>For every camera, the map contains at least the CameraCharacteristics for the camera id.
+     * If the camera is logical camera, it will also contain associated physical camera ids and
+     * their CameraCharacteristics.
+     *
+     */
+    @NonNull
+    public Map<String, CameraCharacteristics> getCameraCharacteristicsMap() {
+        LinkedHashMap<String, CameraCharacteristics> map = new LinkedHashMap<>();
+
+        map.put(mCameraId, mCameraCharacteristicsCompat.toCameraCharacteristics());
+
+        for (String physicalCameraId : mCameraCharacteristicsCompat.getPhysicalCameraIds()) {
+            if (Objects.equals(physicalCameraId, mCameraId)) {
+                continue;
+            }
+            try {
+                map.put(physicalCameraId,
+                        mCameraManager.getCameraCharacteristicsCompat(physicalCameraId)
+                                .toCameraCharacteristics());
+            } catch (CameraAccessExceptionCompat e) {
+                Logger.e(TAG,
+                        "Failed to get CameraCharacteristics for cameraId " + physicalCameraId, e);
+            }
+        }
+        return map;
+    }
+
+    /**
      * A {@link LiveData} which can be redirected to another {@link LiveData}. If no redirection
      * is set, initial value will be used.
      */
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
new file mode 100644
index 0000000..6e9ed1d
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsApi28Impl.java
@@ -0,0 +1,38 @@
+/*
+ * 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.camera.camera2.internal.compat;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+
+import java.util.Set;
+
+@RequiresApi(28)
+class CameraCharacteristicsApi28Impl extends CameraCharacteristicsBaseImpl{
+
+    CameraCharacteristicsApi28Impl(@NonNull CameraCharacteristics cameraCharacteristics) {
+        super(cameraCharacteristics);
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getPhysicalCameraIds() {
+        return mCameraCharacteristics.getPhysicalCameraIds();
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsBaseImpl.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsBaseImpl.java
new file mode 100644
index 0000000..8fd289e
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsBaseImpl.java
@@ -0,0 +1,54 @@
+/*
+ * 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.camera.camera2.internal.compat;
+
+import android.hardware.camera2.CameraCharacteristics;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+
+import java.util.Collections;
+import java.util.Set;
+
+@RequiresApi(21)
+class CameraCharacteristicsBaseImpl
+        implements CameraCharacteristicsCompat.CameraCharacteristicsCompatImpl {
+    @NonNull
+    protected final CameraCharacteristics mCameraCharacteristics;
+    CameraCharacteristicsBaseImpl(@NonNull CameraCharacteristics cameraCharacteristics) {
+        mCameraCharacteristics = cameraCharacteristics;
+    }
+
+    @Nullable
+    @Override
+    public <T> T get(@NonNull CameraCharacteristics.Key<T> key) {
+        return mCameraCharacteristics.get(key);
+    }
+
+    @NonNull
+    @Override
+    public Set<String> getPhysicalCameraIds() {
+        return Collections.emptySet();
+    }
+
+    @NonNull
+    @Override
+    public CameraCharacteristics unwrap() {
+        return mCameraCharacteristics;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
index efc77115..107c0d6 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompat.java
@@ -17,6 +17,7 @@
 package androidx.camera.camera2.internal.compat;
 
 import android.hardware.camera2.CameraCharacteristics;
+import android.os.Build;
 
 import androidx.annotation.GuardedBy;
 import androidx.annotation.NonNull;
@@ -25,6 +26,7 @@
 
 import java.util.HashMap;
 import java.util.Map;
+import java.util.Set;
 
 /**
  * A wrapper for {@link CameraCharacteristics} which caches the retrieved values to optimize
@@ -35,10 +37,14 @@
     @GuardedBy("this")
     private final Map<CameraCharacteristics.Key<?>, Object> mValuesCache = new HashMap<>();
     @NonNull
-    private final CameraCharacteristics mCameraCharacteristics;
+    private final CameraCharacteristicsCompatImpl mCameraCharacteristicsImpl;
 
     private CameraCharacteristicsCompat(@NonNull CameraCharacteristics cameraCharacteristics) {
-        mCameraCharacteristics = cameraCharacteristics;
+        if (Build.VERSION.SDK_INT >= 28) {
+            mCameraCharacteristicsImpl = new CameraCharacteristicsApi28Impl(cameraCharacteristics);
+        } else {
+            mCameraCharacteristicsImpl = new CameraCharacteristicsBaseImpl(cameraCharacteristics);
+        }
     }
 
     /**
@@ -70,7 +76,7 @@
                 return value;
             }
 
-            value = mCameraCharacteristics.get(key);
+            value = mCameraCharacteristicsImpl.get(key);
             if (value != null) {
                 mValuesCache.put(key, value);
             }
@@ -79,10 +85,42 @@
     }
 
     /**
+     * Returns the physical camera Ids if it is a logical camera. Otherwise it would
+     * return an empty set.
+     */
+    @NonNull
+    public Set<String> getPhysicalCameraIds() {
+        return mCameraCharacteristicsImpl.getPhysicalCameraIds();
+    }
+
+    /**
      * Returns the {@link CameraCharacteristics} represented by this object.
      */
     @NonNull
     public CameraCharacteristics toCameraCharacteristics() {
-        return mCameraCharacteristics;
+        return mCameraCharacteristicsImpl.unwrap();
     }
-}
+
+    /**
+     * CameraCharacteristic Implementation Interface
+     */
+    public interface CameraCharacteristicsCompatImpl {
+        /**
+         * Gets the key/values from the CameraCharacteristics .
+         */
+        @Nullable
+        <T> T get(@NonNull CameraCharacteristics.Key<T> key);
+
+        /**
+         * Get physical camera ids.
+         */
+        @NonNull
+        Set<String> getPhysicalCameraIds();
+
+        /**
+         * Returns the underlying {@link CameraCharacteristics} instance.
+         */
+        @NonNull
+        CameraCharacteristics unwrap();
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraNoResponseWhenEnablingFlashQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraNoResponseWhenEnablingFlashQuirk.java
new file mode 100644
index 0000000..0a5a550
--- /dev/null
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraNoResponseWhenEnablingFlashQuirk.java
@@ -0,0 +1,42 @@
+/*
+ * 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.camera.camera2.internal.compat.quirk;
+
+import static androidx.camera.core.CameraSelector.LENS_FACING_BACK;
+
+import android.hardware.camera2.CameraCharacteristics;
+import android.os.Build;
+
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.internal.compat.quirk.UseTorchAsFlashQuirk;
+
+import java.util.Locale;
+
+/**
+ * Camera gets stuck when taking pictures with flash ON or AUTO in dark environment.
+ *
+ * <p>See b/193336562 for details.
+ */
+class CameraNoResponseWhenEnablingFlashQuirk implements UseTorchAsFlashQuirk {
+    static boolean load(@NonNull CameraCharacteristicsCompat characteristics) {
+        return "SAMSUNG".equals(Build.BRAND.toUpperCase(Locale.US))
+                // Enables on all Samsung Galaxy Note 5 devices.
+                && Build.MODEL.toUpperCase(Locale.US).startsWith("SM-N920")
+                && characteristics.get(CameraCharacteristics.LENS_FACING) == LENS_FACING_BACK;
+    }
+}
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
index c76c712..5e4cf17 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/CameraQuirks.java
@@ -59,6 +59,13 @@
         if (CamcorderProfileResolutionQuirk.load(cameraCharacteristicsCompat)) {
             quirks.add(new CamcorderProfileResolutionQuirk(cameraCharacteristicsCompat));
         }
+        if (ImageCaptureWashedOutImageQuirk.load(cameraCharacteristicsCompat)) {
+            quirks.add(new ImageCaptureWashedOutImageQuirk());
+        }
+        if (CameraNoResponseWhenEnablingFlashQuirk.load(cameraCharacteristicsCompat)) {
+            quirks.add(new CameraNoResponseWhenEnablingFlashQuirk());
+        }
+
         return new Quirks(quirks);
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java
similarity index 70%
rename from camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java
rename to camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java
index 62fa918..0ca7ff3 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/internal/compat/quirk/ImageCaptureWashedOutImageQuirk.java
@@ -14,11 +14,16 @@
  * limitations under the License.
  */
 
-package androidx.camera.core.internal.compat.quirk;
+package androidx.camera.camera2.internal.compat.quirk;
 
+import static androidx.camera.core.CameraSelector.LENS_FACING_BACK;
+
+import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
 
-import androidx.camera.core.impl.Quirk;
+import androidx.annotation.NonNull;
+import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.core.internal.compat.quirk.UseTorchAsFlashQuirk;
 
 import java.util.Arrays;
 import java.util.List;
@@ -29,7 +34,7 @@
  *
  * <p>See b/176399765 and b/181966663.
  */
-public class ImageCaptureWashedOutImageQuirk implements Quirk {
+public class ImageCaptureWashedOutImageQuirk implements UseTorchAsFlashQuirk {
 
     // List of devices with the issue. See b/181966663.
     private static final List<String> DEVICE_MODELS = Arrays.asList(
@@ -54,8 +59,9 @@
             "SM-G935P"
     );
 
-    static boolean load() {
+    static boolean load(@NonNull CameraCharacteristicsCompat cameraCharacteristics) {
         return "SAMSUNG".equals(Build.BRAND.toUpperCase(Locale.US))
-                && DEVICE_MODELS.contains(Build.MODEL.toUpperCase(Locale.US));
+                && DEVICE_MODELS.contains(Build.MODEL.toUpperCase(Locale.US))
+                && cameraCharacteristics.get(CameraCharacteristics.LENS_FACING) == LENS_FACING_BACK;
     }
 }
diff --git a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
index eaff42a..80fdc10 100644
--- a/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
+++ b/camera/camera-camera2/src/main/java/androidx/camera/camera2/interop/Camera2CameraInfo.java
@@ -26,12 +26,14 @@
 import androidx.camera.core.CameraInfo;
 import androidx.core.util.Preconditions;
 
+import java.util.Map;
+
 /**
  * An interface for retrieving Camera2-related camera information.
  */
 @ExperimentalCamera2Interop
 public final class Camera2CameraInfo {
-
+    private static final String TAG = "Camera2CameraInfo";
     private final Camera2CameraInfoImpl mCamera2CameraInfoImpl;
 
     /**
@@ -50,8 +52,8 @@
      * @param cameraInfo The {@link CameraInfo} to get from.
      * @return The camera information with Camera2 implementation.
      * @throws IllegalArgumentException if the camera info does not contain the camera2 information
-     *                               (e.g., if CameraX was not initialized with a
-     *                               {@link androidx.camera.camera2.Camera2Config}).
+     *                                  (e.g., if CameraX was not initialized with a
+     *                                  {@link androidx.camera.camera2.Camera2Config}).
      */
     @NonNull
     public static Camera2CameraInfo from(@NonNull CameraInfo cameraInfo) {
@@ -73,7 +75,6 @@
      * one time. When the CameraDevice changes then the camera id will change.
      *
      * @return the camera ID.
-
      * @throws IllegalStateException if the camera info does not contain the camera 2 camera ID
      *                               (e.g., if CameraX was not initialized with a
      *                               {@link androidx.camera.camera2.Camera2Config}).
@@ -90,8 +91,8 @@
      * that would be obtained from
      * {@link android.hardware.camera2.CameraManager#getCameraCharacteristics(String)}.
      *
-     * @param <T>        The type of the characteristic value.
-     * @param key        The {@link CameraCharacteristics.Key} of the characteristic.
+     * @param <T> The type of the characteristic value.
+     * @param key The {@link CameraCharacteristics.Key} of the characteristic.
      * @return the value of the characteristic.
      */
     @Nullable
@@ -111,7 +112,6 @@
      * @throws IllegalStateException if the camera info does not contain the camera 2
      *                               characteristics(e.g., if CameraX was not initialized with a
      *                               {@link androidx.camera.camera2.Camera2Config}).
-     *
      * @hide
      */
     // TODO: Hidden until new extensions API released.
@@ -124,4 +124,19 @@
         Camera2CameraInfoImpl impl = (Camera2CameraInfoImpl) cameraInfo;
         return impl.getCameraCharacteristicsCompat().toCameraCharacteristics();
     }
+
+    /**
+     * Returns a map consisting of the camera ids and the {@link CameraCharacteristics}s.
+     *
+     * <p>For every camera, the map contains at least the CameraCharacteristics for the camera id.
+     * If the camera is logical camera, it will also contain associated physical camera ids and
+     * their CameraCharacteristics.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    @NonNull
+    public Map<String, CameraCharacteristics> getCameraCharacteristicsMap() {
+        return mCamera2CameraInfoImpl.getCameraCharacteristicsMap();
+    }
 }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
index b054f4f..106223f 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/Camera2CameraInfoImplTest.java
@@ -25,13 +25,18 @@
 import static org.mockito.Mockito.when;
 
 import android.content.Context;
-import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
+import android.hardware.camera2.CameraDevice;
 import android.hardware.camera2.CameraManager;
 import android.os.Build;
+import android.util.Pair;
 import android.view.Surface;
 
+import androidx.annotation.NonNull;
+import androidx.annotation.RequiresApi;
+import androidx.camera.camera2.internal.compat.CameraAccessExceptionCompat;
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
+import androidx.camera.camera2.internal.compat.CameraManagerCompat;
 import androidx.camera.core.CameraInfo;
 import androidx.camera.core.CameraSelector;
 import androidx.camera.core.ExposureState;
@@ -56,13 +61,17 @@
 import org.robolectric.shadows.ShadowCameraCharacteristics;
 import org.robolectric.shadows.ShadowCameraManager;
 
+import java.util.Arrays;
+import java.util.HashMap;
+import java.util.HashSet;
+import java.util.List;
+import java.util.Map;
 import java.util.concurrent.Executor;
 
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
 public class Camera2CameraInfoImplTest {
-
     private static final String CAMERA0_ID = "0";
     private static final int CAMERA0_SUPPORTED_HARDWARE_LEVEL =
             CameraCharacteristics.INFO_SUPPORTED_HARDWARE_LEVEL_LEGACY;
@@ -81,6 +90,7 @@
 
     private CameraCharacteristicsCompat mCameraCharacteristics0;
     private CameraCharacteristicsCompat mCameraCharacteristics1;
+    private CameraManagerCompat mCameraManagerCompat;
     private ZoomControl mMockZoomControl;
     private TorchControl mMockTorchControl;
     private ExposureControl mExposureControl;
@@ -88,19 +98,13 @@
     private Camera2CameraControlImpl mMockCameraControl;
 
     @Before
-    public void setUp() throws CameraAccessException {
+    public void setUp() throws CameraAccessExceptionCompat {
         initCameras();
 
-        CameraManager cameraManager =
-                (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
-                        Context.CAMERA_SERVICE);
-
-        mCameraCharacteristics0 =
-                CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
-                        cameraManager.getCameraCharacteristics(CAMERA0_ID));
-        mCameraCharacteristics1 =
-                CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
-                        cameraManager.getCameraCharacteristics(CAMERA1_ID));
+        mCameraManagerCompat =
+                CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext());
+        mCameraCharacteristics0 = mCameraManagerCompat.getCameraCharacteristicsCompat(CAMERA0_ID);
+        mCameraCharacteristics1 = mCameraManagerCompat.getCameraCharacteristicsCompat(CAMERA1_ID);
 
         mMockZoomControl = mock(ZoomControl.class);
         mMockTorchControl = mock(TorchControl.class);
@@ -115,25 +119,26 @@
     }
 
     @Test
-    public void canCreateCameraInfo() {
+    public void canCreateCameraInfo() throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
 
         assertThat(cameraInfoInternal).isNotNull();
     }
 
     @Test
-    public void cameraInfo_canReturnSensorOrientation() {
+    public void cameraInfo_canReturnSensorOrientation() throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(cameraInfoInternal.getSensorRotationDegrees()).isEqualTo(
                 CAMERA0_SENSOR_ORIENTATION);
     }
 
     @Test
-    public void cameraInfo_canCalculateCorrectRelativeRotation_forBackCamera() {
+    public void cameraInfo_canCalculateCorrectRelativeRotation_forBackCamera()
+            throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
 
         // Note: these numbers depend on the camera being a back-facing camera.
         assertThat(cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
@@ -147,9 +152,10 @@
     }
 
     @Test
-    public void cameraInfo_canCalculateCorrectRelativeRotation_forFrontCamera() {
+    public void cameraInfo_canCalculateCorrectRelativeRotation_forFrontCamera()
+            throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA1_ID, mCameraCharacteristics1);
+                new Camera2CameraInfoImpl(CAMERA1_ID, mCameraManagerCompat);
 
         // Note: these numbers depend on the camera being a front-facing camera.
         assertThat(cameraInfoInternal.getSensorRotationDegrees(Surface.ROTATION_0))
@@ -163,47 +169,52 @@
     }
 
     @Test
-    public void cameraInfo_canReturnLensFacing() {
+    public void cameraInfo_canReturnLensFacing() throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(cameraInfoInternal.getLensFacing()).isEqualTo(CAMERA0_LENS_FACING_ENUM);
     }
 
     @Test
-    public void cameraInfo_canReturnHasFlashUnit_forBackCamera() {
+    public void cameraInfo_canReturnHasFlashUnit_forBackCamera()
+            throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(cameraInfoInternal.hasFlashUnit()).isEqualTo(CAMERA0_FLASH_INFO_BOOLEAN);
     }
 
     @Test
-    public void cameraInfo_canReturnHasFlashUnit_forFrontCamera() {
+    public void cameraInfo_canReturnHasFlashUnit_forFrontCamera()
+            throws CameraAccessExceptionCompat {
         CameraInfoInternal cameraInfoInternal =
-                new Camera2CameraInfoImpl(CAMERA1_ID, mCameraCharacteristics1);
+                new Camera2CameraInfoImpl(CAMERA1_ID, mCameraManagerCompat);
         assertThat(cameraInfoInternal.hasFlashUnit()).isEqualTo(CAMERA1_FLASH_INFO_BOOLEAN);
     }
 
     @Test
-    public void cameraInfoWithoutCameraControl_canReturnDefaultTorchState() {
+    public void cameraInfoWithoutCameraControl_canReturnDefaultTorchState()
+            throws CameraAccessExceptionCompat {
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(camera2CameraInfoImpl.getTorchState().getValue())
                 .isEqualTo(TorchControl.DEFAULT_TORCH_STATE);
     }
 
     @Test
-    public void cameraInfoWithCameraControl_canReturnTorchState() {
+    public void cameraInfoWithCameraControl_canReturnTorchState()
+            throws CameraAccessExceptionCompat {
         when(mMockTorchControl.getTorchState()).thenReturn(new MutableLiveData<>(TorchState.ON));
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         camera2CameraInfoImpl.linkWithCameraControl(mMockCameraControl);
         assertThat(camera2CameraInfoImpl.getTorchState().getValue()).isEqualTo(TorchState.ON);
     }
 
     @Test
-    public void torchStateLiveData_SameInstanceBeforeAndAfterCameraControlLink() {
+    public void torchStateLiveData_SameInstanceBeforeAndAfterCameraControlLink()
+            throws CameraAccessExceptionCompat {
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
 
         // Calls getTorchState() to trigger RedirectableLiveData
         LiveData<Integer> torchStateLiveData = camera2CameraInfoImpl.getTorchState();
@@ -219,29 +230,32 @@
     // zoom related tests just ensure it uses ZoomControl to get the value
     // Full tests are performed at ZoomControlDeviceTest / ZoomControlTest.
     @Test
-    public void cameraInfoWithCameraControl_getZoom_valueIsCorrect() {
+    public void cameraInfoWithCameraControl_getZoom_valueIsCorrect()
+            throws CameraAccessExceptionCompat {
         ZoomState zoomState = ImmutableZoomState.create(3.0f, 8.0f, 1.0f, 0.2f);
         when(mMockZoomControl.getZoomState()).thenReturn(new MutableLiveData<>(zoomState));
 
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         camera2CameraInfoImpl.linkWithCameraControl(mMockCameraControl);
 
         assertThat(camera2CameraInfoImpl.getZoomState().getValue()).isEqualTo(zoomState);
     }
 
     @Test
-    public void cameraInfoWithoutCameraControl_getDetaultZoomState() {
+    public void cameraInfoWithoutCameraControl_getDetaultZoomState()
+            throws CameraAccessExceptionCompat {
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(camera2CameraInfoImpl.getZoomState().getValue())
                 .isEqualTo(ZoomControl.getDefaultZoomState(mCameraCharacteristics0));
     }
 
     @Test
-    public void zoomStateLiveData_SameInstanceBeforeAndAfterCameraControlLink() {
+    public void zoomStateLiveData_SameInstanceBeforeAndAfterCameraControlLink()
+            throws CameraAccessExceptionCompat {
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
 
         // Calls getZoomState() to trigger RedirectableLiveData
         LiveData<ZoomState> zoomStateLiveData = camera2CameraInfoImpl.getZoomState();
@@ -256,21 +270,23 @@
     }
 
     @Test
-    public void cameraInfoWithCameraControl_canReturnExposureState() {
+    public void cameraInfoWithCameraControl_canReturnExposureState()
+            throws CameraAccessExceptionCompat {
         ExposureState exposureState = new ExposureStateImpl(mCameraCharacteristics0, 2);
         when(mExposureControl.getExposureState()).thenReturn(exposureState);
 
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         camera2CameraInfoImpl.linkWithCameraControl(mMockCameraControl);
 
         assertThat(camera2CameraInfoImpl.getExposureState()).isEqualTo(exposureState);
     }
 
     @Test
-    public void cameraInfoWithoutCameraControl_canReturnDefaultExposureState() {
+    public void cameraInfoWithoutCameraControl_canReturnDefaultExposureState()
+            throws CameraAccessExceptionCompat {
         Camera2CameraInfoImpl camera2CameraInfoImpl =
-                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraCharacteristics0);
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
 
         ExposureState defaultState =
                 ExposureControl.getDefaultExposureState(mCameraCharacteristics0);
@@ -286,25 +302,26 @@
     }
 
     @Test
-    public void cameraInfo_getImplementationType_legacy() {
-        final CameraInfoInternal cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID,
-                mCameraCharacteristics0);
+    public void cameraInfo_getImplementationType_legacy() throws CameraAccessExceptionCompat {
+        final CameraInfoInternal cameraInfo =
+                new Camera2CameraInfoImpl(CAMERA0_ID, mCameraManagerCompat);
         assertThat(cameraInfo.getImplementationType()).isEqualTo(
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2_LEGACY);
     }
 
     @Test
-    public void cameraInfo_getImplementationType_noneLegacy() {
-        final CameraInfoInternal cameraInfo = new Camera2CameraInfoImpl(CAMERA1_ID,
-                mCameraCharacteristics1);
+    public void cameraInfo_getImplementationType_noneLegacy() throws CameraAccessExceptionCompat {
+        final CameraInfoInternal cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA1_ID, mCameraManagerCompat);
         assertThat(cameraInfo.getImplementationType()).isEqualTo(
                 CameraInfo.IMPLEMENTATION_TYPE_CAMERA2);
     }
 
     @Test
-    public void addSessionCameraCaptureCallback_isCalledToCameraControl() {
-        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA1_ID,
-                mCameraCharacteristics1);
+    public void addSessionCameraCaptureCallback_isCalledToCameraControl()
+            throws CameraAccessExceptionCompat {
+        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA1_ID, mCameraManagerCompat);
         cameraInfo.linkWithCameraControl(mMockCameraControl);
 
         Executor executor = mock(Executor.class);
@@ -315,9 +332,10 @@
     }
 
     @Test
-    public void removeSessionCameraCaptureCallback_isCalledToCameraControl() {
-        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA1_ID,
-                mCameraCharacteristics1);
+    public void removeSessionCameraCaptureCallback_isCalledToCameraControl()
+            throws CameraAccessExceptionCompat {
+        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA1_ID, mCameraManagerCompat);
         cameraInfo.linkWithCameraControl(mMockCameraControl);
 
         CameraCaptureCallback callback = mock(CameraCaptureCallback.class);
@@ -327,9 +345,10 @@
     }
 
     @Test
-    public void addSessionCameraCaptureCallbackWithoutCameraControl_attachedToCameraControlLater() {
-        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA1_ID,
-                mCameraCharacteristics1);
+    public void addSessionCameraCaptureCallbackWithoutCameraControl_attachedToCameraControlLater()
+            throws CameraAccessExceptionCompat {
+        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA1_ID, mCameraManagerCompat);
         Executor executor = mock(Executor.class);
         CameraCaptureCallback callback = mock(CameraCaptureCallback.class);
         cameraInfo.addSessionCaptureCallback(executor, callback);
@@ -340,9 +359,10 @@
     }
 
     @Test
-    public void removeSessionCameraCaptureCallbackWithoutCameraControl_callbackIsRemoved() {
-        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA1_ID,
-                mCameraCharacteristics1);
+    public void removeSessionCameraCaptureCallbackWithoutCameraControl_callbackIsRemoved()
+            throws CameraAccessExceptionCompat {
+        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA1_ID, mCameraManagerCompat);
         // Add two callbacks
         Executor executor1 = mock(Executor.class);
         CameraCaptureCallback callback1 = mock(CameraCaptureCallback.class);
@@ -361,9 +381,10 @@
     }
 
     @Test
-    public void cameraInfoWithCameraControl_canReturnIsFocusMeteringSupported() {
-        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(CAMERA0_ID,
-                mCameraCharacteristics0);
+    public void cameraInfoWithCameraControl_canReturnIsFocusMeteringSupported()
+            throws CameraAccessExceptionCompat {
+        final Camera2CameraInfoImpl cameraInfo = new Camera2CameraInfoImpl(
+                CAMERA0_ID, mCameraManagerCompat);
 
         cameraInfo.linkWithCameraControl(mMockCameraControl);
 
@@ -377,6 +398,54 @@
         assertThat(cameraInfo.isFocusMeteringSupported(action)).isTrue();
     }
 
+    @Config(minSdk = 28)
+    @RequiresApi(28)
+    @Test
+    public void canReturnCameraCharacteristicsMapWithPhysicalCameras()
+            throws CameraAccessExceptionCompat {
+        CameraCharacteristics characteristics0 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristicsPhysical2 = mock(CameraCharacteristics.class);
+        CameraCharacteristics characteristicsPhysical3 = mock(CameraCharacteristics.class);
+        when(characteristics0.getPhysicalCameraIds())
+                .thenReturn(new HashSet<>(Arrays.asList("0", "2", "3")));
+        CameraManagerCompat cameraManagerCompat = initCameraManagerWithPhysicalIds(
+                Arrays.asList(
+                        new Pair<>("0", characteristics0),
+                        new Pair<>("2", characteristicsPhysical2),
+                        new Pair<>("3", characteristicsPhysical3)));
+        Camera2CameraInfoImpl impl = new Camera2CameraInfoImpl("0", cameraManagerCompat);
+
+        Map<String, CameraCharacteristics> map = impl.getCameraCharacteristicsMap();
+        assertThat(map.size()).isEqualTo(3);
+        assertThat(map.get("0")).isSameInstanceAs(characteristics0);
+        assertThat(map.get("2")).isSameInstanceAs(characteristicsPhysical2);
+        assertThat(map.get("3")).isSameInstanceAs(characteristicsPhysical3);
+    }
+
+    @Config(maxSdk = 27)
+    @Test
+    public void canReturnCameraCharacteristicsMapWithMainCamera()
+            throws CameraAccessExceptionCompat {
+        Camera2CameraInfoImpl impl = new Camera2CameraInfoImpl("0", mCameraManagerCompat);
+        Map<String, CameraCharacteristics> map = impl.getCameraCharacteristicsMap();
+        assertThat(map.size()).isEqualTo(1);
+        assertThat(map.get("0"))
+                .isSameInstanceAs(mCameraCharacteristics0.toCameraCharacteristics());
+    }
+
+    private CameraManagerCompat initCameraManagerWithPhysicalIds(
+            List<Pair<String, CameraCharacteristics>> cameraIdsAndCharacteristicsList) {
+        FakeCameraManagerImpl cameraManagerImpl = new FakeCameraManagerImpl();
+        for (Pair<String, CameraCharacteristics> pair : cameraIdsAndCharacteristicsList) {
+            String cameraId = pair.first;
+            CameraCharacteristics cameraCharacteristics = pair.second;
+            cameraManagerImpl.addCamera(cameraId, cameraCharacteristics);
+        }
+        return CameraManagerCompat.from(cameraManagerImpl);
+    }
+
+
+
     private void initCameras() {
         // **** Camera 0 characteristics ****//
         CameraCharacteristics characteristics0 =
@@ -432,4 +501,49 @@
                                 .getSystemService(Context.CAMERA_SERVICE)))
                 .addCamera(CAMERA1_ID, characteristics1);
     }
+
+    private static class FakeCameraManagerImpl
+            implements CameraManagerCompat.CameraManagerCompatImpl {
+        private final HashMap<String, CameraCharacteristics> mCameraIdCharacteristics =
+                new HashMap<>();
+
+        public void addCamera(@NonNull String cameraId,
+                @NonNull CameraCharacteristics cameraCharacteristics) {
+            mCameraIdCharacteristics.put(cameraId, cameraCharacteristics);
+        }
+        @NonNull
+        @Override
+        public String[] getCameraIdList() throws CameraAccessExceptionCompat {
+            return mCameraIdCharacteristics.keySet().toArray(new String[0]);
+        }
+
+        @Override
+        public void registerAvailabilityCallback(@NonNull Executor executor,
+                @NonNull CameraManager.AvailabilityCallback callback) {
+        }
+
+        @Override
+        public void unregisterAvailabilityCallback(
+                @NonNull CameraManager.AvailabilityCallback callback) {
+        }
+
+        @NonNull
+        @Override
+        public CameraCharacteristics getCameraCharacteristics(@NonNull String cameraId)
+                throws CameraAccessExceptionCompat {
+            return mCameraIdCharacteristics.get(cameraId);
+        }
+
+        @Override
+        public void openCamera(@NonNull String cameraId, @NonNull Executor executor,
+                @NonNull CameraDevice.StateCallback callback) throws CameraAccessExceptionCompat {
+
+        }
+
+        @NonNull
+        @Override
+        public CameraManager getCameraManager() {
+            return null;
+        }
+    }
 }
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 61a23c0..b039939f7 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
@@ -569,6 +569,8 @@
         ((ShadowCameraManager) Shadow.extract(cameraManager))
                 .addCamera(cameraId, characteristics);
 
+        CameraManagerCompat cameraManagerCompat =
+                CameraManagerCompat.from((Context) ApplicationProvider.getApplicationContext());
         StreamConfigurationMap mockMap = mock(StreamConfigurationMap.class);
         when(mockMap.getOutputSizes(anyInt())).thenReturn(mSupportedSizes);
         // ImageFormat.PRIVATE was supported since API level 23. Before that, the supported
@@ -579,7 +581,7 @@
         @CameraSelector.LensFacing int lensFacingEnum = CameraUtil.getLensFacingEnumFromInt(
                 lensFacing);
         mCameraFactory.insertCamera(lensFacingEnum, cameraId, () -> new FakeCamera(cameraId, null,
-                new Camera2CameraInfoImpl(cameraId, getCameraCharacteristicsCompat(cameraId))));
+                new Camera2CameraInfoImpl(cameraId, cameraManagerCompat)));
     }
 
     private void initCameraX() {
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSizeConstraintsTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSizeConstraintsTest.java
index c830dc8..5c8beda 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSizeConstraintsTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/SupportedSizeConstraintsTest.java
@@ -25,7 +25,6 @@
 import android.content.Context;
 import android.graphics.ImageFormat;
 import android.graphics.SurfaceTexture;
-import android.hardware.camera2.CameraAccessException;
 import android.hardware.camera2.CameraCharacteristics;
 import android.hardware.camera2.CameraManager;
 import android.hardware.camera2.CameraMetadata;
@@ -36,7 +35,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.camera.camera2.Camera2Config;
-import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
 import androidx.camera.camera2.internal.compat.CameraManagerCompat;
 import androidx.camera.camera2.internal.compat.workaround.ExcludedSupportedSizesContainer;
 import androidx.camera.core.CameraUnavailableException;
@@ -106,7 +104,7 @@
             };
 
     private final Context mContext = ApplicationProvider.getApplicationContext();
-    private final CameraManagerCompat mCameraManager = CameraManagerCompat.from(mContext);
+    private final CameraManagerCompat mCameraManagerCompat = CameraManagerCompat.from(mContext);
 
     @Before
     public void setUp() throws IllegalAccessException {
@@ -144,7 +142,7 @@
         setupCamera();
 
         final SupportedSurfaceCombination supportedSurfaceCombination =
-                new SupportedSurfaceCombination(mContext, BACK_CAMERA_ID, mCameraManager,
+                new SupportedSurfaceCombination(mContext, BACK_CAMERA_ID, mCameraManagerCompat,
                         mMockCamcorderProfileHelper);
 
         List<Size> excludedSizes = Arrays.asList(
@@ -183,8 +181,8 @@
         shadowCharacteristics.set(
                 CameraCharacteristics.SENSOR_ORIENTATION, DEFAULT_SENSOR_ORIENTATION);
 
-        CameraManager cameraManager = (CameraManager) ApplicationProvider.getApplicationContext()
-                .getSystemService(Context.CAMERA_SERVICE);
+        CameraManager cameraManager =
+                (CameraManager) mContext.getSystemService(Context.CAMERA_SERVICE);
         ((ShadowCameraManager) Shadow.extract(cameraManager)).addCamera(BACK_CAMERA_ID,
                 characteristics);
 
@@ -202,8 +200,7 @@
         final FakeCameraFactory cameraFactory = new FakeCameraFactory();
         cameraFactory.insertCamera(lensFacingEnum, BACK_CAMERA_ID,
                 () -> new FakeCamera(BACK_CAMERA_ID, null,
-                        new Camera2CameraInfoImpl(BACK_CAMERA_ID,
-                                getCameraCharacteristicsCompat(BACK_CAMERA_ID))));
+                        new Camera2CameraInfoImpl(BACK_CAMERA_ID, mCameraManagerCompat)));
 
         initCameraX(cameraFactory);
     }
@@ -227,14 +224,4 @@
                 .build();
         CameraX.initialize(mContext, cameraXConfig);
     }
-
-    private CameraCharacteristicsCompat getCameraCharacteristicsCompat(String cameraId)
-            throws CameraAccessException {
-        CameraManager cameraManager =
-                (CameraManager) ApplicationProvider.getApplicationContext().getSystemService(
-                        Context.CAMERA_SERVICE);
-
-        return CameraCharacteristicsCompat.toCameraCharacteristicsCompat(
-                cameraManager.getCameraCharacteristics(cameraId));
-    }
 }
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 bda1bc4..e4d4710 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
@@ -2222,8 +2222,7 @@
         mCameraManagerCompat = CameraManagerCompat.from(mContext);
 
         mCameraFactory.insertCamera(lensFacingEnum, cameraId, () -> new FakeCamera(cameraId, null,
-                new Camera2CameraInfoImpl(cameraId,
-                        mCameraManagerCompat.getCameraCharacteristicsCompat(cameraId))));
+                new Camera2CameraInfoImpl(cameraId, mCameraManagerCompat)));
 
         initCameraX();
     }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompatTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompatTest.java
index 80bad45..1fbe51f 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompatTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/internal/compat/CameraCharacteristicsCompatTest.java
@@ -18,9 +18,14 @@
 
 import static com.google.common.truth.Truth.assertThat;
 
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.verify;
+
 import android.hardware.camera2.CameraCharacteristics;
 import android.os.Build;
 
+import androidx.annotation.RequiresApi;
+
 import org.junit.Before;
 import org.junit.Test;
 import org.junit.runner.RunWith;
@@ -85,4 +90,24 @@
         assertThat(characteristicsCompat.get(CameraCharacteristics.SENSOR_ORIENTATION))
                 .isNull();
     }
+
+    @Config(minSdk = 28)
+    @RequiresApi(28)
+    @Test
+    public void getPhysicalCameraIds_invokeCameraCharacteristics_api28() {
+        CameraCharacteristics cameraCharacteristics = mock(CameraCharacteristics.class);
+        CameraCharacteristicsCompat characteristicsCompat =
+                CameraCharacteristicsCompat.toCameraCharacteristicsCompat(cameraCharacteristics);
+
+        characteristicsCompat.getPhysicalCameraIds();
+        verify(cameraCharacteristics).getPhysicalCameraIds();
+    }
+
+    @Config(maxSdk = 27)
+    @Test
+    public void getPhysicalCameraIds_returnEmptyList_below28() {
+        CameraCharacteristicsCompat characteristicsCompat =
+                CameraCharacteristicsCompat.toCameraCharacteristicsCompat(mCharacteristics);
+        assertThat(characteristicsCompat.getPhysicalCameraIds()).isEmpty();
+    }
 }
diff --git a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
index d6883c0..992be21 100644
--- a/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
+++ b/camera/camera-camera2/src/test/java/androidx/camera/camera2/interop/Camera2CameraInfoTest.java
@@ -25,6 +25,7 @@
 import android.os.Build;
 
 import androidx.annotation.OptIn;
+import androidx.annotation.RequiresApi;
 import androidx.camera.camera2.internal.Camera2CameraInfoImpl;
 import androidx.camera.camera2.internal.compat.CameraCharacteristicsCompat;
 import androidx.camera.core.impl.CameraInfoInternal;
@@ -35,6 +36,9 @@
 import org.robolectric.annotation.Config;
 import org.robolectric.annotation.internal.DoNotInstrument;
 
+import java.util.HashMap;
+import java.util.Map;
+
 @RunWith(RobolectricTestRunner.class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
@@ -85,4 +89,18 @@
         CameraInfoInternal wrongCameraInfo = mock(CameraInfoInternal.class);
         Camera2CameraInfo.from(wrongCameraInfo);
     }
+
+    @Config(minSdk = 28)
+    @RequiresApi(28)
+    @Test
+    public void canGetCameraCharacteristicsMap_fromCamera2CameraInfo() {
+        Camera2CameraInfoImpl impl = mock(Camera2CameraInfoImpl.class);
+        Map<String, CameraCharacteristics> characteristicsMap = new HashMap<>();
+        when(impl.getCameraCharacteristicsMap()).thenReturn(characteristicsMap);
+        Camera2CameraInfo camera2CameraInfo = new Camera2CameraInfo(impl);
+
+        Map<String, CameraCharacteristics> map = camera2CameraInfo.getCameraCharacteristicsMap();
+        assertThat(map).isSameInstanceAs(characteristicsMap);
+    }
+
 }
diff --git a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt b/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt
index e915887d..7d1c8fd 100644
--- a/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt
+++ b/camera/camera-core/src/androidTest/java/androidx/camera/core/ProcessingImageReaderDeviceTest.kt
@@ -83,12 +83,14 @@
 
     @Test
     fun processesImage_whenImageInBundleEnqueued() = runBlocking {
-        val processingImageReader = ProcessingImageReader(
-            640, 480,
-            ImageFormat.YUV_420_888, 2,
-            CameraXExecutors.mainThreadExecutor(), mCaptureBundle,
+        val processingImageReader = ProcessingImageReader.Builder(
+            640,
+            480,
+            ImageFormat.YUV_420_888,
+            2,
+            mCaptureBundle,
             mProcessor
-        )
+        ).build()
 
         val job = async {
             suspendCoroutine<ImageProxy?> { cont ->
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 71ae5c9..4ad53be 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
@@ -113,9 +113,8 @@
 import androidx.camera.core.internal.IoConfig;
 import androidx.camera.core.internal.TargetConfig;
 import androidx.camera.core.internal.YuvToJpegProcessor;
-import androidx.camera.core.internal.compat.quirk.DeviceQuirks;
-import androidx.camera.core.internal.compat.quirk.ImageCaptureWashedOutImageQuirk;
 import androidx.camera.core.internal.compat.quirk.SoftwareJpegEncodingPreferredQuirk;
+import androidx.camera.core.internal.compat.quirk.UseTorchAsFlashQuirk;
 import androidx.camera.core.internal.compat.workaround.ExifRotationAvailability;
 import androidx.camera.core.internal.utils.ImageUtil;
 import androidx.concurrent.futures.CallbackToFutureAdapter;
@@ -312,7 +311,7 @@
      * <p>When the flag is set, torch will be opened and closed to replace the flash fired by flash
      * mode.
      */
-    private final boolean mUseTorchFlash;
+    private boolean mUseTorchFlash = false;
 
     ////////////////////////////////////////////////////////////////////////////////////////////
     // [UseCase attached dynamic] - Can change but is only available when the UseCase is attached.
@@ -362,11 +361,6 @@
         } else {
             mEnableCheck3AConverged = false; // skip 3A convergence in MIN_LATENCY mode
         }
-
-        mUseTorchFlash = DeviceQuirks.get(ImageCaptureWashedOutImageQuirk.class) != null;
-        if (mUseTorchFlash) {
-            Logger.d(TAG, "Open and close torch to replace the flash fired by flash mode.");
-        }
     }
 
     @UiThread
@@ -417,16 +411,12 @@
             }
 
             // TODO: To allow user to use an Executor for the image processing.
-            mProcessingImageReader =
-                    new ProcessingImageReader(
-                            resolution.getWidth(),
-                            resolution.getHeight(),
-                            inputFormat,
-                            mMaxCaptureStages,
-                            /* postProcessExecutor */mExecutor,
-                            getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle()),
-                            captureProcessor,
-                            outputFormat);
+            mProcessingImageReader = new ProcessingImageReader.Builder(resolution.getWidth(),
+                    resolution.getHeight(), inputFormat, mMaxCaptureStages,
+                    getCaptureBundle(CaptureBundles.singleDefaultCaptureBundle()),
+                    captureProcessor).setPostProcessExecutor(mExecutor).setOutputFormat(
+                    outputFormat).build();
+
             mMetadataMatchingCaptureCallback = mProcessingImageReader.getCameraCaptureCallback();
             mImageReader = new SafeCloseImageReaderProxy(mProcessingImageReader);
             if (softwareJpegProcessor != null) {
@@ -1348,6 +1338,14 @@
         // enforceSoftwareJpegConstraints() hasn't removed the request.
         mUseSoftwareJpeg = useCaseConfig.isSoftwareJpegEncoderRequested();
 
+        CameraInternal camera = getCamera();
+        Preconditions.checkNotNull(camera, "Attached camera cannot be null");
+        mUseTorchFlash = camera.getCameraInfoInternal().getCameraQuirks()
+                .contains(UseTorchAsFlashQuirk.class);
+        if (mUseTorchFlash) {
+            Logger.d(TAG, "Open and close torch to replace the flash fired by flash mode.");
+        }
+
         mExecutor =
                 Executors.newFixedThreadPool(
                         1,
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
index 25e687d..3afb75c 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/ProcessingImageReader.java
@@ -40,6 +40,7 @@
 import java.util.Collections;
 import java.util.List;
 import java.util.concurrent.Executor;
+import java.util.concurrent.Executors;
 
 /**
  * An {@link ImageReaderProxy} which takes one or more {@link android.media.Image}, processes it,
@@ -53,6 +54,12 @@
  */
 class ProcessingImageReader implements ImageReaderProxy {
     private static final String TAG = "ProcessingImageReader";
+
+    // Exif metadata are restricted in size to 64 kB in JPEG images because according to
+    // the specification this information must be contained within a single JPEG APP1
+    // segment. (See: https://en.wikipedia.org/wiki/Exif)
+    private static final int EXIF_MAX_SIZE_BYTES = 64000;
+
     final Object mLock = new Object();
 
     // Callback when Image is ready from InputImageReader.
@@ -169,75 +176,41 @@
 
     private final List<Integer> mCaptureIdList = new ArrayList<>();
 
-    ProcessingImageReader(int width, int height, int format, int maxImages,
-            @NonNull Executor postProcessExecutor,
-            @NonNull CaptureBundle captureBundle, @NonNull CaptureProcessor captureProcessor) {
-        this(width, height, format, maxImages, postProcessExecutor, captureBundle,
-                captureProcessor, format);
-    }
-
-    /**
-     * Create a {@link ProcessingImageReader} with specific configurations.
-     *
-     * @param width               Width of the ImageReader
-     * @param height              Height of the ImageReader
-     * @param inputFormat         Input image format
-     * @param maxImages           Maximum Image number the ImageReader can hold. The capacity should
-     *                            be greater than the captureBundle size in order to hold all the
-     *                            Images needed with this processing.
-     * @param postProcessExecutor The Executor to execute the post-process of the image result.
-     * @param captureBundle       The {@link CaptureBundle} includes the processing information
-     * @param captureProcessor    The {@link CaptureProcessor} to be invoked when the Images are
-     *                            ready
-     * @param outputFormat        Output image format
-     */
-    ProcessingImageReader(int width, int height, int inputFormat, int maxImages,
-            @NonNull Executor postProcessExecutor,
-            @NonNull CaptureBundle captureBundle, @NonNull CaptureProcessor captureProcessor,
-            int outputFormat) {
-        this(new MetadataImageReader(width, height, inputFormat, maxImages), postProcessExecutor,
-                captureBundle, captureProcessor, outputFormat);
-    }
-
-    ProcessingImageReader(@NonNull MetadataImageReader imageReader,
-            @NonNull Executor postProcExecutor,
-            @NonNull CaptureBundle capBundle,
-            @NonNull CaptureProcessor capProcessor) {
-        this(imageReader, postProcExecutor, capBundle, capProcessor, imageReader.getImageFormat());
-    }
-
-    ProcessingImageReader(@NonNull MetadataImageReader imageReader,
-            @NonNull Executor postProcessExecutor,
-            @NonNull CaptureBundle captureBundle,
-            @NonNull CaptureProcessor captureProcessor,
-            int outputFormat) {
-        if (imageReader.getMaxImages() < captureBundle.getCaptureStages().size()) {
+    ProcessingImageReader(@NonNull Builder builder) {
+        if (builder.mInputImageReader.getMaxImages()
+                < builder.mCaptureBundle.getCaptureStages().size()) {
             throw new IllegalArgumentException(
                     "MetadataImageReader is smaller than CaptureBundle.");
         }
 
-        mInputImageReader = imageReader;
+        mInputImageReader = builder.mInputImageReader;
 
         // For JPEG ImageReaders, the Surface that is created will have format BLOB which can
         // only be allocated with a height of 1. The output Image from the image reader will read
         // its dimensions from the JPEG data's EXIF in order to set the final dimensions.
-        int outputWidth = imageReader.getWidth();
-        int outputHeight = imageReader.getHeight();
-        if (outputFormat == ImageFormat.JPEG) {
-            outputWidth = imageReader.getWidth() * imageReader.getHeight();
+        int outputWidth = mInputImageReader.getWidth();
+        int outputHeight = mInputImageReader.getHeight();
+
+        if (builder.mOutputFormat == ImageFormat.JPEG) {
+            // The output JPEG compression quality is 100 when taking a picture in MAX_QUALITY
+            // mode. It might cause the compressed data size exceeds image's width * height.
+            // YUV_420_888 should be 1.5 times of image's width * height. The compressed data
+            // size shouldn't exceed it. Therefore, scales the output image reader byte buffer to
+            // 1.5 times when the JPEG compression quality setting is 100.
+            outputWidth = (int) (outputWidth * outputHeight * 1.5f) + EXIF_MAX_SIZE_BYTES;
             outputHeight = 1;
         }
         mOutputImageReader = new AndroidImageReaderProxy(
-                ImageReader.newInstance(outputWidth, outputHeight, outputFormat,
-                        imageReader.getMaxImages()));
+                ImageReader.newInstance(outputWidth, outputHeight, builder.mOutputFormat,
+                        mInputImageReader.getMaxImages()));
 
-        mPostProcessExecutor = postProcessExecutor;
-        mCaptureProcessor = captureProcessor;
-        mCaptureProcessor.onOutputSurface(mOutputImageReader.getSurface(), outputFormat);
+        mPostProcessExecutor = builder.mPostProcessExecutor;
+        mCaptureProcessor = builder.mCaptureProcessor;
+        mCaptureProcessor.onOutputSurface(mOutputImageReader.getSurface(), builder.mOutputFormat);
         mCaptureProcessor.onResolutionUpdate(
                 new Size(mInputImageReader.getWidth(), mInputImageReader.getHeight()));
 
-        setCaptureBundle(captureBundle);
+        setCaptureBundle(builder.mCaptureBundle);
     }
 
     @Override
@@ -450,4 +423,81 @@
             }
         }
     }
+
+    /**
+     * The builder to create a {@link ProcessingImageReader} object.
+     */
+    static final class Builder {
+        @NonNull
+        protected final MetadataImageReader mInputImageReader;
+        @NonNull
+        protected final CaptureBundle mCaptureBundle;
+        @NonNull
+        protected final CaptureProcessor mCaptureProcessor;
+
+        protected int mOutputFormat;
+
+        @NonNull
+        protected Executor mPostProcessExecutor = Executors.newSingleThreadExecutor();
+
+        /**
+         * Create a {@link Builder} with specific configurations.
+         *
+         * @param imageReader      The input image reader.
+         * @param captureBundle    The {@link CaptureBundle} includes the processing information
+         * @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are
+         *                         ready
+         */
+        Builder(@NonNull MetadataImageReader imageReader, @NonNull CaptureBundle captureBundle,
+                @NonNull CaptureProcessor captureProcessor) {
+            mInputImageReader = imageReader;
+            mCaptureBundle = captureBundle;
+            mCaptureProcessor = captureProcessor;
+            mOutputFormat = imageReader.getImageFormat();
+        }
+
+        /**
+         * Create a {@link Builder} with specific configurations.
+         *
+         * @param width            Width of the ImageReader
+         * @param height           Height of the ImageReader
+         * @param inputFormat      Input image format
+         * @param maxImages        Maximum Image number the ImageReader can hold. The capacity
+         *                         should be greater than the captureBundle size in order to hold
+         *                         all the Images needed with this processing.
+         * @param captureBundle    The {@link CaptureBundle} includes the processing information
+         * @param captureProcessor The {@link CaptureProcessor} to be invoked when the Images are
+         *                         ready
+         */
+        Builder(int width, int height, int inputFormat, int maxImages,
+                @NonNull CaptureBundle captureBundle, @NonNull CaptureProcessor captureProcessor) {
+            this(new MetadataImageReader(width, height, inputFormat, maxImages), captureBundle,
+                    captureProcessor);
+        }
+
+        /**
+         * Sets an Executor to execute the post-process of the image result.
+         */
+        @NonNull
+        Builder setPostProcessExecutor(@NonNull Executor postProcessExecutor) {
+            mPostProcessExecutor = postProcessExecutor;
+            return this;
+        }
+
+        /**
+         * Sets the output image format.
+         */
+        @NonNull
+        Builder setOutputFormat(int outputFormat) {
+            mOutputFormat = outputFormat;
+            return this;
+        }
+
+        /**
+         * Builds an {@link ProcessingImageReader} from current configurations.
+         */
+        ProcessingImageReader build() {
+            return new ProcessingImageReader(this);
+        }
+    }
 }
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 40c4d50..5cdf970 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
@@ -45,7 +45,6 @@
 import java.io.OutputStream;
 import java.nio.ByteBuffer;
 import java.util.List;
-import java.util.concurrent.ExecutionException;
 
 /**
  * A CaptureProcessor which produces JPEGs from input YUV images.
@@ -151,10 +150,15 @@
             // Enqueue the completed jpeg image
             imageWriter.queueInputImage(jpegImage);
             jpegImage = null;
-        } catch (InterruptedException | ExecutionException e) {
+        } catch (Exception e) {
+            // InterruptedException, ExecutionException and EOFException might be caught here.
+            //
             // InterruptedException should not be possible here since
             // imageProxyListenableFuture.isDone() returned true, but we have to handle the
             // exception case so bundle it with ExecutionException.
+            //
+            // EOFException might happen if the compressed JPEG data size exceeds the byte buffer
+            // size of the output image reader.
             if (processing) {
                 Logger.e(TAG, "Failed to process YUV -> JPEG", e);
                 // Something went wrong attempting to retrieve ImageProxy. Enqueue an invalid buffer
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
index 1dc5105..b43c532 100644
--- a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/DeviceQuirksLoader.java
@@ -42,10 +42,6 @@
             quirks.add(new ImageCaptureRotationOptionQuirk());
         }
 
-        if (ImageCaptureWashedOutImageQuirk.load()) {
-            quirks.add(new ImageCaptureWashedOutImageQuirk());
-        }
-
         return quirks;
     }
 }
diff --git a/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/UseTorchAsFlashQuirk.java b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/UseTorchAsFlashQuirk.java
new file mode 100644
index 0000000..b8facd4
--- /dev/null
+++ b/camera/camera-core/src/main/java/androidx/camera/core/internal/compat/quirk/UseTorchAsFlashQuirk.java
@@ -0,0 +1,29 @@
+/*
+ * 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.camera.core.internal.compat.quirk;
+
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk interface which denotes CameraX should use torch for flash when flashMode is
+ * ON or AUTO.
+ *
+ * <p>Subclasses of this interface can denote the reason why torch is required instead of AE
+ * pre-capture.
+ */
+public interface UseTorchAsFlashQuirk extends Quirk {
+}
diff --git a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
index d1dd263..09d9d63 100644
--- a/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
+++ b/camera/camera-core/src/test/java/androidx/camera/core/ProcessingImageReaderTest.java
@@ -130,9 +130,9 @@
             throws InterruptedException, TimeoutException, ExecutionException {
         // Sets the callback from ProcessingImageReader to start processing
         CaptureProcessor captureProcessor = mock(CaptureProcessor.class);
-        ProcessingImageReader processingImageReader = new ProcessingImageReader(
-                mMetadataImageReader, sPausedExecutor, mCaptureBundle,
-                captureProcessor);
+        ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
+                mMetadataImageReader, mCaptureBundle, captureProcessor).setPostProcessExecutor(
+                sPausedExecutor).build();
         processingImageReader.setOnImageAvailableListener(mock(
                 ImageReaderProxy.OnImageAvailableListener.class),
                 CameraXExecutors.mainThreadExecutor());
@@ -193,9 +193,8 @@
             throws InterruptedException {
         // Sets the callback from ProcessingImageReader to start processing
         WaitingCaptureProcessor waitingCaptureProcessor = new WaitingCaptureProcessor();
-        ProcessingImageReader processingImageReader = new ProcessingImageReader(
-                mMetadataImageReader, android.os.AsyncTask.THREAD_POOL_EXECUTOR, mCaptureBundle,
-                waitingCaptureProcessor);
+        ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
+                mMetadataImageReader, mCaptureBundle, waitingCaptureProcessor).build();
         processingImageReader.setOnImageAvailableListener(mock(
                 ImageReaderProxy.OnImageAvailableListener.class),
                 CameraXExecutors.mainThreadExecutor());
@@ -234,9 +233,9 @@
     @Test
     public void closeImageHalfway() throws InterruptedException {
         // Sets the callback from ProcessingImageReader to start processing
-        ProcessingImageReader processingImageReader = new ProcessingImageReader(
-                mMetadataImageReader, sPausedExecutor, mCaptureBundle,
-                NOOP_PROCESSOR);
+        ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(
+                mMetadataImageReader, mCaptureBundle, NOOP_PROCESSOR).setPostProcessExecutor(
+                sPausedExecutor).build();
         processingImageReader.setOnImageAvailableListener(mock(
                 ImageReaderProxy.OnImageAvailableListener.class),
                 CameraXExecutors.mainThreadExecutor());
@@ -263,17 +262,15 @@
         MetadataImageReader metadataImageReader = new MetadataImageReader(imageReaderProxy);
 
         // Expects to throw exception when creating ProcessingImageReader.
-        new ProcessingImageReader(metadataImageReader, android.os.AsyncTask.THREAD_POOL_EXECUTOR,
-                mCaptureBundle,
-                NOOP_PROCESSOR);
+        new ProcessingImageReader.Builder(metadataImageReader, mCaptureBundle,
+                NOOP_PROCESSOR).build();
     }
 
     @Test(expected = IllegalArgumentException.class)
     public void captureStageExceedMaxCaptureStage_setCaptureBundleThrowsException() {
         // Creates a ProcessingImageReader with maximum Image number.
-        ProcessingImageReader processingImageReader = new ProcessingImageReader(100, 100,
-                ImageFormat.YUV_420_888, 2, android.os.AsyncTask.THREAD_POOL_EXECUTOR,
-                mCaptureBundle, mock(CaptureProcessor.class));
+        ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(100, 100,
+                ImageFormat.YUV_420_888, 2, mCaptureBundle, mock(CaptureProcessor.class)).build();
 
         // Expects to throw exception when invoke the setCaptureBundle method with a
         // CaptureBundle size greater than maximum image number.
@@ -284,9 +281,9 @@
     @Test
     public void imageReaderFormatIsOutputFormat() {
         // Creates a ProcessingImageReader with input format YUV_420_888 and output JPEG
-        ProcessingImageReader processingImageReader = new ProcessingImageReader(100, 100,
-                ImageFormat.YUV_420_888, 2, android.os.AsyncTask.THREAD_POOL_EXECUTOR,
-                mCaptureBundle, mock(CaptureProcessor.class), ImageFormat.JPEG);
+        ProcessingImageReader processingImageReader = new ProcessingImageReader.Builder(100, 100,
+                ImageFormat.YUV_420_888, 2, mCaptureBundle,
+                mock(CaptureProcessor.class)).setOutputFormat(ImageFormat.JPEG).build();
 
         assertThat(processingImageReader.getImageFormat()).isEqualTo(ImageFormat.JPEG);
     }
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
index 190550f..7d12101 100644
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/RecorderTest.kt
@@ -72,6 +72,8 @@
 import java.util.concurrent.Semaphore
 import java.util.concurrent.TimeUnit
 
+private const val FINALIZE_TIMEOUT = 5000L
+
 @LargeTest
 @RunWith(AndroidJUnit4::class)
 class RecorderTest {
@@ -187,7 +189,7 @@
 
         activeRecording.stop()
 
-        inOrder.verify(videoRecordEventListener, timeout(1000L))
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         checkFileHasAudioAndVideo(Uri.fromFile(file))
@@ -234,7 +236,7 @@
         activeRecording.stop()
 
         // Wait for the recording to complete.
-        assertThat(finalizeSemaphore.tryAcquire(1000L, TimeUnit.MILLISECONDS)).isTrue()
+        assertThat(finalizeSemaphore.tryAcquire(FINALIZE_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
 
         assertThat(uri).isNotEqualTo(Uri.EMPTY)
 
@@ -273,7 +275,7 @@
 
             activeRecording.stop()
 
-            inOrder.verify(videoRecordEventListener, timeout(1000L))
+            inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
                 .accept(any(VideoRecordEvent.Finalize::class.java))
 
             checkFileHasAudioAndVideo(Uri.fromFile(file))
@@ -336,7 +338,7 @@
         activeRecording.stop()
 
         // Wait for the recording to be finalized.
-        inOrder.verify(videoRecordEventListener, timeout(1000L))
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         checkFileHasAudioAndVideo(Uri.fromFile(file))
@@ -387,7 +389,7 @@
         // Stop
         activeRecording.stop()
 
-        inOrder.verify(videoRecordEventListener, timeout(1000L))
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         val captor = ArgumentCaptor.forClass(VideoRecordEvent::class.java)
@@ -519,7 +521,7 @@
 
         activeRecording.stop()
         // Wait for the recording to be finalized.
-        inOrder.verify(videoRecordEventListener, timeout(1000L))
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
         file.delete()
     }
@@ -554,7 +556,7 @@
 
         activeRecording.stop()
         // Wait for the recording to be finalized.
-        inOrder.verify(videoRecordEventListener, timeout(1000L))
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
         file.delete()
     }
@@ -577,7 +579,7 @@
 
         invokeSurfaceRequest()
 
-        verify(videoRecordEventListener, timeout(1000L))
+        verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         file.delete()
@@ -605,7 +607,38 @@
                 .accept(any(VideoRecordEvent.Status::class.java))
         }
 
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
+            .accept(any(VideoRecordEvent.Finalize::class.java))
+
+        file.delete()
+    }
+
+    @Test
+    fun stop_WhenUseCaseDetached() {
+        clearInvocations(videoRecordEventListener)
+        invokeSurfaceRequest()
+        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        val outputOptions = FileOutputOptions.builder().setFile(file).build()
+
+        val pendingRecording = recorder.prepareRecording(outputOptions)
+        pendingRecording.withEventListener(
+            CameraXExecutors.directExecutor(),
+            videoRecordEventListener
+        ).withAudioEnabled()
+
+        pendingRecording.start()
+
+        val inOrder = inOrder(videoRecordEventListener)
         inOrder.verify(videoRecordEventListener, timeout(1000L))
+            .accept(any(VideoRecordEvent.Start::class.java))
+        inOrder.verify(videoRecordEventListener, timeout(15000L).atLeast(5))
+            .accept(any(VideoRecordEvent.Status::class.java))
+
+        instrumentation.runOnMainSync {
+            cameraUseCaseAdapter.removeUseCases(listOf(preview))
+        }
+
+        inOrder.verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         file.delete()
@@ -661,7 +694,7 @@
 
         activeRecording.stop()
 
-        verify(videoRecordEventListener, timeout(1000L))
+        verify(videoRecordEventListener, timeout(FINALIZE_TIMEOUT))
             .accept(any(VideoRecordEvent.Finalize::class.java))
 
         checkFileAudio(Uri.fromFile(file), false)
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureRotationTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureRotationTest.kt
deleted file mode 100644
index 6aed56b..0000000
--- a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoCaptureRotationTest.kt
+++ /dev/null
@@ -1,200 +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.camera.video
-
-import android.Manifest
-import android.content.ContentResolver
-import android.content.Context
-import android.media.MediaMetadataRetriever
-import android.media.MediaRecorder
-import android.net.Uri
-import android.os.Build
-import android.view.Surface
-import androidx.camera.camera2.Camera2Config
-import androidx.camera.core.CameraSelector
-import androidx.camera.core.CameraX
-import androidx.camera.core.VideoCapture
-import androidx.camera.core.impl.utils.CameraOrientationUtil
-import androidx.camera.core.impl.utils.executor.CameraXExecutors
-import androidx.camera.core.internal.CameraUseCaseAdapter
-import androidx.camera.testing.AudioUtil
-import androidx.camera.testing.CameraUtil
-import androidx.test.core.app.ApplicationProvider
-import androidx.test.filters.LargeTest
-import androidx.test.platform.app.InstrumentationRegistry
-import androidx.test.rule.GrantPermissionRule
-import com.google.common.truth.Truth.assertThat
-import com.nhaarman.mockitokotlin2.any
-import org.junit.After
-import org.junit.Assume
-import org.junit.Before
-import org.junit.Rule
-import org.junit.Test
-import org.junit.runner.RunWith
-import org.junit.runners.Parameterized
-import org.mockito.ArgumentCaptor
-import org.mockito.Mockito
-import java.io.File
-import java.io.IOException
-import java.util.ArrayList
-import java.util.concurrent.ExecutionException
-import java.util.concurrent.TimeUnit
-import java.util.concurrent.TimeoutException
-
-@LargeTest
-@RunWith(Parameterized::class)
-class VideoCaptureRotationTest(
-    private var cameraSelector: CameraSelector,
-    private var targetRotation: Int
-) {
-    companion object {
-        @JvmStatic
-        @Parameterized.Parameters
-        fun data(): Collection<Array<Any>> {
-            val result: MutableList<Array<Any>> = ArrayList()
-            result.add(arrayOf(CameraSelector.DEFAULT_BACK_CAMERA, Surface.ROTATION_90))
-            result.add(arrayOf(CameraSelector.DEFAULT_BACK_CAMERA, Surface.ROTATION_180))
-            result.add(arrayOf(CameraSelector.DEFAULT_FRONT_CAMERA, Surface.ROTATION_90))
-            result.add(arrayOf(CameraSelector.DEFAULT_FRONT_CAMERA, Surface.ROTATION_180))
-            return result
-        }
-    }
-
-    private val instrumentation = InstrumentationRegistry.getInstrumentation()
-
-    @get:Rule
-    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest()
-
-    @get:Rule
-    val runtimePermissionRule: GrantPermissionRule = GrantPermissionRule.grant(
-        Manifest.permission.WRITE_EXTERNAL_STORAGE,
-        Manifest.permission.RECORD_AUDIO
-    )
-
-    private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
-    private lateinit var context: Context
-    private lateinit var contentResolver: ContentResolver
-    private lateinit var videoUseCase: VideoCapture
-    private lateinit var callback: VideoCapture.OnVideoSavedCallback
-    private lateinit var outputFileResultsArgumentCaptor:
-        ArgumentCaptor<VideoCapture.OutputFileResults>
-
-    @Before
-    fun setUp() {
-        // TODO(b/168175357): Fix VideoCaptureTest problems on CuttleFish API 29
-        Assume.assumeFalse(
-            "Cuttlefish has MediaCodec dequeueInput/Output buffer fails issue. Unable to test.",
-            Build.MODEL.contains("Cuttlefish") && Build.VERSION.SDK_INT == 29
-        )
-
-        // TODO(b/168187087): Video: Unable to record Video on Pixel 1 API 26,27 when only
-        // VideoCapture is bound
-        Assume.assumeFalse(
-            "Pixel running API 26 has CameraDevice.onError when set repeating request",
-            Build.DEVICE == "sailfish" &&
-                (Build.VERSION.SDK_INT == 26 || Build.VERSION.SDK_INT == 27)
-        )
-        Assume.assumeTrue(AudioUtil.canStartAudioRecord(MediaRecorder.AudioSource.CAMCORDER))
-
-        context = ApplicationProvider.getApplicationContext()
-        CameraX.initialize(context, Camera2Config.defaultConfig())
-        Assume.assumeTrue(
-            CameraUtil.hasCameraWithLensFacing(
-                cameraSelector.lensFacing!!
-            )
-        )
-        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
-        contentResolver = context.contentResolver
-
-        callback = Mockito.mock(
-            VideoCapture.OnVideoSavedCallback::class.java
-        )
-        outputFileResultsArgumentCaptor = ArgumentCaptor.forClass(
-            VideoCapture.OutputFileResults::class.java
-        )
-    }
-
-    @After
-    @Throws(InterruptedException::class, ExecutionException::class, TimeoutException::class)
-    fun tearDown() {
-        instrumentation.runOnMainSync {
-            if (this::cameraUseCaseAdapter.isInitialized) {
-                cameraUseCaseAdapter.removeUseCases(cameraUseCaseAdapter.useCases)
-            }
-        }
-        CameraX.shutdown()[10000, TimeUnit.MILLISECONDS]
-    }
-
-    @Test
-    @Throws(IOException::class)
-    fun metadataGetCorrectRotation_afterVideoCaptureRecording() {
-        // Sets the device rotation.
-        videoUseCase = VideoCapture.Builder()
-            .setTargetRotation(targetRotation)
-            .build()
-        val savedFile = File.createTempFile("CameraX", ".tmp")
-        savedFile.deleteOnExit()
-
-        instrumentation.runOnMainSync {
-            try {
-                cameraUseCaseAdapter.addUseCases(setOf(videoUseCase))
-            } catch (e: CameraUseCaseAdapter.CameraException) {
-                e.printStackTrace()
-            }
-        }
-
-        // Start recording
-        videoUseCase.startRecording(
-            VideoCapture.OutputFileOptions.Builder(savedFile).build(),
-            CameraXExecutors.mainThreadExecutor(), callback
-        )
-        // The way to control recording length might not be applicable in the new VideoCapture.
-        try {
-            Thread.sleep(3000)
-        } catch (e: InterruptedException) {
-            e.printStackTrace()
-        }
-
-        // Assert.
-        // Checks the target rotation is correct when the use case is bound.
-        videoUseCase.stopRecording()
-
-        Mockito.verify(callback, Mockito.timeout(2000)).onVideoSaved(any())
-
-        val targetRotationDegree = CameraOrientationUtil.surfaceRotationToDegrees(targetRotation)
-        val videoRotation: Int
-        val mediaRetriever = MediaMetadataRetriever()
-
-        mediaRetriever.apply {
-            setDataSource(context, Uri.fromFile(savedFile))
-            videoRotation = extractMetadata(
-                MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION
-            )?.toInt()!!
-        }
-
-        val sensorRotation = CameraUtil.getSensorOrientation(cameraSelector.lensFacing!!)
-        // Whether the camera lens and display are facing opposite directions.
-        val isOpposite = cameraSelector.lensFacing == CameraSelector.LENS_FACING_BACK
-        val relativeRotation = CameraOrientationUtil.getRelativeImageRotation(
-            targetRotationDegree,
-            sensorRotation!!,
-            isOpposite
-        )
-
-        // Checks the rotation from video file's metadata is matched with the relative rotation.
-        assertThat(videoRotation).isEqualTo(relativeRotation)
-    }
-}
\ No newline at end of file
diff --git a/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
new file mode 100644
index 0000000..c482c6d
--- /dev/null
+++ b/camera/camera-video/src/androidTest/java/androidx/camera/video/VideoRecordingTest.kt
@@ -0,0 +1,305 @@
+/*
+ * 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.camera.video
+
+import android.content.Context
+import android.graphics.SurfaceTexture
+import android.media.MediaMetadataRetriever
+import android.net.Uri
+import android.os.Build
+import android.util.Log
+import android.util.Size
+import android.view.Surface
+import androidx.camera.camera2.Camera2Config
+import androidx.camera.core.CameraInfo
+import androidx.camera.core.CameraSelector
+import androidx.camera.core.CameraX
+import androidx.camera.core.Preview
+import androidx.camera.core.impl.utils.CameraOrientationUtil
+import androidx.camera.core.impl.utils.executor.CameraXExecutors
+import androidx.camera.core.internal.CameraUseCaseAdapter
+import androidx.camera.testing.CameraUtil
+import androidx.camera.testing.SurfaceTextureProvider
+import androidx.core.util.Consumer
+import androidx.test.core.app.ApplicationProvider
+import androidx.test.filters.LargeTest
+import androidx.test.platform.app.InstrumentationRegistry
+import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import org.junit.After
+import org.junit.Assume
+import org.junit.Before
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+import java.io.File
+import java.util.concurrent.CountDownLatch
+import java.util.concurrent.TimeUnit
+
+@LargeTest
+@RunWith(Parameterized::class)
+class VideoRecordingTest(
+    private var cameraSelector: CameraSelector
+) {
+
+    @get:Rule
+    val cameraRule = CameraUtil.grantCameraPermissionAndPreTest()
+
+    companion object {
+        private const val VIDEO_TIMEOUT = 10_000L
+        private const val TAG = "VideoRecordingTest"
+        @JvmStatic
+        @Parameterized.Parameters
+        fun data(): Collection<Array<Any>> {
+            return listOf(
+                arrayOf(CameraSelector.DEFAULT_BACK_CAMERA),
+                arrayOf(CameraSelector.DEFAULT_FRONT_CAMERA),
+            )
+        }
+    }
+
+    private val instrumentation = InstrumentationRegistry.getInstrumentation()
+    private val context: Context = ApplicationProvider.getApplicationContext()
+    private lateinit var cameraUseCaseAdapter: CameraUseCaseAdapter
+    private lateinit var preview: Preview
+    private lateinit var cameraInfo: CameraInfo
+
+    private lateinit var latchForVideoSaved: CountDownLatch
+    private lateinit var latchForVideoRecording: CountDownLatch
+
+    private lateinit var finalize: VideoRecordEvent.Finalize
+
+    private val videoRecordEventListener = Consumer<VideoRecordEvent> {
+        when (it.eventType) {
+            VideoRecordEvent.EVENT_TYPE_START -> {
+                // Recording start.
+                Log.d(TAG, "Recording start")
+            }
+            VideoRecordEvent.EVENT_TYPE_FINALIZE -> {
+                // Recording stop.
+                Log.d(TAG, "Recording finalize")
+                finalize = it as VideoRecordEvent.Finalize
+                latchForVideoSaved.countDown()
+            }
+            VideoRecordEvent.EVENT_TYPE_STATUS -> {
+                // Make sure the recording proceed for a while.
+                latchForVideoRecording.countDown()
+            }
+            VideoRecordEvent.EVENT_TYPE_PAUSE, VideoRecordEvent.EVENT_TYPE_RESUME -> {
+                // no op for this test, skip these event now.
+            }
+            else -> {
+                throw IllegalStateException()
+            }
+        }
+    }
+
+    @Before
+    fun setUp() {
+        Assume.assumeTrue(CameraUtil.hasCameraWithLensFacing(cameraSelector.lensFacing!!))
+        // Skip for b/168175357
+        Assume.assumeFalse(
+            "Cuttlefish has MediaCodec dequeueInput/Output buffer fails issue. Unable to test.",
+            Build.MODEL.contains("Cuttlefish") && Build.VERSION.SDK_INT == 29
+        )
+
+        CameraX.initialize(context, Camera2Config.defaultConfig()).get()
+        cameraUseCaseAdapter = CameraUtil.createCameraUseCaseAdapter(context, cameraSelector)
+        cameraInfo = cameraUseCaseAdapter.cameraInfo
+
+        // Add extra Preview to provide an additional surface for b/168187087.
+        preview = Preview.Builder().build()
+        // Sets surface provider to preview
+        instrumentation.runOnMainSync {
+            preview.setSurfaceProvider(
+                getSurfaceProvider()
+            )
+        }
+    }
+
+    @After
+    fun tearDown() {
+        if (this::cameraUseCaseAdapter.isInitialized) {
+            instrumentation.runOnMainSync {
+                cameraUseCaseAdapter.apply {
+                    removeUseCases(useCases)
+                }
+            }
+        }
+        CameraX.shutdown().get(10, TimeUnit.SECONDS)
+    }
+
+    @Test
+    fun getMetadataRotation_when_setTargetRotation() {
+        // Arrange.
+        val videoCapture = VideoCapture.withOutput(Recorder.Builder().build())
+        // Just set one Surface.ROTATION_90 to verify the function work or not.
+        val targetRotation = Surface.ROTATION_90
+        videoCapture.targetRotation = targetRotation
+
+        val file = File.createTempFile("CameraX", ".tmp").apply { deleteOnExit() }
+        latchForVideoSaved = CountDownLatch(1)
+        latchForVideoRecording = CountDownLatch(5)
+
+        instrumentation.runOnMainSync {
+            cameraUseCaseAdapter.addUseCases(listOf(preview, videoCapture))
+        }
+
+        // Act.
+        completeVideoRecording(videoCapture, file)
+
+        // Verify.
+        verifyMetadataRotation(targetRotation, file)
+        file.delete()
+    }
+
+    // TODO: Add other metadata info check, e.g. location, after Recorder add more metadata.
+
+    @Test
+    fun getCorrectResolution_when_setSupportedQuality() {
+        Assume.assumeTrue(QualitySelector.getSupportedQualities(cameraInfo).isNotEmpty())
+
+        val qualityList = QualitySelector.getSupportedQualities(cameraInfo)
+        instrumentation.runOnMainSync {
+            cameraUseCaseAdapter.addUseCases(listOf(preview))
+        }
+
+        Log.d(TAG, "CameraSelector: ${cameraSelector.lensFacing}, QualityList: $qualityList ")
+        qualityList.forEach loop@{ quality ->
+            val targetResolution = QualitySelector.getResolution(cameraInfo, quality)
+            if (targetResolution == null) {
+                // If targetResolution is null, try next one
+                Log.e(TAG, "Unable to get resolution for the quality: $quality")
+                return@loop
+            }
+
+            val recorder = Recorder.Builder()
+                .setQualitySelector(QualitySelector.of(quality)).build()
+
+            val videoCapture = VideoCapture.withOutput(recorder)
+            val file = File.createTempFile("video_$targetResolution", ".tmp")
+                .apply { deleteOnExit() }
+
+            latchForVideoSaved = CountDownLatch(1)
+            latchForVideoRecording = CountDownLatch(5)
+
+            instrumentation.runOnMainSync {
+                cameraUseCaseAdapter.addUseCases(listOf(videoCapture))
+            }
+
+            // Act.
+            completeVideoRecording(videoCapture, file)
+
+            // Verify.
+            verifyVideoResolution(targetResolution, file)
+
+            // Cleanup.
+            file.delete()
+            instrumentation.runOnMainSync {
+                cameraUseCaseAdapter.apply {
+                    removeUseCases(listOf(videoCapture))
+                }
+            }
+        }
+    }
+
+    private fun completeVideoRecording(videoCapture: VideoCapture<Recorder>, file: File) {
+        val outputOptions = FileOutputOptions.builder().setFile(file).build()
+
+        val activeRecording = videoCapture.output
+            .prepareRecording(outputOptions)
+            .withEventListener(
+                CameraXExecutors.directExecutor(),
+                videoRecordEventListener
+            )
+            .start()
+
+        // Wait for status event to proceed recording for a while.
+        assertThat(latchForVideoRecording.await(VIDEO_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+
+        activeRecording.stop()
+        // Wait for finalize event to saved file.
+        assertThat(latchForVideoSaved.await(VIDEO_TIMEOUT, TimeUnit.MILLISECONDS)).isTrue()
+
+        // Check if any error after recording finalized
+        assertWithMessage(TAG + "Finalize with error: ${finalize.error}, ${finalize.cause}.")
+            .that(finalize.hasError()).isFalse()
+    }
+
+    private fun verifyMetadataRotation(targetRotation: Int, file: File) {
+        // Whether the camera lens and display are facing opposite directions.
+        val isOpposite = cameraSelector.lensFacing == CameraSelector.LENS_FACING_BACK
+        val relativeRotation = CameraOrientationUtil.getRelativeImageRotation(
+            CameraOrientationUtil.surfaceRotationToDegrees(targetRotation),
+            CameraUtil.getSensorOrientation(cameraSelector.lensFacing!!)!!,
+            isOpposite
+        )
+        val videoRotation = getRotationInMetadata(Uri.fromFile(file))
+
+        // Checks the rotation from video file's metadata is matched with the relative rotation.
+        assertWithMessage(
+            TAG + ", $targetRotation rotation test failure:" +
+                ", videoRotation: $videoRotation" +
+                ", relativeRotation: $relativeRotation"
+        ).that(videoRotation).isEqualTo(relativeRotation)
+    }
+
+    private fun verifyVideoResolution(targetResolution: Size, file: File) {
+        val mediaRetriever = MediaMetadataRetriever()
+        lateinit var resolution: Size
+        mediaRetriever.apply {
+            setDataSource(context, Uri.fromFile(file))
+            val height = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_HEIGHT)!!
+                .toInt()
+            val width = extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_WIDTH)!!
+                .toInt()
+            resolution = Size(width, height)
+        }
+
+        // Compare with the resolution of video and the targetResolution in QualitySelector
+        assertWithMessage(
+            TAG + ", verifyVideoResolution failure:" +
+                ", videoResolution: $resolution" +
+                ", targetResolution: $targetResolution"
+        ).that(resolution).isEqualTo(targetResolution)
+    }
+
+    private fun getRotationInMetadata(uri: Uri): Int {
+        val mediaRetriever = MediaMetadataRetriever()
+        return mediaRetriever.let {
+            it.setDataSource(context, uri)
+            it.extractMetadata(MediaMetadataRetriever.METADATA_KEY_VIDEO_ROTATION)?.toInt()!!
+        }
+    }
+
+    private fun getSurfaceProvider(): Preview.SurfaceProvider {
+        return SurfaceTextureProvider.createSurfaceTextureProvider(
+            object : SurfaceTextureProvider.SurfaceTextureCallback {
+                override fun onSurfaceTextureReady(
+                    surfaceTexture: SurfaceTexture,
+                    resolution: Size
+                ) {
+                    // No-op
+                }
+                override fun onSafeToRelease(surfaceTexture: SurfaceTexture) {
+                    surfaceTexture.release()
+                }
+            }
+        )
+    }
+}
\ No newline at end of file
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/AudioSpec.java b/camera/camera-video/src/main/java/androidx/camera/video/AudioSpec.java
index 703d831..95d2746 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/AudioSpec.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/AudioSpec.java
@@ -23,6 +23,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
 
 import com.google.auto.value.AutoValue;
 
@@ -31,7 +32,9 @@
 
 /**
  * Audio specification that is options to config audio source and encoding.
+ * @hide
  */
+@RestrictTo(Scope.LIBRARY)
 @AutoValue
 public abstract class AudioSpec {
 
@@ -158,7 +161,11 @@
     @NonNull
     public abstract Builder toBuilder();
 
-    /** The builder of the {@link AudioSpec}. */
+    /**
+     * The builder of the {@link AudioSpec}.
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
     @SuppressWarnings("StaticFinalBuilder")
     @AutoValue.Builder
     public abstract static class Builder {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/MediaSpec.java b/camera/camera-video/src/main/java/androidx/camera/video/MediaSpec.java
index f27d8c6..66a78ee 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/MediaSpec.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/MediaSpec.java
@@ -22,6 +22,7 @@
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
 import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
 import androidx.core.util.Consumer;
 
 import com.google.auto.value.AutoValue;
@@ -32,7 +33,9 @@
 /**
  * MediaSpec communicates the encoding type and encoder-specific options for both the
  * video and audio inputs to the VideoOutput.
+ * @hide
  */
+@RestrictTo(Scope.LIBRARY)
 @AutoValue
 public abstract class MediaSpec {
 
@@ -120,7 +123,9 @@
 
     /**
      * The builder for {@link MediaSpec}.
+     * @hide
      */
+    @RestrictTo(Scope.LIBRARY)
     @SuppressWarnings("StaticFinalBuilder")
     @AutoValue.Builder
     public abstract static class Builder {
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java b/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
index c570ce8..2f7ad9a 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/QualitySelector.java
@@ -39,21 +39,20 @@
 /**
  * QualitySelector defines the desired quality setting.
  *
- * <p>There are several defined quality constants such as {@link #QUALITY_SD},
- * {@link #QUALITY_HD}, {@link #QUALITY_FHD} and {@link #QUALITY_FHD}, but not all of them
- * are supported on every device since each device has its own capabilities.
+ * <p>There are pre-defined quality constants that are universally used for video, such as
+ * {@link #QUALITY_SD}, {@link #QUALITY_HD}, {@link #QUALITY_FHD} and {@link #QUALITY_UHD}, but
+ * not all of them are supported on every device since each device has its own capabilities.
  * {@link #isQualitySupported(CameraInfo, int)} can be used to check whether a quality is
  * supported on the device or not and {@link #getResolution(CameraInfo, int)} can be used to get
- * the actual resolution defined in the device. However, checking qualities one by one is not
- * so inconvenient for the quality setting. QualitySelector is designed to facilitate the quality
- * setting. The typical usage is
+ * the actual resolution defined in the device. Aside from checking the qualities one by one,
+ * QualitySelector provides a more convenient way to select a quality. The typical usage of
+ * selecting a single desired quality is:
  * <pre>
  *     <code>
  * QualitySelector qualitySelector = QualitySelector.of(QualitySelector.QUALITY_FHD)
  *     </code>
  * </pre>
- * if there is only one desired quality, or a series of quality constants can be set by desired
- * order
+ * Or the usage of selecting a series of qualities by desired order:
  * <pre>
  *     <code>
  * QualitySelector qualitySelector = QualitySelector
@@ -62,12 +61,12 @@
  *         .finallyTry(QualitySelector.QUALITY_SHD)
  *     </code>
  * </pre>
- * A recommended way to set the {@link Procedure#finallyTry(int)} is giving guaranteed supported
+ * The recommended way to set the {@link Procedure#finallyTry(int)} is giving guaranteed supported
  * qualities such as {@link #QUALITY_LOWEST} and {@link #QUALITY_HIGHEST}, which ensures the
- * QualitySelector can always choose a supported quality. Another way to ensure a quality is
+ * QualitySelector can always choose a supported quality. Another way to ensure a quality will be
  * selected when none of the desired qualities are supported is to use
  * {@link Procedure#finallyTry(int, int)} with an open-ended fallback strategy such as
- * {@link #FALLBACK_STRATEGY_LOWER}.
+ * {@link #FALLBACK_STRATEGY_LOWER}:
  * <pre>
  *     <code>
  * QualitySelector qualitySelector = QualitySelector
@@ -75,30 +74,26 @@
  *         .finallyTry(QualitySelector.QUALITY_FHD, FALLBACK_STRATEGY_LOWER)
  *     </code>
  * </pre>
- * If QUALITY_UHD and QUALITY_FHD are not supported on the device, the next lower supported
- * quality than QUALITY_FHD will be attempted. If no lower quality is supported, the next higher
- * supported quality will be selected. {@link #select(CameraInfo)} can obtain the final result
- * quality based on the desired qualities and fallback strategy, {@link #QUALITY_NONE} will be
- * returned if all desired qualities are not supported and fallback strategy also cannot find a
- * supported one.
+ * If QUALITY_UHD and QUALITY_FHD are not supported on the device, QualitySelector will select
+ * the quality that is closest to and lower than QUALITY_FHD. If no lower quality is supported,
+ * the quality that is closest to and higher than QUALITY_FHD will be selected.
  */
 public class QualitySelector {
     private static final String TAG = "QualitySelector";
 
     /**
-     * Indicates no quality.
+     * A non-applicable quality.
      *
      * <p>Check QUALITY_NONE via {@link #isQualitySupported(CameraInfo, int)} will return
-     * {@code false}. {@link #select(CameraInfo)} will return QUALITY_NONE if all desired
-     * qualities are not supported and fallback strategy is not able to find a supported one.
+     * {@code false}.
      */
     public static final int QUALITY_NONE = -1;
     /**
-     * Choose the lowest video quality supported by the video frame producer.
+     * The lowest video quality supported by the video frame producer.
      */
     public static final int QUALITY_LOWEST = CamcorderProfile.QUALITY_LOW;
     /**
-     * Choose the highest video quality supported by the video frame producer.
+     * The highest video quality supported by the video frame producer.
      */
     public static final int QUALITY_HIGHEST = CamcorderProfile.QUALITY_HIGH;
     /**
@@ -144,38 +139,31 @@
             QUALITY_FHD, QUALITY_HD, QUALITY_SD);
 
     /**
-     * No fallback strategy.
-     *
-     * <p>When using this fallback strategy, if {@link #select(CameraInfo)} fails to find a
-     * supported quality, it will return {@link #QUALITY_NONE}.
+     * The strategy that no fallback strategy will be applied.
      */
     public static final int FALLBACK_STRATEGY_NONE = 0;
 
     /**
-     * Choose a higher quality if the desired quality isn't supported. Choose a lower quality if
-     * no higher quality is supported.
+     * Choose the quality that is closest to and higher than the desired quality. If that can not
+     * result in a supported quality, choose the quality that is closest to and lower than the
+     * desired quality.
      */
     public static final int FALLBACK_STRATEGY_HIGHER = 1;
 
     /**
-     * Choose a higher quality if the desired quality isn't supported.
-     *
-     * <p>When a higher quality can't be found, {@link #select(CameraInfo)} will return
-     * {@link #QUALITY_NONE}.
+     * Choose the quality that is closest to and higher than the desired quality.
      */
     public static final int FALLBACK_STRATEGY_STRICTLY_HIGHER = 2;
 
     /**
-     * Choose a lower quality if the desired quality isn't supported. Choose a higher quality if
-     * no lower quality is supported.
+     * Choose the quality that is closest to and lower than the desired quality. If that can not
+     * result in a supported quality, choose the quality that is closest to and higher than the
+     * desired quality.
      */
     public static final int FALLBACK_STRATEGY_LOWER = 3;
 
     /**
-     * Choose a lower quality if the desired quality isn't supported.
-     *
-     * <p>When a lower quality can't be found, {@link #select(CameraInfo)} will return
-     * {@link #QUALITY_NONE}.
+     * Choose the quality that is closest to and lower than the desired quality.
      */
     public static final int FALLBACK_STRATEGY_STRICTLY_LOWER = 4;
 
@@ -199,7 +187,7 @@
     }
 
     /**
-     * Check if the input quality is one of video quality constants.
+     * Checks if the input quality is one of video quality constants.
      *
      * @hide
      */
@@ -209,7 +197,7 @@
     }
 
     /**
-     * Get all video quality constants with clearly defined size sorted from large to small.
+     * Gets all video quality constants with clearly defined size sorted from largest to smallest.
      *
      * <p>{@link #QUALITY_NONE}, {@link #QUALITY_HIGHEST} and {@link #QUALITY_LOWEST} are not
      * included.
@@ -225,7 +213,7 @@
     /**
      * Gets all supported qualities on the device.
      *
-     * <p>The returned list is sorted by quality size from large to small. For the qualities in
+     * <p>The returned list is sorted by quality size from largest to smallest. For the qualities in
      * the returned list, with the same input cameraInfo,
      * {@link #isQualitySupported(CameraInfo, int)} will return {@code true} and
      * {@link #getResolution(CameraInfo, int)} will return the corresponding resolution.
@@ -243,13 +231,20 @@
     /**
      * Checks if the quality is supported.
      *
-     * <p>For the qualities in the list of {@link #getSupportedQualities}, calling this method with
-     * these qualities will return {@code true}.
+     * <p>Calling this method with one of the qualities contained in the returned list of
+     * {@link #getSupportedQualities} will return {@code true}.
      *
-     * @param cameraInfo the cameraInfo
-     * @param quality one of the quality constants. Possible values include
-     * {@link #QUALITY_LOWEST}, {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD},
-     * {@link #QUALITY_FHD}, or {@link #QUALITY_UHD}.
+     * <p>Possible values for {@code quality} include {@link #QUALITY_LOWEST},
+     * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD}, {@link #QUALITY_FHD},
+     * {@link #QUALITY_UHD} and {@link #QUALITY_NONE}.
+     *
+     * <p>If this method is called with {@link #QUALITY_LOWEST} or {@link #QUALITY_HIGHEST}, it
+     * will return {@code true} except the case that none of the qualities can be supported.
+     *
+     * <p>If this method is called with {@link #QUALITY_NONE}, it will always return {@code false}.
+     *
+     * @param cameraInfo the cameraInfo for checking the quality.
+     * @param quality one of the quality constants.
      * @return {@code true} if the quality is supported; {@code false} otherwise.
      * @see #getSupportedQualities(CameraInfo)
      */
@@ -261,11 +256,14 @@
     /**
      * Gets the corresponding resolution from the input quality.
      *
-     * @param cameraInfo the cameraInfo
-     * @param quality one of the quality constants. Possible values include
-     * {@link QualitySelector#QUALITY_LOWEST}, {@link QualitySelector#QUALITY_HIGHEST},
-     * {@link QualitySelector#QUALITY_SD}, {@link QualitySelector#QUALITY_HD},
-     * {@link QualitySelector#QUALITY_FHD}, or {@link QualitySelector#QUALITY_UHD}.
+     * <p>Possible values for {@code quality} include {@link #QUALITY_LOWEST},
+     * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD}, {@link #QUALITY_FHD},
+     * {@link #QUALITY_UHD} and {@link #QUALITY_NONE}.
+     *
+     * <p>If this method is called with {@link #QUALITY_NONE}, it will always return {@code null}.
+     *
+     * @param cameraInfo the cameraInfo for checking the quality.
+     * @param quality one of the quality constants.
      * @return the corresponding resolution from the input quality, or {@code null} if the
      * quality is not supported on the device. {@link #isQualitySupported(CameraInfo, int)} can
      * be used to check if the input quality is supported.
@@ -296,13 +294,17 @@
     }
 
     /**
-     * Sets the first desired quality.
+     * Sets the desired quality with the highest priority.
+     *
+     * <p>This method initiates a procedure for specifying the requirements of selecting
+     * qualities. Other requirements can be further added with {@link Procedure} methods.
      *
      * @param quality the quality constant. Possible values include {@link #QUALITY_LOWEST},
      * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD}, {@link #QUALITY_FHD},
      * or {@link #QUALITY_UHD}.
-     * @return the procedure that can continue to be set
-     * @throws IllegalArgumentException if not a quality constant.
+     * @return the {@link Procedure} for specifying quality selection requirements.
+     * @throws IllegalArgumentException if the given quality is not a quality constant.
+     * @see Procedure
      */
     @NonNull
     public static Procedure firstTry(@VideoQuality int quality) {
@@ -318,7 +320,7 @@
      * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD}, {@link #QUALITY_FHD},
      * or {@link #QUALITY_UHD}.
      * @return the QualitySelector instance.
-     * @throws IllegalArgumentException if not a quality constant.
+     * @throws IllegalArgumentException if the given quality is not a quality constant.
      */
     @NonNull
     public static QualitySelector of(@VideoQuality int quality) {
@@ -349,24 +351,28 @@
     }
 
     /**
-     * Find a quality match to the desired quality settings.
+     * Finds a quality that matches the desired quality settings.
      *
-     * <p>The method bases on the desired qualities and the fallback strategy to find out a
-     * supported quality on this device. The desired qualities can be set by a series of try
-     * methods such as {@link #firstTry(int)}, {@link #of(int)},
-     * {@link Procedure#thenTry(int)} and {@link Procedure#finallyTry(int)}. The fallback strategy
-     * can be set via {@link #of(int, int)} and {@link Procedure#finallyTry(int, int)}. If no
-     * fallback strategy is specified, {@link #FALLBACK_STRATEGY_NONE} will be applied by default.
+     * <p>The method bases on the desired qualities and the fallback strategy to find a supported
+     * quality on this device. The desired qualities can be set by a series of try methods such
+     * as {@link #firstTry(int)}, {@link #of(int)}, {@link Procedure#thenTry(int)} and
+     * {@link Procedure#finallyTry(int)}. The fallback strategy can be set via
+     * {@link #of(int, int)} and {@link Procedure#finallyTry(int, int)}. If no fallback strategy
+     * is specified, {@link #FALLBACK_STRATEGY_NONE} will be applied by default.
      *
      * <p>The search algorithm first checks which desired quality is supported according to the
      * set sequence. If no desired quality is supported, the fallback strategy will be applied to
      * the quality set with it. If there is still no quality can be found, {@link #QUALITY_NONE}
      * will be returned.
      *
-     * @param cameraInfo the cameraInfo
+     * @param cameraInfo the cameraInfo for checking the quality.
      * @return the first supported quality of the desired qualities, or a supported quality
      * searched by fallback strategy, or {@link #QUALITY_NONE} when no quality is found.
+     * @see Procedure
+     *
+     * @hide
      */
+    @RestrictTo(Scope.LIBRARY)
     @VideoQuality
     public int select(@NonNull CameraInfo cameraInfo) {
         VideoCapabilities videoCapabilities = VideoCapabilities.from(cameraInfo);
@@ -474,7 +480,7 @@
     }
 
     /**
-     * The procedure can continue to set the desired quality and fallback strategy.
+     * The procedure can be used to set desired qualities and fallback strategy.
      */
     public static class Procedure {
         private final List<Integer> mPreferredQualityList = new ArrayList<>();
@@ -484,13 +490,13 @@
         }
 
         /**
-         * Sets the next desired quality.
+         * Adds a quality candidate.
          *
          * @param quality the quality constant. Possible values include {@link #QUALITY_LOWEST},
          * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD},
          * {@link #QUALITY_FHD} or {@link #QUALITY_UHD}.
          * @return the procedure that can continue to be set
-         * @throws IllegalArgumentException if not a quality constant
+         * @throws IllegalArgumentException if the given quality is not a quality constant
          */
         @NonNull
         public Procedure thenTry(@VideoQuality int quality) {
@@ -503,11 +509,14 @@
          *
          * <p>The returned QualitySelector will adopt {@link #FALLBACK_STRATEGY_NONE}.
          *
+         * <p>This method finishes the setting procedure and generates a {@link QualitySelector}
+         * with the requirements set to the procedure.
+         *
          * @param quality the quality constant. Possible values include {@link #QUALITY_LOWEST},
          * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD},
          * {@link #QUALITY_FHD} or {@link #QUALITY_UHD}.
-         * @return the QualitySelector.
-         * @throws IllegalArgumentException if not a quality constant
+         * @return the {@link QualitySelector}.
+         * @throws IllegalArgumentException if the given quality is not a quality constant
          */
         @NonNull
         public QualitySelector finallyTry(@VideoQuality int quality) {
@@ -520,6 +529,9 @@
          * <p>The fallback strategy will be applied on this quality when all desired qualities are
          * not supported.
          *
+         * <p>This method finishes the setting procedure and generates a {@link QualitySelector}
+         * with the requirements set to the procedure.
+         *
          * @param quality the quality constant. Possible values include {@link #QUALITY_LOWEST},
          * {@link #QUALITY_HIGHEST}, {@link #QUALITY_SD}, {@link #QUALITY_HD},
          * {@link #QUALITY_FHD} or {@link #QUALITY_UHD}.
@@ -527,7 +539,7 @@
          * {@link #FALLBACK_STRATEGY_NONE}, {@link #FALLBACK_STRATEGY_HIGHER},
          * {@link #FALLBACK_STRATEGY_STRICTLY_HIGHER}, {@link #FALLBACK_STRATEGY_LOWER} and
          * {@link #FALLBACK_STRATEGY_STRICTLY_LOWER}.
-         * @return the QualitySelector.
+         * @return the {@link QualitySelector}.
          * @throws IllegalArgumentException if {@code quality} is not a quality constant or
          * {@code fallbackStrategy} is not a fallback strategy constant.
          */
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
index caaa794..869bcd9b 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/Recorder.java
@@ -16,6 +16,11 @@
 
 package androidx.camera.video;
 
+import static androidx.camera.video.QualitySelector.FALLBACK_STRATEGY_HIGHER;
+import static androidx.camera.video.QualitySelector.QUALITY_FHD;
+import static androidx.camera.video.QualitySelector.QUALITY_HD;
+import static androidx.camera.video.QualitySelector.QUALITY_SD;
+
 import android.Manifest;
 import android.annotation.SuppressLint;
 import android.content.ContentValues;
@@ -142,14 +147,9 @@
          */
         PAUSED,
         /**
-         * There's a running recording and the Recorder is being released.
+         * There's a running recording and the Recorder is being reset.
          */
-        RELEASING,
-        /**
-         * The Recorder has been released and any operation attempt will throw an
-         * {@link IllegalStateException}.
-         */
-        RELEASED,
+        RESETING,
         /**
          * The Recorder encountered errors and any operation will attempt will throw an
          * {@link IllegalStateException}. Users can handle the error by monitoring
@@ -185,6 +185,24 @@
         ENCODER_ERROR
     }
 
+    /**
+     * Default quality selector for recordings.
+     *
+     * <p>The default quality selector chooses a video quality suitable for recordings based on
+     * device and compatibility constraints. It is equivalent to:
+     * <pre>{@code
+     * QualitySelector.firstTry(QUALITY_FHD)
+     *         .thenTry(QUALITY_HD)
+     *         .thenTry(QUALITY_SD)
+     *         .finallyTry(QUALITY_FHD, FALLBACK_STRATEGY_HIGHER);
+     * }</pre>
+     */
+    public static final QualitySelector DEFAULT_QUALITY_SELECTOR =
+            QualitySelector.firstTry(QUALITY_FHD)
+                    .thenTry(QUALITY_HD)
+                    .thenTry(QUALITY_SD)
+                    .finallyTry(QUALITY_FHD, FALLBACK_STRATEGY_HIGHER);
+
     private static final AudioSpec AUDIO_SPEC_DEFAULT =
             AudioSpec.builder()
                     .setSourceFormat(
@@ -196,6 +214,7 @@
                     .build();
     private static final VideoSpec VIDEO_SPEC_DEFAULT =
             VideoSpec.builder()
+                    .setQualitySelector(DEFAULT_QUALITY_SELECTOR)
                     .setAspectRatio(VideoSpec.ASPECT_RATIO_16_9)
                     .build();
     private static final MediaSpec MEDIA_SPEC_DEFAULT =
@@ -285,9 +304,11 @@
 
     @SuppressLint("MissingPermission")
     @Override
-    public void onSurfaceRequested(@NonNull SurfaceRequest surfaceRequest) {
+    public void onSurfaceRequested(@NonNull SurfaceRequest request) {
         synchronized (mLock) {
             switch (getObservableData(mState)) {
+                case RESETING:
+                    // Fall-through
                 case PENDING_RECORDING:
                     // Fall-through
                 case PENDING_PAUSED:
@@ -295,7 +316,7 @@
                 case INITIALIZING:
                     // The recorder should be initialized only once until it is released.
                     if (mSurfaceRequested.compareAndSet(false, true)) {
-                        mSequentialExecutor.execute(() -> initializeInternal(surfaceRequest));
+                        mSequentialExecutor.execute(() -> initializeInternal(request));
                     }
                     break;
                 case IDLING:
@@ -306,16 +327,12 @@
                     // Fall-through
                 case ERROR:
                     throw new IllegalStateException("The Recorder has been initialized.");
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    surfaceRequest.willNotProvideSurface();
-                    Logger.w(TAG, "A surface is requested while the Recorder is released.");
-                    break;
             }
         }
     }
 
+    /** @hide */
+    @RestrictTo(RestrictTo.Scope.LIBRARY)
     @Override
     @NonNull
     public Observable<MediaSpec> getMediaSpec() {
@@ -401,30 +418,7 @@
     @NonNull
     private PendingRecording prepareRecordingInternal(@NonNull OutputOptions options) {
         Preconditions.checkNotNull(options, "The OutputOptions cannot be null.");
-        synchronized (mLock) {
-            switch (getObservableData(mState)) {
-                case INITIALIZING:
-                    // Fall-through
-                case PENDING_RECORDING:
-                    // Fall-through
-                case PENDING_PAUSED:
-                    // Fall-through
-                case ERROR:
-                    // Fall-through, create PendingRecording as usual, but it will be instantly
-                    // finalized at start().
-                case IDLING:
-                    // Fall-through
-                case PAUSED:
-                    // Fall-through
-                case RECORDING:
-                    break;
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    throw new IllegalStateException("The Recorder has been released.");
-            }
-            return new PendingRecording(this, options);
-        }
+        return new PendingRecording(this, options);
     }
 
     /**
@@ -432,7 +426,7 @@
      *
      * @return the {@link QualitySelector} provided to
      * {@link Builder#setQualitySelector(QualitySelector)} on the builder used to create this
-     * recorder, or the default value of {@link VideoSpec#QUALITY_SELECTOR_AUTO} if no quality
+     * recorder, or the default value of {@link Recorder#DEFAULT_QUALITY_SELECTOR} if no quality
      * selector was provided.
      */
     @NonNull
@@ -492,6 +486,8 @@
             ActiveRecording activeRecording = ActiveRecording.from(pendingRecording);
             mRunningRecording = activeRecording;
             switch (getObservableData(mState)) {
+                case RESETING:
+                    // Fall-through
                 case INITIALIZING:
                     // The recording will automatically start once the initialization completes.
                     setState(State.PENDING_RECORDING);
@@ -508,10 +504,6 @@
                     // Fall-through
                 case RECORDING:
                     throw new IllegalStateException("There's an active recording.");
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    throw new IllegalStateException("The Recorder has been released.");
                 case ERROR:
                     finalizeRecording(VideoRecordEvent.ERROR_RECORDER_ERROR, mErrorCause);
                     break;
@@ -526,6 +518,8 @@
             switch (getObservableData(mState)) {
                 case PENDING_RECORDING:
                     // Fall-through
+                case RESETING:
+                    // Fall-through
                 case INITIALIZING:
                     // The recording will automatically pause once the initialization completes.
                     setState(State.PENDING_PAUSED);
@@ -541,10 +535,6 @@
                 case PAUSED:
                     // No-op when the recording is already paused.
                     break;
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    throw new IllegalStateException("The Recorder has been released.");
                 case ERROR:
                     finalizeRecording(VideoRecordEvent.ERROR_RECORDER_ERROR, mErrorCause);
                     break;
@@ -557,6 +547,8 @@
             switch (getObservableData(mState)) {
                 case PENDING_PAUSED:
                     // Fall-through
+                case RESETING:
+                    // Fall-through
                 case INITIALIZING:
                     // The recording will automatically start once the initialization completes.
                     setState(State.PENDING_RECORDING);
@@ -572,10 +564,6 @@
                     mSequentialExecutor.execute(this::resumeInternal);
                     setState(State.RECORDING);
                     break;
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    throw new IllegalStateException("The Recorder has been released.");
                 case ERROR:
                     finalizeRecording(VideoRecordEvent.ERROR_RECORDER_ERROR, mErrorCause);
                     break;
@@ -590,6 +578,8 @@
                     // Fall-through
                 case PENDING_PAUSED:
                     // Fall-through
+                case RESETING:
+                    // Fall-through
                 case INITIALIZING:
                     finalizeRecording(VideoRecordEvent.ERROR_RECORDER_UNINITIALIZED,
                             new IllegalStateException("The Recorder hasn't been initialized."));
@@ -602,10 +592,6 @@
                 case RECORDING:
                     mSequentialExecutor.execute(() -> stopInternal(VideoRecordEvent.ERROR_NONE));
                     break;
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    throw new IllegalStateException("The Recorder has been released.");
                 case ERROR:
                     finalizeRecording(VideoRecordEvent.ERROR_RECORDER_ERROR, mErrorCause);
                     break;
@@ -621,7 +607,7 @@
      * is released will get {@link IllegalStateException}.
      */
     @ExecutedBy("mSequentialExecutor")
-    void release() {
+    void reset() {
         synchronized (mLock) {
             switch (getObservableData(mState)) {
                 case PENDING_RECORDING:
@@ -633,20 +619,18 @@
                 case ERROR:
                     // Fall-through
                 case IDLING:
-                    releaseInternal();
+                    resetInternal();
                     break;
                 case PAUSED:
                     // Fall-through
                 case RECORDING:
-                    setState(State.RELEASING);
+                    setState(State.RESETING);
                     // If there's an active recording, stop it first then release the resources
                     // at finalizeRecording().
                     mSequentialExecutor.execute(() -> stopInternal(VideoRecordEvent.ERROR_NONE));
                     break;
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
-                    // No-Op, the Recorder is already released.
+                case RESETING:
+                    // No-Op, the Recorder is being reset.
                     break;
             }
         }
@@ -669,8 +653,7 @@
                     resultSurface.release();
                 }
                 mSurface = null;
-                release();
-                setState(State.INITIALIZING);
+                reset();
             });
             onInitialized();
         } else {
@@ -692,9 +675,7 @@
                     // Fall-through
                 case PAUSED:
                     // Fall-through
-                case RELEASING:
-                    // Fall-through
-                case RELEASED:
+                case RESETING:
                     throw new IllegalStateException(
                             "Incorrectly invoke onInitialized() in state " + state);
                 case INITIALIZING:
@@ -990,8 +971,7 @@
                             resultSurface.release();
                         }
                         mSurface = null;
-                        release();
-                        setState(State.INITIALIZING);
+                        reset();
                     });
                     onInitialized();
                 });
@@ -1253,7 +1233,7 @@
     }
 
     @ExecutedBy("mSequentialExecutor")
-    private void releaseInternal() {
+    private void resetInternal() {
         if (mAudioEncoder != null) {
             mAudioEncoder.release();
             mAudioSource = null;
@@ -1268,7 +1248,7 @@
         }
 
         mSurfaceRequested.set(false);
-        setState(State.RELEASED);
+        setState(State.INITIALIZING);
     }
 
     private int internalAudioStateToEventAudioState(AudioState audioState) {
@@ -1349,8 +1329,8 @@
             setAudioState(AudioState.INITIALIZING);
         }
         synchronized (mLock) {
-            if (getObservableData(mState) == State.RELEASING) {
-                releaseInternal();
+            if (getObservableData(mState) == State.RESETING) {
+                resetInternal();
             } else {
                 setState(State.IDLING);
             }
@@ -1460,7 +1440,7 @@
          * depending on the resolutions supported by the camera and codec capabilities.
          *
          * <p>If no quality selector is provided, the default is
-         * {@link VideoSpec#QUALITY_SELECTOR_AUTO}.
+         * {@link #DEFAULT_QUALITY_SELECTOR}.
          * @see QualitySelector
          */
         @NonNull
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
index b8791541..acb2ae7 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoCapture.java
@@ -90,19 +90,32 @@
 /**
  * A use case that provides camera stream suitable for video application.
  *
- * <p>VideoCapture is used to create a camera stream suitable for video application. This stream
- * is used by the implementation of {@link VideoOutput}. Calling {@link #withOutput(VideoOutput)}
- * can generate a VideoCapture use case binding to the given VideoOutput.
+ * <p>VideoCapture is used to create a camera stream suitable for video application. The camera
+ * stream is used by the extended classes of {@link VideoOutput}.
+ * {@link #withOutput(VideoOutput)} can be used to create a VideoCapture instance associated with
+ * the given VideoOutput.
  *
- * <p>When binding VideoCapture, VideoCapture will initialize the camera stream according to the
- * resolution found by the {@link QualitySelector} in VideoOutput. Then VideoCapture will invoke
- * {@link VideoOutput#onSurfaceRequested(SurfaceRequest)} to request VideoOutput to provide a
- * {@link Surface} via {@link SurfaceRequest#provideSurface} to complete the initialization
- * process. After VideoCapture is bound, updating the QualitySelector in VideoOutput will have no
- * effect. If it needs to change the resolution of the camera stream after VideoCapture is bound,
- * it has to unbind the original VideoCapture, update the QualitySelector in VideoOutput and then
- * re-bind the VideoCapture. If the implementation of VideoOutput does not support modifying the
- * QualitySelector afterwards, it has to create a new VideoOutput and VideoCapture for re-bind.
+ * <p>When {@linkplain androidx.camera.lifecycle.ProcessCameraProvider#bindToLifecycle binding}
+ * VideoCapture to lifecycle, VideoCapture will create a camera stream with the resolution
+ * selected by the {@link QualitySelector} in VideoOutput. Example:
+ * <pre>
+ *     <code>
+ *         Recorder recorder = new Recorder.Builder()
+ *                 .setQualitySelector(QualitySelector.of(QualitySelector.QUALITY_FHD))
+ *                 .build();
+ *         VideoCapture<Recorder> videoCapture = VideoCapture.withOutput(recorder);
+ *     </code>
+ * </pre>
+ *
+ * <p>Then VideoCapture will {@link VideoOutput#onSurfaceRequested(SurfaceRequest) ask}
+ * VideoOutput to {@link SurfaceRequest#provideSurface provide} a {@link Surface} for setting up
+ * the camera stream. After VideoCapture is bound, update QualitySelector in VideoOutput will not
+ * have any effect. If it needs to change the resolution of the camera stream after VideoCapture
+ * is bound, it has to update QualitySelector of VideoOutput and rebind
+ * ({@linkplain androidx.camera.lifecycle.ProcessCameraProvider#unbind unbind} and
+ * {@linkplain androidx.camera.lifecycle.ProcessCameraProvider#bindToLifecycle bind}) the
+ * VideoCapture. If the extended class of VideoOutput does not have API to modify the
+ * QualitySelector, it has to create new VideoOutput and VideoCapture for rebinding.
  *
  * @param <T> the type of VideoOutput
  */
@@ -118,14 +131,13 @@
     private SurfaceRequest mSurfaceRequest;
 
     /**
-     * Create a VideoCapture builder with a {@link VideoOutput}.
+     * Create a VideoCapture associated with the given {@link VideoOutput}.
      *
-     * @param videoOutput the associated VideoOutput.
-     * @return the new Builder
+     * @throws NullPointerException if {@code videoOutput} is null.
      */
     @NonNull
     public static <T extends VideoOutput> VideoCapture<T> withOutput(@NonNull T videoOutput) {
-        return new VideoCapture.Builder<T>(videoOutput).build();
+        return new VideoCapture.Builder<T>(Preconditions.checkNotNull(videoOutput)).build();
     }
 
     /**
@@ -138,7 +150,10 @@
     }
 
     /**
-     * Gets the {@link VideoOutput} associated to this VideoCapture.
+     * Gets the {@link VideoOutput} associated with this VideoCapture.
+     *
+     * @return the value provided to {@link #withOutput(VideoOutput)} used to create this
+     * VideoCapture.
      */
     @SuppressWarnings("unchecked")
     @NonNull
@@ -155,7 +170,10 @@
      * has been attached to a camera.
      *
      * @return The rotation of the intended target.
+     *
+     * @hide
      */
+    @RestrictTo(Scope.LIBRARY_GROUP)
     @RotationValue
     public int getTargetRotation() {
         return getTargetRotationInternal();
@@ -173,7 +191,10 @@
      * created. The use case is fully created once it has been attached to a camera.
      *
      * @param rotation Desired rotation of the output video.
+     *
+     * @hide
      */
+    @RestrictTo(Scope.LIBRARY_GROUP)
     @OptIn(markerClass = ExperimentalUseCaseGroup.class)
     public void setTargetRotation(@RotationValue int rotation) {
         if (setTargetRotationInternal(rotation)) {
@@ -181,6 +202,12 @@
         }
     }
 
+    /**
+     * {@inheritDoc}
+     *
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY_GROUP)
     @Override
     public void onAttached() {
         getOutput().getStreamState().addObserver(CameraXExecutors.mainThreadExecutor(),
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoOutput.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoOutput.java
index f75556b..4a1b310 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoOutput.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoOutput.java
@@ -122,7 +122,9 @@
      * changes may not come for free and may require the video frame producer to re-initialize,
      * which could cause a new {@link SurfaceRequest} to be sent to
      * {@link #onSurfaceRequested(SurfaceRequest)}.
+     * @hide
      */
+    @RestrictTo(Scope.LIBRARY)
     @NonNull
     default Observable<MediaSpec> getMediaSpec() {
         return ConstantObservable.withValue(null);
diff --git a/camera/camera-video/src/main/java/androidx/camera/video/VideoSpec.java b/camera/camera-video/src/main/java/androidx/camera/video/VideoSpec.java
index bb14990..e286cbd 100644
--- a/camera/camera-video/src/main/java/androidx/camera/video/VideoSpec.java
+++ b/camera/camera-video/src/main/java/androidx/camera/video/VideoSpec.java
@@ -25,6 +25,8 @@
 
 import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
+import androidx.annotation.RestrictTo;
+import androidx.annotation.RestrictTo.Scope;
 
 import com.google.auto.value.AutoValue;
 
@@ -33,7 +35,9 @@
 
 /**
  * Video specification that is options to config video encoding.
+ * @hide
  */
+@RestrictTo(Scope.LIBRARY)
 @AutoValue
 public abstract class VideoSpec {
 
@@ -124,7 +128,11 @@
     @NonNull
     public abstract Builder toBuilder();
 
-    /** The builder of the {@link VideoSpec}. */
+    /**
+     * The builder of the {@link VideoSpec}.
+     * @hide
+     */
+    @RestrictTo(Scope.LIBRARY)
     @SuppressWarnings("StaticFinalBuilder")
     @AutoValue.Builder
     public abstract static class Builder {
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
index 6561438..4382484 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/PreviewTransformation.java
@@ -53,6 +53,7 @@
 import androidx.camera.core.ViewPort;
 import androidx.camera.view.internal.compat.quirk.DeviceQuirks;
 import androidx.camera.view.internal.compat.quirk.PreviewOneThirdWiderQuirk;
+import androidx.camera.view.internal.compat.quirk.TextureViewRotationQuirk;
 import androidx.core.util.Preconditions;
 
 /**
@@ -152,8 +153,14 @@
     Matrix getTextureViewCorrectionMatrix() {
         Preconditions.checkState(isTransformationInfoReady());
         RectF surfaceRect = new RectF(0, 0, mResolution.getWidth(), mResolution.getHeight());
-        return getRectToRect(surfaceRect, surfaceRect,
-                -surfaceRotationToRotationDegrees(mTargetRotation));
+        int rotationDegrees = -surfaceRotationToRotationDegrees(mTargetRotation);
+
+        TextureViewRotationQuirk textureViewRotationQuirk =
+                DeviceQuirks.get(TextureViewRotationQuirk.class);
+        if (textureViewRotationQuirk != null) {
+            rotationDegrees += textureViewRotationQuirk.getCorrectionRotation(mIsFrontCamera);
+        }
+        return getRectToRect(surfaceRect, surfaceRect, rotationDegrees);
     }
 
     /**
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
index d73a851..00641c7 100644
--- a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
+++ b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/DeviceQuirksLoader.java
@@ -47,6 +47,10 @@
             quirks.add(new SurfaceViewStretchedQuirk());
         }
 
+        if (TextureViewRotationQuirk.load()) {
+            quirks.add(new TextureViewRotationQuirk());
+        }
+
         return quirks;
     }
 }
diff --git a/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/TextureViewRotationQuirk.java b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/TextureViewRotationQuirk.java
new file mode 100644
index 0000000..d7e1880
--- /dev/null
+++ b/camera/camera-view/src/main/java/androidx/camera/view/internal/compat/quirk/TextureViewRotationQuirk.java
@@ -0,0 +1,55 @@
+/*
+ * 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.camera.view.internal.compat.quirk;
+
+import android.os.Build;
+import android.view.TextureView;
+
+import androidx.camera.core.impl.Quirk;
+
+/**
+ * A quirk that requires applying extra rotation on {@link TextureView}
+ *
+ * <p> On certain devices, the rotation of the output is incorrect. One example is b/177561470.
+ * In which case, the extra rotation is needed to correct the output on {@link TextureView}.
+ */
+public class TextureViewRotationQuirk implements Quirk {
+
+    private static final String FAIRPHONE = "Fairphone";
+    private static final String FAIRPHONE_2_MODEL = "FP2";
+
+    static boolean load() {
+        return isFairphone2();
+    }
+
+    /**
+     * Gets correction needed for the given camera.
+     */
+    public int getCorrectionRotation(boolean isFrontCamera) {
+        if (isFairphone2() && isFrontCamera) {
+            // On Fairphone2, the front camera output on TextureView is rotated 180°.
+            // See: b/177561470.
+            return 180;
+        }
+        return 0;
+    }
+
+    private static boolean isFairphone2() {
+        return FAIRPHONE.equalsIgnoreCase(Build.MANUFACTURER)
+                && FAIRPHONE_2_MODEL.equalsIgnoreCase(Build.MODEL);
+    }
+}
diff --git a/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.kt b/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.kt
index f37ef2d0e..bc3c951 100644
--- a/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.kt
+++ b/camera/camera-view/src/test/java/androidx/camera/view/PreviewTransformationTest.kt
@@ -35,6 +35,7 @@
 import org.robolectric.RobolectricTestRunner
 import org.robolectric.annotation.Config
 import org.robolectric.annotation.internal.DoNotInstrument
+import org.robolectric.util.ReflectionHelpers
 import kotlin.math.roundToInt
 
 // Size of the PreviewView. Aspect ratio 2:1.
@@ -71,19 +72,19 @@
 @RunWith(RobolectricTestRunner::class)
 @DoNotInstrument
 @Config(minSdk = Build.VERSION_CODES.LOLLIPOP)
-public class PreviewTransformationTest {
+class PreviewTransformationTest {
 
     private lateinit var mPreviewTransform: PreviewTransformation
     private lateinit var mView: View
 
     @Before
-    public fun setUp() {
+    fun setUp() {
         mPreviewTransform = PreviewTransformation()
         mView = View(ApplicationProvider.getApplicationContext())
     }
 
     @Test
-    public fun withPreviewStretchedQuirk_cropRectIsAdjusted() {
+    fun withPreviewStretchedQuirk_cropRectIsAdjusted() {
         // Arrange.
         QuirkInjector.inject(PreviewOneThirdWiderQuirk())
 
@@ -100,7 +101,7 @@
     }
 
     @Test
-    public fun cropRectWidthOffByOnePixel_match() {
+    fun cropRectWidthOffByOnePixel_match() {
         assertThat(
             isCropRectAspectRatioMatchPreviewView(
                 Rect(
@@ -114,7 +115,7 @@
     }
 
     @Test
-    public fun cropRectWidthOffByTwoPixels_mismatch() {
+    fun cropRectWidthOffByTwoPixels_mismatch() {
         assertThat(
             isCropRectAspectRatioMatchPreviewView(
                 Rect(
@@ -138,7 +139,43 @@
     }
 
     @Test
-    public fun correctTextureViewWith0Rotation() {
+    fun fairphone2BackCamera_noCorrection() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Fairphone")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "FP2")
+        assertThat(getTextureViewCorrection(Surface.ROTATION_0, BACK_CAMERA)).isEqualTo(
+            intArrayOf(
+                0,
+                0,
+                SURFACE_SIZE.width,
+                0,
+                SURFACE_SIZE.width,
+                SURFACE_SIZE.height,
+                0,
+                SURFACE_SIZE.height
+            )
+        )
+    }
+
+    @Test
+    fun fairphone2BackCamera_corrected() {
+        ReflectionHelpers.setStaticField(Build::class.java, "MANUFACTURER", "Fairphone")
+        ReflectionHelpers.setStaticField(Build::class.java, "MODEL", "FP2")
+        assertThat(getTextureViewCorrection(Surface.ROTATION_0, FRONT_CAMERA)).isEqualTo(
+            intArrayOf(
+                SURFACE_SIZE.width,
+                SURFACE_SIZE.height,
+                0,
+                SURFACE_SIZE.height,
+                0,
+                0,
+                SURFACE_SIZE.width,
+                0
+            )
+        )
+    }
+
+    @Test
+    fun correctTextureViewWith0Rotation() {
         assertThat(getTextureViewCorrection(Surface.ROTATION_0)).isEqualTo(
             intArrayOf(
                 0,
@@ -154,7 +191,7 @@
     }
 
     @Test
-    public fun correctTextureViewWith90Rotation() {
+    fun correctTextureViewWith90Rotation() {
         assertThat(getTextureViewCorrection(Surface.ROTATION_90)).isEqualTo(
             intArrayOf(
                 0,
@@ -170,7 +207,7 @@
     }
 
     @Test
-    public fun correctTextureViewWith180Rotation() {
+    fun correctTextureViewWith180Rotation() {
         assertThat(getTextureViewCorrection(Surface.ROTATION_180)).isEqualTo(
             intArrayOf(
                 SURFACE_SIZE.width,
@@ -186,7 +223,7 @@
     }
 
     @Test
-    public fun correctTextureViewWith270Rotation() {
+    fun correctTextureViewWith270Rotation() {
         assertThat(getTextureViewCorrection(Surface.ROTATION_270)).isEqualTo(
             intArrayOf(
                 SURFACE_SIZE.width,
@@ -201,15 +238,22 @@
         )
     }
 
+    private fun getTextureViewCorrection(@RotationValue rotation: Int): IntArray {
+        return getTextureViewCorrection(rotation, BACK_CAMERA)
+    }
+
     /**
      * Corrects TextureView based on target rotation and return the corrected vertices.
      */
-    private fun getTextureViewCorrection(@RotationValue rotation: Int): IntArray {
+    private fun getTextureViewCorrection(
+        @RotationValue rotation: Int,
+        isFrontCamera: Boolean
+    ): IntArray {
         // Arrange.
         mPreviewTransform.setTransformationInfo(
             SurfaceRequest.TransformationInfo.of(CROP_RECT, 90, rotation),
             SURFACE_SIZE,
-            BACK_CAMERA
+            isFrontCamera
         )
 
         // Act.
@@ -220,18 +264,16 @@
 
     private fun convertToIntArray(elements: FloatArray): IntArray {
         var result = IntArray(elements.size)
-        var index = 0
 
-        for (element in elements) {
-            result.set(index, element.roundToInt())
-            index++
+        for ((index, element) in elements.withIndex()) {
+            result[index] = element.roundToInt()
         }
 
         return result
     }
 
     @Test
-    public fun ratioMatch_surfaceIsScaledToFillPreviewView() {
+    fun ratioMatch_surfaceIsScaledToFillPreviewView() {
         // Arrange.
         mPreviewTransform.setTransformationInfo(
             SurfaceRequest.TransformationInfo.of(
@@ -260,7 +302,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fitStart() {
+    fun mismatchedCropRect_fitStart() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FIT_START,
             LayoutDirection.LTR,
@@ -272,7 +314,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fitCenter() {
+    fun mismatchedCropRect_fitCenter() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FIT_CENTER,
             LayoutDirection.LTR,
@@ -284,7 +326,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fitEnd() {
+    fun mismatchedCropRect_fitEnd() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FIT_END,
             LayoutDirection.LTR,
@@ -296,7 +338,7 @@
     }
 
     @Test
-    public fun mismatchedCropRectFrontCamera_fitStart() {
+    fun mismatchedCropRectFrontCamera_fitStart() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FIT_START,
             LayoutDirection.LTR,
@@ -308,7 +350,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fillStart() {
+    fun mismatchedCropRect_fillStart() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FILL_START,
             LayoutDirection.LTR,
@@ -320,7 +362,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fillCenter() {
+    fun mismatchedCropRect_fillCenter() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FILL_CENTER,
             LayoutDirection.LTR,
@@ -332,7 +374,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fillEnd() {
+    fun mismatchedCropRect_fillEnd() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FILL_END,
             LayoutDirection.LTR,
@@ -344,7 +386,7 @@
     }
 
     @Test
-    public fun mismatchedCropRect_fitStartWithRtl_actsLikeFitEnd() {
+    fun mismatchedCropRect_fitStartWithRtl_actsLikeFitEnd() {
         assertForMismatchedCropRect(
             PreviewView.ScaleType.FIT_START,
             LayoutDirection.RTL,
@@ -382,7 +424,7 @@
     }
 
     @Test
-    public fun frontCamera0_transformationIsMirrored() {
+    fun frontCamera0_transformationIsMirrored() {
         testOffCenterCropRectMirroring(FRONT_CAMERA, CROP_RECT_0, PREVIEW_VIEW_SIZE, 0)
 
         // Assert:
@@ -393,7 +435,7 @@
     }
 
     @Test
-    public fun backCamera0_transformationIsNotMirrored() {
+    fun backCamera0_transformationIsNotMirrored() {
         testOffCenterCropRectMirroring(BACK_CAMERA, CROP_RECT_0, PREVIEW_VIEW_SIZE, 0)
 
         // Assert:
@@ -404,7 +446,7 @@
     }
 
     @Test
-    public fun frontCameraRotated90_transformationIsMirrored() {
+    fun frontCameraRotated90_transformationIsMirrored() {
         testOffCenterCropRectMirroring(
             FRONT_CAMERA, CROP_RECT_90, PIVOTED_PREVIEW_VIEW_SIZE, 90
         )
@@ -417,7 +459,7 @@
     }
 
     @Test
-    public fun previewViewSizeIs0_noOps() {
+    fun previewViewSizeIs0_noOps() {
         testOffCenterCropRectMirroring(
             FRONT_CAMERA, CROP_RECT_90, Size(0, 0), 90
         )
@@ -430,7 +472,7 @@
     }
 
     @Test
-    public fun backCameraRotated90_transformationIsNotMirrored() {
+    fun backCameraRotated90_transformationIsNotMirrored() {
         testOffCenterCropRectMirroring(BACK_CAMERA, CROP_RECT_90, PIVOTED_PREVIEW_VIEW_SIZE, 90)
 
         // Assert:
diff --git a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
index 811daab..06b2eff 100644
--- a/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
+++ b/camera/integration-tests/coretestapp/src/main/java/androidx/camera/integration/core/CameraXActivity.java
@@ -21,6 +21,7 @@
 import static androidx.camera.core.ImageCapture.FLASH_MODE_ON;
 
 import android.Manifest;
+import android.annotation.SuppressLint;
 import android.content.ContentResolver;
 import android.content.ContentValues;
 import android.content.Context;
@@ -351,6 +352,7 @@
         });
     }
 
+    @SuppressLint("MissingPermission")
     private void setUpRecordButton() {
         mRecordUi.getButtonRecord().setOnClickListener((view) -> {
             RecordUi.State state = mRecordUi.getState();
@@ -359,6 +361,7 @@
                     createDefaultVideoFolderIfNotExist();
                     mActiveRecording = getVideoCapture().getOutput()
                             .prepareRecording(getNewVideoOutputFileOptions())
+                            .withAudioEnabled()
                             .withEventListener(ContextCompat.getMainExecutor(CameraXActivity.this),
                                     mVideoRecordEventListener)
                             .start();
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
index 264be0c..9d70074 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppActivity.java
@@ -205,6 +205,7 @@
                 serviceComponentName);
         mViewModel = new ViewModelProvider(this, factory).get(CarAppViewModel.class);
         mViewModel.setActivity(this);
+        mViewModel.resetState();
         mViewModel.getError().observe(this, this::onErrorChanged);
         mViewModel.getState().observe(this, this::onStateChanged);
 
@@ -239,43 +240,50 @@
     }
 
     private void onErrorChanged(@Nullable ErrorHandler.ErrorType errorType) {
-        mErrorMessageView.setError(errorType);
+        ThreadUtils.runOnMain(() -> {
+            mErrorMessageView.setError(errorType);
+        });
     }
 
     private void onStateChanged(@NonNull CarAppViewModel.State state) {
-        requireNonNull(mSurfaceView);
-        requireNonNull(mSurfaceHolderListener);
+        ThreadUtils.runOnMain(() -> {
+            requireNonNull(mSurfaceView);
+            requireNonNull(mSurfaceHolderListener);
 
-        switch (state) {
-            case IDLE:
-                mSurfaceView.setVisibility(View.GONE);
-                mSurfaceHolderListener.setSurfaceListener(null);
-                mErrorMessageView.setVisibility(View.GONE);
-                mLoadingView.setVisibility(View.GONE);
-                break;
-            case ERROR:
-                mSurfaceView.setVisibility(View.GONE);
-                mSurfaceHolderListener.setSurfaceListener(null);
-                mErrorMessageView.setVisibility(View.VISIBLE);
-                mLoadingView.setVisibility(View.GONE);
-                break;
-            case CONNECTING:
-                mSurfaceView.setVisibility(View.GONE);
-                mSurfaceHolderListener.setSurfaceListener(null);
-                mErrorMessageView.setVisibility(View.GONE);
-                mLoadingView.setVisibility(View.VISIBLE);
-                break;
-            case CONNECTED:
-                mSurfaceView.setVisibility(View.VISIBLE);
-                mErrorMessageView.setVisibility(View.GONE);
-                mLoadingView.setVisibility(View.GONE);
-                break;
-        }
+            switch (state) {
+                case IDLE:
+                    mSurfaceView.setVisibility(View.GONE);
+                    mSurfaceHolderListener.setSurfaceListener(null);
+                    mErrorMessageView.setVisibility(View.GONE);
+                    mLoadingView.setVisibility(View.GONE);
+                    break;
+                case ERROR:
+                    mSurfaceView.setVisibility(View.GONE);
+                    mSurfaceHolderListener.setSurfaceListener(null);
+                    mErrorMessageView.setVisibility(View.VISIBLE);
+                    mLoadingView.setVisibility(View.GONE);
+                    break;
+                case CONNECTING:
+                    mSurfaceView.setVisibility(View.GONE);
+                    mErrorMessageView.setVisibility(View.GONE);
+                    mLoadingView.setVisibility(View.VISIBLE);
+                    break;
+                case CONNECTED:
+                    mSurfaceView.setVisibility(View.VISIBLE);
+                    mErrorMessageView.setVisibility(View.GONE);
+                    mLoadingView.setVisibility(View.GONE);
+                    break;
+            }
+        });
     }
 
     @Override
     protected void onNewIntent(@NonNull Intent intent) {
         super.onNewIntent(intent);
+
+        requireNonNull(mSurfaceHolderListener).setSurfaceListener(null);
+        requireNonNull(mActivityLifecycleDelegate).registerRendererCallback(null);
+
         requireNonNull(mViewModel).bind(intent, mCarActivity, getDisplayId());
     }
 
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
index ad3f433..7011f44 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/activity/CarAppViewModel.java
@@ -93,11 +93,18 @@
         mIRendererCallback = rendererCallback;
     }
 
-    /** Updates the activity hosting this view model */
+    /** Updates the activity hosting this view model. */
     void setActivity(@Nullable Activity activity) {
         sActivity = new WeakReference<>(activity);
     }
 
+    /** Resets the internal state of this view model. */
+    @SuppressWarnings("NullAway")
+    void resetState() {
+        mState.setValue(State.IDLE);
+        mError.setValue(null);
+    }
+
     /**
      * Binds to the renderer service and initializes the service if not bound already.
      *
@@ -107,8 +114,8 @@
     @SuppressWarnings("NullAway")
     void bind(@NonNull Intent intent, @NonNull ICarAppActivity iCarAppActivity,
             int displayId) {
-        mState.postValue(State.CONNECTING);
-        mError.postValue(null);
+        mState.setValue(State.CONNECTING);
+        mError.setValue(null);
         mServiceConnectionManager.bind(intent, iCarAppActivity, displayId);
     }
 
@@ -123,7 +130,7 @@
         if (mIRendererCallback != null) {
             getServiceDispatcher().dispatch("onDestroyed", mIRendererCallback::onDestroyed);
         }
-        mState.postValue(State.IDLE);
+        mState.setValue(State.IDLE);
         unbind();
     }
 
@@ -158,9 +165,9 @@
                 // displayed to the user.
                 return;
             }
-            mError.postValue(errorCode);
+            mState.setValue(State.ERROR);
+            mError.setValue(errorCode);
         });
-        mState.postValue(State.ERROR);
         unbind();
     }
 
@@ -170,16 +177,15 @@
     @SuppressWarnings("NullAway")
     @Override
     public void onConnect() {
-        mState.postValue(State.CONNECTED);
-        mError.postValue(null);
+        mState.setValue(State.CONNECTED);
+        mError.setValue(null);
     }
 
     /** Attempts to rebind to the host service */
     @SuppressWarnings("NullAway")
     public void retryBinding() {
         Activity activity = requireNonNull(sActivity.get());
-        mState.postValue(State.CONNECTING);
-        mError.postValue(null);
+        mError.setValue(null);
         activity.recreate();
     }
 
diff --git a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
index 1d13783..d0013db 100644
--- a/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
+++ b/car/app/app-automotive/src/main/java/androidx/car/app/hardware/info/AutomotiveCarInfo.java
@@ -34,10 +34,13 @@
 import static java.util.Objects.requireNonNull;
 
 import android.car.VehiclePropertyIds;
+import android.os.Build;
 import android.util.Log;
 
+import androidx.annotation.DoNotInline;
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
 import androidx.annotation.VisibleForTesting;
 import androidx.car.app.hardware.common.CarPropertyResponse;
@@ -83,13 +86,12 @@
     public static final int TOLL_CARD_STATUS_ID = 289410874;
 
     // VEHICLE_SPEED_DISPLAY_UNIT in VehiclePropertyIds. The property is added after Android Q.
-    // TODO(192383417) check the compile SDK
     public static final int SPEED_DISPLAY_UNIT_ID = 289408516;
 
     private static final float UNKNOWN_CAPACITY = Float.NEGATIVE_INFINITY;
     private static final List<Integer> MILEAGE_REQUEST =
             Arrays.asList(PERF_ODOMETER, DISTANCE_DISPLAY_UNITS);
-    private static final List<Integer> TOLL_REQUEST =
+    static final List<Integer> TOLL_REQUEST =
             Collections.singletonList(TOLL_CARD_STATUS_ID);
     private static final List<Integer> SPEED_REQUEST =
             Arrays.asList(VehiclePropertyIds.PERF_VEHICLE_SPEED,
@@ -140,15 +142,25 @@
     @Override
     public void addTollListener(@NonNull Executor executor,
             @NonNull OnCarDataAvailableListener<TollCard> listener) {
-        TollListener tollListener = new TollListener(listener, executor);
-        mPropertyManager.submitRegisterListenerRequest(TOLL_REQUEST, DEFAULT_SAMPLE_RATE,
-                tollListener, executor);
-        mListenerMap.put(listener, tollListener);
+        if (Build.VERSION.SDK_INT > 30) {
+            Api31Impl.addTollListener(executor, listener, mPropertyManager, mListenerMap);
+        } else {
+            TollCard unimplementedTollCard = new TollCard.Builder()
+                    .setCardState(CarValue.UNIMPLEMENTED_INTEGER).build();
+            executor.execute(() -> listener.onCarDataAvailable(unimplementedTollCard));
+        }
     }
 
     @Override
     public void removeTollListener(@NonNull OnCarDataAvailableListener<TollCard> listener) {
-        removeListenerImpl(listener);
+        OnCarPropertyResponseListener responseListener = mListenerMap.remove(listener);
+        if (responseListener == null) {
+            Log.d(LogTags.TAG_CAR_HARDWARE, "Listener is not registered yet");
+            return;
+        }
+        if (Build.VERSION.SDK_INT > 30) {
+            Api31Impl.removeTollListener(responseListener, mPropertyManager);
+        }
     }
 
     @Override
@@ -332,6 +344,25 @@
         }, executor);
     }
 
+    @RequiresApi(31)
+    private static class Api31Impl {
+        @DoNotInline
+        static void addTollListener(Executor executor,
+                OnCarDataAvailableListener<TollCard> listener, PropertyManager propertyManager,
+                Map<OnCarDataAvailableListener<?>, OnCarPropertyResponseListener> listenerMap) {
+            TollListener tollListener = new TollListener(listener, executor);
+            propertyManager.submitRegisterListenerRequest(TOLL_REQUEST, DEFAULT_SAMPLE_RATE,
+                    tollListener, executor);
+            listenerMap.put(listener, tollListener);
+        }
+
+        @DoNotInline
+        static void removeTollListener(OnCarPropertyResponseListener listener,
+                PropertyManager propertyManager) {
+            propertyManager.submitUnregisterListenerRequest(listener);
+        }
+    }
+
     private void removeListenerImpl(OnCarDataAvailableListener<?> listener) {
         OnCarPropertyResponseListener responseListener = mListenerMap.remove(listener);
         if (responseListener != null) {
@@ -515,14 +546,14 @@
                             case EV_BATTERY_LEVEL:
                                 if (mEvBatteryCapacity != Float.NEGATIVE_INFINITY) {
                                     batteryPercentValue = new CarValue<>(
-                                            (Float) response.getValue() / mEvBatteryCapacity,
+                                            (Float) response.getValue() / mEvBatteryCapacity * 100,
                                             response.getTimestampMillis(), response.getStatus());
                                 }
                                 break;
                             case FUEL_LEVEL:
                                 if (mFuelCapacity != Float.NEGATIVE_INFINITY) {
                                     fuelPercentValue = new CarValue<>(
-                                            (Float) response.getValue() / mFuelCapacity,
+                                            (Float) response.getValue() / mFuelCapacity * 100,
                                             response.getTimestampMillis(), response.getStatus());
                                 }
                                 break;
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
index 1435075..a633647 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppActivityTest.java
@@ -262,10 +262,10 @@
                     verify(rendererCallback, times(1)).onBackPressed();
 
                     // Verify focus request sent to host.
-                    activity.mSurfaceView.requestFocus();
-                    verify(callback, times(1)).onWindowFocusChanged(true, false);
                     activity.mSurfaceView.clearFocus();
                     verify(callback, times(1)).onWindowFocusChanged(false, false);
+                    activity.mSurfaceView.requestFocus();
+                    verify(callback, times(1)).onWindowFocusChanged(true, false);
 
                     long downTime = SystemClock.uptimeMillis();
                     long eventTime = SystemClock.uptimeMillis();
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
index 7c80e2c..9a2a52e 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/activity/CarAppViewModelTest.java
@@ -131,7 +131,7 @@
         mMainLooper.idle();
 
         assertThat(mCarAppViewModel.getState().getValue())
-                .isEqualTo(CarAppViewModel.State.CONNECTING);
+                .isEqualTo(CarAppViewModel.State.IDLE);
         assertThat(mCarAppViewModel.getError().getValue()).isNull();
     }
 }
diff --git a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
index aeb1972..800c110 100644
--- a/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
+++ b/car/app/app-automotive/src/test/java/androidx/car/app/hardware/info/AutomotiveCarInfoTest.java
@@ -52,6 +52,7 @@
 
 import androidx.car.app.hardware.common.CarPropertyResponse;
 import androidx.car.app.hardware.common.CarUnit;
+import androidx.car.app.hardware.common.CarValue;
 import androidx.car.app.hardware.common.OnCarDataAvailableListener;
 import androidx.car.app.hardware.common.OnCarPropertyResponseListener;
 import androidx.car.app.hardware.common.PropertyManager;
@@ -197,8 +198,9 @@
         assertThat(mileage.getDistanceDisplayUnit().getValue()).isEqualTo(2);
     }
 
+    @Config(minSdk = 31)
     @Test
-    public void getTollCard_verifyResponse() throws InterruptedException {
+    public void getTollCard_verifyResponseApi31() throws InterruptedException {
         AtomicReference<TollCard> loadedResult = new AtomicReference<>();
         OnCarDataAvailableListener<TollCard> listener = (data) -> {
             loadedResult.set(data);
@@ -222,6 +224,20 @@
         assertThat(tollCard.getCardState().getValue()).isEqualTo(TollCard.TOLLCARD_STATE_VALID);
     }
 
+    @Config(minSdk = 30)
+    @Test
+    public void getTollCard_verifyResponseApi30() {
+        AtomicReference<TollCard> loadedResult = new AtomicReference<>();
+        OnCarDataAvailableListener<TollCard> listener = (data) -> {
+            loadedResult.set(data);
+            mCountDownLatch.countDown();
+        };
+        mAutomotiveCarInfo.addTollListener(mExecutor, listener);
+
+        TollCard tollCard = loadedResult.get();
+        assertThat(tollCard.getCardState().getStatus()).isEqualTo(CarValue.STATUS_UNIMPLEMENTED);
+    }
+
     @Test
     public void getSpeed_verifyResponse() throws InterruptedException {
         float defaultSpeed = 20f;
@@ -263,12 +279,15 @@
     public void getEnergyLevel_verifyResponse() throws InterruptedException {
         ArgumentCaptor<OnCarPropertyResponseListener> captor = ArgumentCaptor.forClass(
                 OnCarPropertyResponseListener.class);
-
+        float evBatteryCapacity = 100f;
+        float evBatteryLevelValue = 50f;
+        float fuelCapacity = 120f;
+        float fuelLevelValue = 50f;
         List<CarPropertyResponse<?>> capacities = new ArrayList<>();
         capacities.add(CarPropertyResponse.create(INFO_EV_BATTERY_CAPACITY,
-                STATUS_SUCCESS, 1, 2f));
+                STATUS_SUCCESS, 1, evBatteryCapacity));
         capacities.add(CarPropertyResponse.create(INFO_FUEL_CAPACITY,
-                STATUS_SUCCESS, 1, 3f));
+                STATUS_SUCCESS, 1, fuelCapacity));
         ListenableFuture<List<CarPropertyResponse<?>>> future =
                 Futures.immediateFuture(capacities);
         when(mPropertyManager.submitGetPropertyRequest(any(), any())).thenReturn(future);
@@ -286,9 +305,9 @@
                 eq(DEFAULT_SAMPLE_RATE), captor.capture(), any());
 
         mResponse.add(CarPropertyResponse.create(EV_BATTERY_LEVEL,
-                STATUS_SUCCESS, 1, 4f));
+                STATUS_SUCCESS, 1, evBatteryLevelValue));
         mResponse.add(CarPropertyResponse.create(FUEL_LEVEL,
-                STATUS_SUCCESS, 1, 6f));
+                STATUS_SUCCESS, 1, fuelLevelValue));
         mResponse.add(CarPropertyResponse.create(FUEL_LEVEL_LOW,
                 STATUS_SUCCESS, 1, true));
         mResponse.add(CarPropertyResponse.create(RANGE_REMAINING,
@@ -300,9 +319,9 @@
 
         EnergyLevel energyLevel = loadedResult.get();
         assertThat(energyLevel.getBatteryPercent().getValue()).isEqualTo(
-                2f);
+                evBatteryLevelValue / evBatteryCapacity * 100);
         assertThat(energyLevel.getFuelPercent().getValue()).isEqualTo(
-                2f);
+                fuelLevelValue / fuelCapacity * 100);
         assertThat(energyLevel.getEnergyIsLow().getValue()).isEqualTo(
                 true);
         assertThat(energyLevel.getRangeRemainingMeters().getValue()).isEqualTo(
diff --git a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
index 1c79b24..1a2c9bb 100644
--- a/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/helloworld/automotive/src/main/AndroidManifest.xml
@@ -20,7 +20,22 @@
     android:versionCode="1"
     android:versionName="1.0">
 
-    <uses-feature android:name="android.software.car.templates_host" />
+    <!-- Various required feature settings for an automotive app. -->
+    <uses-feature
+        android:name="android.hardware.type.automotive"
+        android:required="true" />
+    <uses-feature
+        android:name="android.software.car.templates_host"
+        android:required="true" />
+    <uses-feature
+        android:name="android.hardware.wifi"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.screen.portrait"
+        android:required="false" />
+    <uses-feature
+        android:name="android.hardware.screen.landscape"
+        android:required="false" />
 
     <application
       android:label="@string/app_name"
diff --git a/car/app/app-samples/helloworld/common/src/test/java/androidx/car/app/sample/helloworld/common/HelloWorldSessionTest.java b/car/app/app-samples/helloworld/common/src/test/java/androidx/car/app/sample/helloworld/common/HelloWorldSessionTest.java
index 9679e5c..f8aaac63f 100644
--- a/car/app/app-samples/helloworld/common/src/test/java/androidx/car/app/sample/helloworld/common/HelloWorldSessionTest.java
+++ b/car/app/app-samples/helloworld/common/src/test/java/androidx/car/app/sample/helloworld/common/HelloWorldSessionTest.java
@@ -50,7 +50,7 @@
     public void onCreateScreen_returnsExpectedScreen() {
         HelloWorldService service = Robolectric.setupService(HelloWorldService.class);
         Session session = service.onCreateSession();
-        SessionController controller = SessionController.of(session, mTestCarContext);
+        SessionController controller = new SessionController(session, mTestCarContext);
 
         controller.create(new Intent().setComponent(
                 new ComponentName(mTestCarContext, HelloWorldService.class)));
diff --git a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
index ca1f203..725f8d1 100644
--- a/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/navigation/automotive/src/main/AndroidManifest.xml
@@ -28,7 +28,22 @@
   <uses-permission android:name="androidx.car.app.ACCESS_SURFACE"/>
   <uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
 
-  <uses-feature android:name="android.software.car.templates_host" />
+  <!-- Various required feature settings for an automotive app. -->
+  <uses-feature
+      android:name="android.hardware.type.automotive"
+      android:required="true" />
+  <uses-feature
+      android:name="android.software.car.templates_host"
+      android:required="true" />
+  <uses-feature
+      android:name="android.hardware.wifi"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.portrait"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.landscape"
+      android:required="false" />
 
   <application
     android:label="@string/app_name"
diff --git a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
index dd07ac6..5add5d2 100644
--- a/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/places/automotive/src/main/AndroidManifest.xml
@@ -27,7 +27,22 @@
   <!-- For PlaceListMapTemplate -->
   <uses-permission android:name="androidx.car.app.MAP_TEMPLATES"/>
 
-  <uses-feature android:name="android.software.car.templates_host" />
+  <!-- Various required feature settings for an automotive app. -->
+  <uses-feature
+      android:name="android.hardware.type.automotive"
+      android:required="true" />
+  <uses-feature
+      android:name="android.software.car.templates_host"
+      android:required="true" />
+  <uses-feature
+      android:name="android.hardware.wifi"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.portrait"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.landscape"
+      android:required="false" />
 
   <application
       android:label="@string/app_name"
diff --git a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
index 3fd2ff87..6c2e940 100644
--- a/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
+++ b/car/app/app-samples/showcase/automotive/src/main/AndroidManifest.xml
@@ -41,7 +41,22 @@
   <uses-permission android:name="android.car.permission.READ_CAR_DISPLAY_UNITS"/>
   <uses-permission android:name="android.car.permission.CAR_ENERGY_PORTS"/>
 
-  <uses-feature android:name="android.software.car.templates_host" />
+  <!-- Various required feature settings for an automotive app. -->
+  <uses-feature
+      android:name="android.hardware.type.automotive"
+      android:required="true" />
+  <uses-feature
+      android:name="android.software.car.templates_host"
+      android:required="true" />
+  <uses-feature
+      android:name="android.hardware.wifi"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.portrait"
+      android:required="false" />
+  <uses-feature
+      android:name="android.hardware.screen.landscape"
+      android:required="false" />
 
   <application
       android:label="@string/app_name"
diff --git a/car/app/app-testing/api/current.txt b/car/app/app-testing/api/current.txt
index 7c69ce0..fa317ae 100644
--- a/car/app/app-testing/api/current.txt
+++ b/car/app/app-testing/api/current.txt
@@ -6,12 +6,12 @@
   }
 
   public class ScreenController {
+    ctor public ScreenController(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController create();
     method public androidx.car.app.testing.ScreenController destroy();
-    method public androidx.car.app.Screen get();
+    method public androidx.car.app.Screen getScreen();
     method public Object? getScreenResult();
     method public java.util.List<androidx.car.app.model.Template!> getTemplatesReturned();
-    method public static androidx.car.app.testing.ScreenController of(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController pause();
     method public void reset();
     method public androidx.car.app.testing.ScreenController resume();
@@ -20,10 +20,10 @@
   }
 
   public class SessionController {
+    ctor public SessionController(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
     method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
-    method public androidx.car.app.Session get();
-    method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
+    method public androidx.car.app.Session getSession();
     method public androidx.car.app.testing.SessionController pause();
     method public androidx.car.app.testing.SessionController resume();
     method public androidx.car.app.testing.SessionController start();
diff --git a/car/app/app-testing/api/public_plus_experimental_current.txt b/car/app/app-testing/api/public_plus_experimental_current.txt
index 7c69ce0..fa317ae 100644
--- a/car/app/app-testing/api/public_plus_experimental_current.txt
+++ b/car/app/app-testing/api/public_plus_experimental_current.txt
@@ -6,12 +6,12 @@
   }
 
   public class ScreenController {
+    ctor public ScreenController(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController create();
     method public androidx.car.app.testing.ScreenController destroy();
-    method public androidx.car.app.Screen get();
+    method public androidx.car.app.Screen getScreen();
     method public Object? getScreenResult();
     method public java.util.List<androidx.car.app.model.Template!> getTemplatesReturned();
-    method public static androidx.car.app.testing.ScreenController of(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController pause();
     method public void reset();
     method public androidx.car.app.testing.ScreenController resume();
@@ -20,10 +20,10 @@
   }
 
   public class SessionController {
+    ctor public SessionController(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
     method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
-    method public androidx.car.app.Session get();
-    method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
+    method public androidx.car.app.Session getSession();
     method public androidx.car.app.testing.SessionController pause();
     method public androidx.car.app.testing.SessionController resume();
     method public androidx.car.app.testing.SessionController start();
diff --git a/car/app/app-testing/api/restricted_current.txt b/car/app/app-testing/api/restricted_current.txt
index 7c69ce0..fa317ae 100644
--- a/car/app/app-testing/api/restricted_current.txt
+++ b/car/app/app-testing/api/restricted_current.txt
@@ -6,12 +6,12 @@
   }
 
   public class ScreenController {
+    ctor public ScreenController(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController create();
     method public androidx.car.app.testing.ScreenController destroy();
-    method public androidx.car.app.Screen get();
+    method public androidx.car.app.Screen getScreen();
     method public Object? getScreenResult();
     method public java.util.List<androidx.car.app.model.Template!> getTemplatesReturned();
-    method public static androidx.car.app.testing.ScreenController of(androidx.car.app.testing.TestCarContext, androidx.car.app.Screen);
     method public androidx.car.app.testing.ScreenController pause();
     method public void reset();
     method public androidx.car.app.testing.ScreenController resume();
@@ -20,10 +20,10 @@
   }
 
   public class SessionController {
+    ctor public SessionController(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
     method public androidx.car.app.testing.SessionController create(android.content.Intent);
     method public androidx.car.app.testing.SessionController destroy();
-    method public androidx.car.app.Session get();
-    method public static androidx.car.app.testing.SessionController of(androidx.car.app.Session, androidx.car.app.testing.TestCarContext);
+    method public androidx.car.app.Session getSession();
     method public androidx.car.app.testing.SessionController pause();
     method public androidx.car.app.testing.SessionController resume();
     method public androidx.car.app.testing.SessionController start();
diff --git a/car/app/app-testing/src/main/java/androidx/car/app/testing/ScreenController.java b/car/app/app-testing/src/main/java/androidx/car/app/testing/ScreenController.java
index 9812453..8ac67da 100644
--- a/car/app/app-testing/src/main/java/androidx/car/app/testing/ScreenController.java
+++ b/car/app/app-testing/src/main/java/androidx/car/app/testing/ScreenController.java
@@ -54,15 +54,30 @@
      *
      * @throws NullPointerException if either {@code testCarContext} or {@code screen} are null
      */
+    public ScreenController(@NonNull TestCarContext testCarContext, @NonNull Screen screen) {
+        mScreen = requireNonNull(screen);
+        mTestCarContext = requireNonNull(testCarContext);
+
+        // Use reflection to inject the TestCarContext into the Screen.
+        try {
+            Field field = Screen.class.getDeclaredField("mCarContext");
+            field.setAccessible(true);
+            field.set(screen, testCarContext);
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException("Failed to set a test car context for testing", e);
+        }
+    }
+
+    /** Returns the {@link Screen} being controlled. */
     @NonNull
-    public static ScreenController of(
-            @NonNull TestCarContext testCarContext, @NonNull Screen screen) {
-        return new ScreenController(requireNonNull(screen), requireNonNull(testCarContext));
+    public Screen getScreen() {
+        return mScreen;
     }
 
     /** Resets values tracked by this {@link ScreenController}. */
     public void reset() {
-        mTestCarContext.getCarService(TestAppManager.class).resetTemplatesStoredForScreen(get());
+        mTestCarContext.getCarService(TestAppManager.class).resetTemplatesStoredForScreen(
+                getScreen());
     }
 
     /**
@@ -80,7 +95,7 @@
         List<Template> templates = new ArrayList<>();
         for (Pair<Screen, Template> pair :
                 mTestCarContext.getCarService(TestAppManager.class).getTemplatesReturned()) {
-            if (pair.first == get()) {
+            if (pair.first == getScreen()) {
                 templates.add(pair.second);
             }
         }
@@ -182,12 +197,6 @@
         return this;
     }
 
-    /** Returns the {@link Screen} being controlled. */
-    @NonNull
-    public Screen get() {
-        return mScreen;
-    }
-
     private void putScreenOnStackIfNotTop() {
         TestScreenManager testScreenManager = mTestCarContext.getCarService(
                 TestScreenManager.class);
@@ -196,20 +205,6 @@
         }
     }
 
-    private ScreenController(Screen screen, TestCarContext testCarContext) {
-        this.mScreen = screen;
-        this.mTestCarContext = testCarContext;
-
-        // Use reflection to inject the TestCarContext into the Screen.
-        try {
-            Field field = Screen.class.getDeclaredField("mCarContext");
-            field.setAccessible(true);
-            field.set(screen, testCarContext);
-        } catch (ReflectiveOperationException e) {
-            throw new IllegalStateException("Failed to set a test car context for testing", e);
-        }
-    }
-
     @SuppressLint("BanUncheckedReflection")
     private void dispatchLifecycleEvent(Event event) {
         // Use reflection to call internal APIs for testing purposes.
diff --git a/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java b/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
index 153290f..fc4d3d8 100644
--- a/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
+++ b/car/app/app-testing/src/main/java/androidx/car/app/testing/SessionController.java
@@ -50,9 +50,29 @@
      * @param context the {@link TestCarContext} that the {@code session} should use.
      * @throws NullPointerException if {@code session} or {@code context} is {@code null}
      */
+    public SessionController(@NonNull Session session, @NonNull TestCarContext context) {
+        mSession = requireNonNull(session);
+        mTestCarContext = requireNonNull(context);
+
+        // Use reflection to inject the TestCarContext into the Session.
+        try {
+            Field registry = Session.class.getDeclaredField("mRegistry");
+            registry.setAccessible(true);
+            registry.set(session, mTestCarContext.getLifecycleOwner().mRegistry);
+
+            Field carContext = Session.class.getDeclaredField("mCarContext");
+            carContext.setAccessible(true);
+            carContext.set(session, mTestCarContext);
+        } catch (ReflectiveOperationException e) {
+            throw new IllegalStateException(
+                    "Failed to set internal Session values for testing", e);
+        }
+    }
+
+    /** Returns the {@link Session} that is being controlled. */
     @NonNull
-    public static SessionController of(@NonNull Session session, @NonNull TestCarContext context) {
-        return new SessionController(requireNonNull(session), requireNonNull(context));
+    public Session getSession() {
+        return mSession;
     }
 
     /**
@@ -147,29 +167,4 @@
 
         return this;
     }
-
-    /** Returns the {@link Session} that is being controlled. */
-    @NonNull
-    public Session get() {
-        return mSession;
-    }
-
-    private SessionController(Session session, TestCarContext context) {
-        mSession = session;
-        mTestCarContext = context;
-
-        // Use reflection to inject the TestCarContext into the Session.
-        try {
-            Field registry = Session.class.getDeclaredField("mRegistry");
-            registry.setAccessible(true);
-            registry.set(session, mTestCarContext.getLifecycleOwner().mRegistry);
-
-            Field carContext = Session.class.getDeclaredField("mCarContext");
-            carContext.setAccessible(true);
-            carContext.set(session, mTestCarContext);
-        } catch (ReflectiveOperationException e) {
-            throw new IllegalStateException(
-                    "Failed to set internal Session values for testing", e);
-        }
-    }
 }
diff --git a/car/app/app-testing/src/test/java/androidx/car/app/testing/ScreenControllerTest.java b/car/app/app-testing/src/test/java/androidx/car/app/testing/ScreenControllerTest.java
index 76f4bc0..b8dfc87 100644
--- a/car/app/app-testing/src/test/java/androidx/car/app/testing/ScreenControllerTest.java
+++ b/car/app/app-testing/src/test/java/androidx/car/app/testing/ScreenControllerTest.java
@@ -64,7 +64,7 @@
         MockitoAnnotations.initMocks(this);
 
         mCarContext = TestCarContext.createCarContext(ApplicationProvider.getApplicationContext());
-        mScreenController = ScreenController.of(mCarContext, mTestScreen);
+        mScreenController = new ScreenController(mCarContext, mTestScreen);
 
         mTestScreen.getLifecycle().addObserver(mMockObserver);
     }
@@ -160,7 +160,7 @@
 
     @Test
     public void getScreenResult() {
-        Screen screen = mScreenController.get();
+        Screen screen = mScreenController.getScreen();
         String result = "this is the result";
 
         screen.setResult(result);
diff --git a/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java b/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
index e2df7e1..ff403f5 100644
--- a/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
+++ b/car/app/app-testing/src/test/java/androidx/car/app/testing/SessionControllerTest.java
@@ -67,7 +67,7 @@
 
         };
 
-        mSessionController = SessionController.of(session, mCarContext);
+        mSessionController = new SessionController(session, mCarContext);
         session.getLifecycle().addObserver(mMockObserver);
     }
 
diff --git a/car/app/app/api/current.txt b/car/app/app/api/current.txt
index 73954f0..c252929 100644
--- a/car/app/app/api/current.txt
+++ b/car/app/app/api/current.txt
@@ -294,7 +294,6 @@
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEnergyIsLow();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getFuelPercent();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemaining();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemainingMeters();
   }
 
@@ -305,7 +304,6 @@
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setEnergyIsLow(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemaining(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemainingMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -354,7 +352,6 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Mileage {
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometer();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometerMeters();
   }
 
@@ -362,7 +359,6 @@
     ctor public Mileage.Builder();
     method public androidx.car.app.hardware.info.Mileage build();
     method public androidx.car.app.hardware.info.Mileage.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
-    method @Deprecated public androidx.car.app.hardware.info.Mileage.Builder setOdometer(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Mileage.Builder setOdometerMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -381,9 +377,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Speed {
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeedMetersPerSecond();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeedMetersPerSecond();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getSpeedDisplayUnit();
   }
@@ -391,9 +385,7 @@
   public static final class Speed.Builder {
     ctor public Speed.Builder();
     method public androidx.car.app.hardware.info.Speed build();
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setRawSpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setRawSpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setSpeedDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
   }
diff --git a/car/app/app/api/public_plus_experimental_current.txt b/car/app/app/api/public_plus_experimental_current.txt
index d10044b..8136625 100644
--- a/car/app/app/api/public_plus_experimental_current.txt
+++ b/car/app/app/api/public_plus_experimental_current.txt
@@ -297,7 +297,6 @@
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEnergyIsLow();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getFuelPercent();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemaining();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemainingMeters();
   }
 
@@ -308,7 +307,6 @@
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setEnergyIsLow(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemaining(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemainingMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -357,7 +355,6 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Mileage {
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometer();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometerMeters();
   }
 
@@ -365,7 +362,6 @@
     ctor public Mileage.Builder();
     method public androidx.car.app.hardware.info.Mileage build();
     method public androidx.car.app.hardware.info.Mileage.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
-    method @Deprecated public androidx.car.app.hardware.info.Mileage.Builder setOdometer(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Mileage.Builder setOdometerMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -384,9 +380,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Speed {
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeedMetersPerSecond();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeedMetersPerSecond();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getSpeedDisplayUnit();
   }
@@ -394,9 +388,7 @@
   public static final class Speed.Builder {
     ctor public Speed.Builder();
     method public androidx.car.app.hardware.info.Speed build();
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setRawSpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setRawSpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setSpeedDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
   }
diff --git a/car/app/app/api/restricted_current.txt b/car/app/app/api/restricted_current.txt
index 73954f0..c252929 100644
--- a/car/app/app/api/restricted_current.txt
+++ b/car/app/app/api/restricted_current.txt
@@ -294,7 +294,6 @@
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Boolean!> getEnergyIsLow();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getFuelPercent();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemaining();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRangeRemainingMeters();
   }
 
@@ -305,7 +304,6 @@
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setEnergyIsLow(androidx.car.app.hardware.common.CarValue<java.lang.Boolean!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setFuelPercent(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemaining(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.EnergyLevel.Builder setRangeRemainingMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -354,7 +352,6 @@
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Mileage {
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getDistanceDisplayUnit();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometer();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getOdometerMeters();
   }
 
@@ -362,7 +359,6 @@
     ctor public Mileage.Builder();
     method public androidx.car.app.hardware.info.Mileage build();
     method public androidx.car.app.hardware.info.Mileage.Builder setDistanceDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
-    method @Deprecated public androidx.car.app.hardware.info.Mileage.Builder setOdometer(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Mileage.Builder setOdometerMeters(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
   }
 
@@ -381,9 +377,7 @@
   }
 
   @androidx.car.app.annotations.CarProtocol @androidx.car.app.annotations.RequiresCarApi(3) public final class Speed {
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getDisplaySpeedMetersPerSecond();
-    method @Deprecated public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeed();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Float!> getRawSpeedMetersPerSecond();
     method public androidx.car.app.hardware.common.CarValue<java.lang.Integer!> getSpeedDisplayUnit();
   }
@@ -391,9 +385,7 @@
   public static final class Speed.Builder {
     ctor public Speed.Builder();
     method public androidx.car.app.hardware.info.Speed build();
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setDisplaySpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
-    method @Deprecated public androidx.car.app.hardware.info.Speed.Builder setRawSpeed(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setRawSpeedMetersPerSecond(androidx.car.app.hardware.common.CarValue<java.lang.Float!>);
     method public androidx.car.app.hardware.info.Speed.Builder setSpeedDisplayUnit(androidx.car.app.hardware.common.CarValue<java.lang.Integer!>);
   }
diff --git a/car/app/app/build.gradle b/car/app/app/build.gradle
index a017570..b6e0f42 100644
--- a/car/app/app/build.gradle
+++ b/car/app/app/build.gradle
@@ -16,6 +16,22 @@
 
 import androidx.build.LibraryGroups
 import androidx.build.LibraryType
+import androidx.build.Release
+import androidx.build.checkapi.LibraryApiTaskConfig
+import androidx.build.metalava.MetalavaRunnerKt
+import androidx.build.uptodatedness.EnableCachingKt
+import androidx.build.Version
+
+import com.android.build.gradle.LibraryExtension
+import com.android.build.gradle.api.AndroidSourceDirectorySet
+import com.android.build.gradle.api.SourceKind
+import com.google.common.io.Files
+import org.apache.commons.io.FileUtils
+
+import java.util.concurrent.TimeUnit
+import javax.inject.Inject
+
+import static androidx.build.dependencies.DependenciesKt.*
 
 plugins {
     id("AndroidXPlugin")
@@ -47,6 +63,10 @@
     testImplementation project(path: ':car:app:app-testing')
 }
 
+project.ext {
+    latestCarAppApiLevel = "3"
+}
+
 android {
     defaultConfig {
         minSdkVersion 23
@@ -74,3 +94,353 @@
     inceptionYear = "2020"
     description = "Build navigation, parking, and charging apps for Android Auto"
 }
+
+// Use MetalavaRunnerKt to execute Metalava operations. MetalavaRunnerKt is defined in the buildSrc
+// project and provides convience methods for interacting with Metalava. Configruation required
+// for MetalavaRunnerKt is taken from buildSrc/src/main/kotlin/androidx/build/checkapi/ApiTasks.kt.
+class ProtocolApiTask extends DefaultTask {
+    private final WorkerExecutor workerExecutor
+
+    @Inject
+    ProtocolApiTask(WorkerExecutor workerExecutor) {
+        this.workerExecutor = workerExecutor
+    }
+
+    @Internal
+    def getLibraryVariant() {
+        LibraryExtension extension = project.extensions.getByType(LibraryExtension.class)
+        LibraryApiTaskConfig config = new LibraryApiTaskConfig(extension)
+        return config.library.libraryVariants.find({
+            it.name == Release.DEFAULT_PUBLISH_CONFIG
+        })
+    }
+
+    @Internal
+    def getLibraryExtension() {
+        return project.extensions.getByType(LibraryExtension.class)
+    }
+
+    @Internal
+    def getSourceDirs() {
+        List<File> sourceDirs = new ArrayList<File>()
+        for (ConfigurableFileTree fileTree : getLibraryVariant().getSourceFolders(SourceKind
+                .JAVA)) {
+            if (fileTree.getDir().exists()) {
+                sourceDirs.add(fileTree.getDir())
+            }
+        }
+        return sourceDirs
+    }
+
+    @Internal
+    def runMetalava(List<String> additionalArgs) {
+        FileCollection metalavaClasspath = MetalavaRunnerKt.getMetalavaClasspath(project)
+        FileCollection dependencyClasspath = getLibraryVariant().getCompileClasspath(null).filter {
+            it.exists()
+        }
+
+        List<File> classpath = new ArrayList<File>()
+        classpath.addAll(getLibraryExtension().bootClasspath)
+        classpath.addAll(dependencyClasspath)
+
+        List<String> standardArgs = [
+                "--classpath",
+                classpath.join(File.pathSeparator),
+                '--source-path',
+                sourceDirs.join(File.pathSeparator),
+                '--format=v4',
+                '--output-kotlin-nulls=yes',
+                '--quiet'
+        ]
+        standardArgs.addAll(additionalArgs)
+
+        MetalavaRunnerKt.runMetalavaWithArgs(
+                metalavaClasspath,
+                standardArgs,
+                workerExecutor,
+        )
+    }
+}
+
+// Use Metalava to generate an API signature file that only includes public API that is annotated
+// with @androidx.car.app.annotations.CarProtocol
+class GenerateProtocolApiTask extends ProtocolApiTask {
+    @Inject
+    GenerateProtocolApiTask(WorkerExecutor workerExecutor) {
+        super(workerExecutor)
+    }
+
+    @InputFiles
+    @PathSensitive(PathSensitivity.RELATIVE)
+    FileCollection inputSourceDirs = project.files() // Re-run on source changes
+
+    @OutputFile
+    File generatedApi
+
+    @TaskAction
+    def exec() {
+        List<String> args = [
+                '--api',
+                generatedApi.toString(),
+                "--show-annotation",
+                "@androidx.car.app.annotations.CarProtocol",
+                "--hide",
+                "UnhiddenSystemApi"
+        ]
+
+        runMetalava(args)
+    }
+}
+
+// Compare two files and throw an exception if they are not equivalent. This task is used to check
+// for diffs to generated Metalava API signature files, which would indicate a protocol API change.
+class CheckProtocolApiTask extends DefaultTask {
+    @InputFile
+    @Optional
+    File currentApi
+
+    @InputFile
+    File generatedApi
+
+    def summarizeDiff(File a, File b) {
+        if (!a.exists()) {
+            return "$a does not exist"
+        }
+        if (!b.exists()) {
+            return "$b does not exist"
+        }
+        Process process = new ProcessBuilder(Arrays.asList("diff", a.toString(), b.toString()))
+                .redirectOutput(ProcessBuilder.Redirect.PIPE)
+                .start()
+        process.waitFor(5, TimeUnit.SECONDS)
+        List<String> diffLines = process.inputStream.newReader().readLines()
+        int maxSummaryLines = 50
+        if (diffLines.size() > maxSummaryLines) {
+            diffLines = diffLines.subList(0, maxSummaryLines)
+            diffLines.add("[long diff was truncated]")
+        }
+        return String.join("\n", diffLines)
+    }
+
+    @TaskAction
+    def exec() {
+        if (currentApi == null) {
+            return
+        }
+
+        if (!FileUtils.contentEquals(currentApi, generatedApi)) {
+            String diff = summarizeDiff(currentApi, generatedApi)
+            String message = """API definition has changed
+
+                    Declared definition is $currentApi
+                    True     definition is $generatedApi
+
+                    Please run `./gradlew updateProtocolApi` to confirm these changes are
+                    intentional by updating the API definition.
+
+                    Difference between these files:
+                    $diff"""
+
+            throw new GradleException("Protocol changes detected!\n$message")
+        }
+    }
+}
+
+// Check for compatibility breaking changes between two Metalava API signature files. This task is
+// used to detect backward-compatibility breaking changes to protocol API.
+class CheckProtocolApiCompatibilityTask extends ProtocolApiTask {
+    @Inject
+    CheckProtocolApiCompatibilityTask(WorkerExecutor workerExecutor) {
+        super(workerExecutor)
+    }
+
+    @InputFile
+    @Optional
+    File previousApi
+
+    @InputFile
+    @Optional
+    File generatedApi
+
+    @TaskAction
+    def exec() {
+        if (previousApi == null || generatedApi == null) {
+            return
+        }
+
+        List<String> args = [
+                '--source-files',
+                generatedApi.toString(),
+                "--check-compatibility:api:released",
+                previousApi.toString()
+        ]
+        runMetalava(args)
+    }
+}
+
+// Update protocol API signature file for current Car API level to reflect the state of current
+// protocol API in the project.
+class UpdateProtocolApiTask extends DefaultTask {
+    @InputDirectory
+    File protocolDir
+
+    @InputFile
+    File generatedApi
+
+    @InputFile
+    @Optional
+    File currentApi
+
+    @TaskAction
+    def exec() {
+        // The expected Car protocol API signature file for the current Car API level and project
+        // version
+        File updatedApi = new File(protocolDir, String.format(
+                "protocol-%s_%s.txt", project.latestCarAppApiLevel, project.version))
+
+        // Determine whether this API level was previously released by checking whether the project
+        // version matches
+        boolean alreadyReleased = currentApi != updatedApi
+
+        // Determine whether this API level is final (Only snapshot, dev, alpha releases are
+        // non-final)
+        boolean isCurrentApiFinal = false
+        if (currentApi != null) {
+            String currentApiFileName = currentApi.name
+            int versionStart = currentApiFileName.indexOf("_")
+            int versionEnd = currentApiFileName.indexOf(".txt")
+
+            String parsedCurrentVersion = currentApiFileName.substring(versionStart + 1, versionEnd)
+            Version currentVersion = new Version(parsedCurrentVersion)
+            isCurrentApiFinal = currentVersion.isFinalApi()
+        }
+
+        if (currentApi != null && alreadyReleased && isCurrentApiFinal) {
+            throw new GradleException("Version has changed for current Car API level. Increment " +
+                    "Car API level before making protocol API changes")
+        }
+
+        // Overwrite protocol API signature file for current Car API level
+        Files.copy(generatedApi, updatedApi)
+    }
+}
+
+class ApiLevelFileWriterTask extends DefaultTask {
+    @Input
+    String carApiLevel = project.latestCarAppApiLevel
+
+    @OutputFile
+    File apiLevelFile
+
+    @TaskAction
+    def exec() {
+        PrintWriter writer = new PrintWriter(apiLevelFile)
+        writer.println(carApiLevel)
+        writer.close()
+    }
+}
+
+// Paths and file locations required for protocol API operations
+class ProtocolLocation {
+    File buildDir
+    File protocolDir
+    File generatedApi
+    File currentApi
+    File previousApi
+
+    def getProtocolApiFile(int carApiLevel) {
+        File[] apiFiles = protocolDir.listFiles(new FilenameFilter() {
+            boolean accept(File dir, String name) {
+                return name.startsWith(String.format("protocol-%d_", carApiLevel))
+            }
+        })
+
+        if (apiFiles == null || apiFiles.size() == 0) {
+            return null
+        } else if (apiFiles.size() > 1) {
+            StringBuilder builder = new StringBuilder()
+            for (File file : currentApis) {
+                builder.append(file.path)
+                builder.append("\n")
+            }
+
+            throw new GradleException(
+                    String.format("Multiple API signature files found for Car API level %s\n%s",
+                            carApiLevel, builder.toString()))
+        }
+
+        return apiFiles[0]
+    }
+
+    ProtocolLocation(Project project) {
+        buildDir = new File(project.buildDir, "/protocol/")
+        generatedApi = new File(buildDir, "/generated.txt")
+        protocolDir = new File(project.projectDir, "/protocol/")
+        int currentApiLevel = Integer.parseInt(project.latestCarAppApiLevel)
+        currentApi = getProtocolApiFile(currentApiLevel)
+        previousApi = getProtocolApiFile(currentApiLevel - 1)
+    }
+}
+
+def RESOURCE_DIRECTORY = "generatedResources"
+def API_LEVEL_FILE_PATH = "$RESOURCE_DIRECTORY/car-app-api.level"
+
+LibraryExtension library = project.extensions.getByType(LibraryExtension.class)
+
+// afterEvaluate required to read extension properties
+afterEvaluate {
+    task writeCarApiLevelFile(type: ApiLevelFileWriterTask) {
+        File artifactName = new File(buildDir, API_LEVEL_FILE_PATH)
+        apiLevelFile = artifactName
+    }
+
+    AndroidSourceDirectorySet resources = library.sourceSets.getByName("main").resources
+    Set<File> resFiles = new HashSet<>()
+    resFiles.add(resources.srcDirs)
+    resFiles.add(new File(buildDir, RESOURCE_DIRECTORY))
+    resources.srcDirs(resFiles)
+    Set<String> includes = resources.includes
+    if (!includes.isEmpty()) {
+        includes.add("*.level")
+        resources.setIncludes(includes)
+    }
+
+    ProtocolLocation projectProtocolLocation = new ProtocolLocation(project)
+    task generateProtocolApi(type: GenerateProtocolApiTask) {
+        description = "Generate an API signature file for the classes annotated with @CarProtocol"
+        generatedApi = projectProtocolLocation.generatedApi
+        dependsOn(assemble)
+    }
+    task checkProtocolApiCompat(type: CheckProtocolApiCompatibilityTask) {
+        description = "Check for BREAKING changes to the protocol API"
+        if (projectProtocolLocation.previousApi != null) {
+            previousApi = projectProtocolLocation.previousApi
+        }
+        generatedApi = projectProtocolLocation.generatedApi
+        dependsOn(generateProtocolApi)
+    }
+    task checkProtocolApi(type: CheckProtocolApiTask) {
+        description = "Check for changes to the protocol API"
+        generatedApi = projectProtocolLocation.generatedApi
+        currentApi = projectProtocolLocation.currentApi
+        dependsOn(checkProtocolApiCompat)
+    }
+    task updateProtocolApi(type: UpdateProtocolApiTask) {
+        description = "Update protocol API signature file for current Car API level to reflect" +
+                "the current state of the protocol API in the source tree."
+        protocolDir = projectProtocolLocation.protocolDir
+        generatedApi = projectProtocolLocation.generatedApi
+        currentApi = projectProtocolLocation.currentApi
+        dependsOn(checkProtocolApiCompat)
+    }
+    EnableCachingKt.cacheEvenIfNoOutputs(checkProtocolApi)
+    EnableCachingKt.cacheEvenIfNoOutputs(checkProtocolApiCompat)
+    checkApi.dependsOn(checkProtocolApi)
+}
+
+library.libraryVariants.all { variant ->
+    variant.processJavaResourcesProvider.configure {
+        it.dependsOn(writeCarApiLevelFile)
+    }
+}
+
diff --git a/car/app/app/src/main/java/androidx/car/app/CarContext.java b/car/app/app/src/main/java/androidx/car/app/CarContext.java
index 84b6922..1107d0c 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarContext.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarContext.java
@@ -277,8 +277,7 @@
      *   <dt>An {@link Intent} to start this app in the car.
      *   <dd>The component name of the intent must be the one for the {@link CarAppService} that
      *       contains this {@link CarContext}. If the component name is for a different
-     *       component, the
-     *       method will throw a {@link SecurityException}.
+     *       component, the method will throw a {@link SecurityException}.
      * </dl>
      *
      * <p><b>This method should not be called until the {@link Lifecycle.State} of the context's
diff --git a/car/app/app/src/main/java/androidx/car/app/hardware/info/EnergyLevel.java b/car/app/app/src/main/java/androidx/car/app/hardware/info/EnergyLevel.java
index 480eae7..67633bb 100644
--- a/car/app/app/src/main/java/androidx/car/app/hardware/info/EnergyLevel.java
+++ b/car/app/app/src/main/java/androidx/car/app/hardware/info/EnergyLevel.java
@@ -46,11 +46,6 @@
     @NonNull
     private final CarValue<Boolean> mEnergyIsLow;
 
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-    @Keep
-    @Nullable
-    private final CarValue<Float> mRangeRemaining;
-
     @Keep
     @Nullable
     private final CarValue<Float> mRangeRemainingMeters;
@@ -80,23 +75,7 @@
     /** Returns the range remaining from the car hardware in meters. */
     @NonNull
     public CarValue<Float> getRangeRemainingMeters() {
-        if (mRangeRemainingMeters != null) {
-            return requireNonNull(mRangeRemainingMeters);
-        }
-        return requireNonNull(mRangeRemaining);
-    }
-
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-    /**
-     * Returns the range remaining from the car hardware in meters.
-     *
-     * @deprecated use {@link #getRangeRemainingMeters()}
-     */
-    @NonNull
-    @Deprecated
-    public CarValue<Float> getRangeRemaining() {
-        return getRangeRemainingMeters();
+        return requireNonNull(mRangeRemainingMeters);
     }
 
     /**
@@ -153,7 +132,6 @@
         mBatteryPercent = requireNonNull(builder.mBatteryPercent);
         mFuelPercent = requireNonNull(builder.mFuelPercent);
         mEnergyIsLow = requireNonNull(builder.mEnergyIsLow);
-        mRangeRemaining = null;
         mRangeRemainingMeters = requireNonNull(builder.mRangeRemainingMeters);
         mDistanceDisplayUnit = requireNonNull(builder.mDistanceDisplayUnit);
     }
@@ -163,7 +141,6 @@
         mBatteryPercent = CarValue.UNIMPLEMENTED_FLOAT;
         mFuelPercent = CarValue.UNIMPLEMENTED_FLOAT;
         mEnergyIsLow = CarValue.UNIMPLEMENTED_BOOLEAN;
-        mRangeRemaining = null;
         mRangeRemainingMeters = CarValue.UNIMPLEMENTED_FLOAT;
         mDistanceDisplayUnit = CarValue.UNIMPLEMENTED_INTEGER;
     }
@@ -206,21 +183,6 @@
             return this;
         }
 
-        // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-        /**
-         * Sets the range of the remaining fuel in meters.
-         *
-         * @throws NullPointerException if {@code rangeRemaining} is {@code null}
-         * @deprecated use {@link #setRangeRemainingMeters}
-         */
-        @NonNull
-        @Deprecated
-        public Builder setRangeRemaining(@NonNull CarValue<Float> rangeRemainingMeters) {
-            mRangeRemainingMeters = requireNonNull(rangeRemainingMeters);
-            return this;
-        }
-
         /**
          * Sets the range of the remaining fuel in meters.
          *
diff --git a/car/app/app/src/main/java/androidx/car/app/hardware/info/Mileage.java b/car/app/app/src/main/java/androidx/car/app/hardware/info/Mileage.java
index 68df7a9..ae7111a 100644
--- a/car/app/app/src/main/java/androidx/car/app/hardware/info/Mileage.java
+++ b/car/app/app/src/main/java/androidx/car/app/hardware/info/Mileage.java
@@ -33,11 +33,6 @@
 @CarProtocol
 @RequiresCarApi(3)
 public final class Mileage {
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-    @Keep
-    @Nullable
-    private final CarValue<Float> mOdometer;
-
     @Keep
     @Nullable
     private final CarValue<Float> mOdometerMeters;
@@ -49,23 +44,7 @@
     /** Returns the value of the odometer from the car hardware in meters. */
     @NonNull
     public CarValue<Float> getOdometerMeters() {
-        if (mOdometerMeters != null) {
-            return requireNonNull(mOdometerMeters);
-        }
-        return requireNonNull(mOdometer);
-    }
-
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-    /**
-     * Returns the value of the odometer from the car hardware in meters.
-     *
-     * @deprecated use {@link #getOdometerMeters()}
-     */
-    @NonNull
-    @Deprecated
-    public CarValue<Float> getOdometer() {
-        return getOdometerMeters();
+        return requireNonNull(mOdometerMeters);
     }
 
     /**
@@ -108,14 +87,12 @@
     }
 
     Mileage(Builder builder) {
-        mOdometer = null;
         mOdometerMeters = requireNonNull(builder.mOdometerMeters);
         mDistanceDisplayUnit = requireNonNull(builder.mDistanceDisplayUnit);
     }
 
     /** Constructs an empty instance, used by serialization code. */
     private Mileage() {
-        mOdometer = CarValue.UNIMPLEMENTED_FLOAT;
         mOdometerMeters = CarValue.UNIMPLEMENTED_FLOAT;
         mDistanceDisplayUnit = CarValue.UNIMPLEMENTED_INTEGER;
     }
@@ -126,21 +103,6 @@
         CarValue<@CarDistanceUnit Integer> mDistanceDisplayUnit =
                 CarValue.UNIMPLEMENTED_INTEGER;
 
-        // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-        /**
-         * Sets the odometer value in meters.
-         *
-         * @throws NullPointerException if {@code odometer} is {@code null}
-         * @deprecated use {@link #setOdometerMeters}
-         */
-        @NonNull
-        @Deprecated
-        public Builder setOdometer(@NonNull CarValue<Float> odometer) {
-            mOdometerMeters = requireNonNull(odometer);
-            return this;
-        }
-
         /**
          * Sets the odometer value in meters.
          *
diff --git a/car/app/app/src/main/java/androidx/car/app/hardware/info/Speed.java b/car/app/app/src/main/java/androidx/car/app/hardware/info/Speed.java
index 9290f84..9a4424c 100644
--- a/car/app/app/src/main/java/androidx/car/app/hardware/info/Speed.java
+++ b/car/app/app/src/main/java/androidx/car/app/hardware/info/Speed.java
@@ -35,20 +35,10 @@
 @CarProtocol
 @RequiresCarApi(3)
 public final class Speed {
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-    @Keep
-    @Nullable
-    private final CarValue<Float> mRawSpeed;
-
     @Keep
     @Nullable
     private final CarValue<Float> mRawSpeedMetersPerSecond;
 
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-    @Keep
-    @Nullable
-    private final CarValue<Float> mDisplaySpeed;
-
     @Keep
     @Nullable
     private final CarValue<Float> mDisplaySpeedMetersPerSecond;
@@ -65,26 +55,7 @@
      */
     @NonNull
     public CarValue<Float> getRawSpeedMetersPerSecond() {
-        if (mRawSpeedMetersPerSecond != null) {
-            return requireNonNull(mRawSpeedMetersPerSecond);
-        }
-        return requireNonNull(mRawSpeed);
-    }
-
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-    /**
-     * Returns the raw speed of the car in meters/second.
-     *
-     * <p>The value is positive when the vehicle is moving forward, negative when moving
-     * backwards and zero when stopped.
-     *
-     * @deprecated use {@link #getRawSpeedMetersPerSecond()}
-     */
-    @NonNull
-    @Deprecated
-    public CarValue<Float> getRawSpeed() {
-        return getRawSpeedMetersPerSecond();
+        return requireNonNull(mRawSpeedMetersPerSecond);
     }
 
     /**
@@ -98,29 +69,7 @@
      */
     @NonNull
     public CarValue<Float> getDisplaySpeedMetersPerSecond() {
-        if (mRawSpeedMetersPerSecond != null) {
-            return requireNonNull(mDisplaySpeedMetersPerSecond);
-        }
-        return requireNonNull(mDisplaySpeed);
-    }
-
-    // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-    /**
-     * Returns the display speed of the car in meters/second.
-     *
-     * <p>Some cars display a slightly slower speed than the actual speed. This is usually
-     * displayed on the speedometer.
-     *
-     * <p>The value is positive when the vehicle is moving forward, negative when moving
-     * backwards and zero when stopped.
-     *
-     * @deprecated use {@link #getDisplaySpeedMetersPerSecond()}
-     */
-    @NonNull
-    @Deprecated
-    public CarValue<Float> getDisplaySpeed() {
-        return getDisplaySpeedMetersPerSecond();
+        return requireNonNull(mDisplaySpeedMetersPerSecond);
     }
 
     /**
@@ -168,18 +117,14 @@
     }
 
     Speed(Builder builder) {
-        mRawSpeed = null;
         mRawSpeedMetersPerSecond = requireNonNull(builder.mRawSpeedMetersPerSecond);
-        mDisplaySpeed = null;
         mDisplaySpeedMetersPerSecond = requireNonNull(builder.mDisplaySpeedMetersPerSecond);
         mSpeedDisplayUnit = requireNonNull(builder.mSpeedDisplayUnit);
     }
 
     /** Constructs an empty instance, used by serialization code. */
     private Speed() {
-        mRawSpeed = null;
         mRawSpeedMetersPerSecond = CarValue.UNIMPLEMENTED_FLOAT;
-        mDisplaySpeed = null;
         mDisplaySpeedMetersPerSecond = CarValue.UNIMPLEMENTED_FLOAT;
         mSpeedDisplayUnit = CarValue.UNIMPLEMENTED_INTEGER;
     }
@@ -190,21 +135,6 @@
         CarValue<Float> mDisplaySpeedMetersPerSecond = CarValue.UNIMPLEMENTED_FLOAT;
         CarValue<@CarSpeedUnit Integer> mSpeedDisplayUnit = CarValue.UNIMPLEMENTED_INTEGER;
 
-        // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-        /**
-         * Sets the raw speed in meters per second.
-         *
-         * @throws NullPointerException if {@code rawSpeedMetersPerSecond} is {@code null}
-         * @deprecated use {@link #setRawSpeedMetersPerSecond}
-         */
-        @Deprecated
-        @NonNull
-        public Builder setRawSpeed(@NonNull CarValue<Float> rawSpeed) {
-            mRawSpeedMetersPerSecond = requireNonNull(rawSpeed);
-            return this;
-        }
-
         /**
          * Sets the raw speed in meters per second.
          *
@@ -217,21 +147,6 @@
             return this;
         }
 
-        // TODO(b/192106888): Remove when new values fully supported by Android Auto Host.
-
-        /**
-         * Sets the display speed in meters per second. *
-         *
-         * @throws NullPointerException if {@code displaySpeedMetersPerSecond} is {@code null}
-         * @deprecated use {@link #setDisplaySpeedMetersPerSecond}
-         */
-        @Deprecated
-        @NonNull
-        public Builder setDisplaySpeed(@NonNull CarValue<Float> displaySpeed) {
-            mDisplaySpeedMetersPerSecond = requireNonNull(displaySpeed);
-            return this;
-        }
-
         /**
          * Sets the display speed in meters per second. *
          *
diff --git a/car/app/app/src/main/java/androidx/car/app/notification/CarPendingIntent.java b/car/app/app/src/main/java/androidx/car/app/notification/CarPendingIntent.java
index 42399eb..f321557 100644
--- a/car/app/app/src/main/java/androidx/car/app/notification/CarPendingIntent.java
+++ b/car/app/app/src/main/java/androidx/car/app/notification/CarPendingIntent.java
@@ -61,7 +61,10 @@
 
     /**
      * Creates a {@link PendingIntent} that can be sent in a notification action which will allow
-     * your car app to be started when the user clicks on the action.
+     * the targeted car app to be started when the user clicks on the action.
+     *
+     * <p>See {@link CarContext#startCarApp} for the supported intents that can be passed to this
+     * method.
      *
      * <p>Here is an example of usage of this method when setting a notification's intent:
      *
@@ -86,11 +89,11 @@
      * @throws InvalidParameterException if the {@code intent} is not for starting a navigation
      *                                   or a phone call and does not have the target car app's
      *                                   component name
-     * @throws SecurityException         if the {@code intent} does not follow the allowed values
-     *                                   as is defined in {@link CarContext#startCarApp(Intent)}
+     * @throws SecurityException         if the {@code intent} is for a different component than the
+     *                                   one associated with the input {@code context}
      *
-     * @return an existing or new PendingIntent matching the given parameters.  May return null
-     * only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
+     * @return an existing or new PendingIntent matching the given parameters. May return {@code
+     * null} only if {@link PendingIntent#FLAG_NO_CREATE} has been supplied.
      */
     @NonNull
     public static PendingIntent getCarApp(@NonNull Context context, int requestCode,
diff --git a/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
index 7accc41..bdc8821 100644
--- a/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
+++ b/car/app/app/src/main/java/androidx/car/app/versioning/CarAppApiLevels.java
@@ -16,9 +16,16 @@
 
 package androidx.car.app.versioning;
 
+import static java.util.Objects.requireNonNull;
+
 import androidx.annotation.RestrictTo;
 import androidx.car.app.CarContext;
 
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+
 /**
  * API levels supported by this library.
  *
@@ -65,6 +72,8 @@
     @CarAppApiLevel
     public static final int UNKNOWN = 0;
 
+    private static final String CAR_API_LEVEL_FILE = "car-app-api.level";
+
     /**
      * Returns whether the given integer is a valid {@link CarAppApiLevel}
      *
@@ -80,7 +89,37 @@
      */
     @CarAppApiLevel
     public static int getLatest() {
-        return LEVEL_3;
+        // The latest Car API level is defined as java resource, generated via build.gradle. This
+        // has to be read through the class loader because we do not have access to the context
+        // to retrieve an Android resource.
+        ClassLoader classLoader = requireNonNull(CarAppApiLevels.class.getClassLoader());
+        InputStream inputStream = classLoader.getResourceAsStream(CAR_API_LEVEL_FILE);
+
+        if (inputStream == null) {
+            throw new IllegalStateException(String.format("Car API level file %s not found",
+                    CAR_API_LEVEL_FILE));
+        }
+
+        try {
+            InputStreamReader streamReader = new InputStreamReader(inputStream);
+            BufferedReader reader = new BufferedReader(streamReader);
+            String line = reader.readLine();
+
+            switch (Integer.parseInt(line)) {
+                case 0:
+                    return UNKNOWN;
+                case 1:
+                    return LEVEL_1;
+                case 2:
+                    return LEVEL_2;
+                case 3:
+                    return LEVEL_3;
+                default:
+                    throw new IllegalStateException("Undefined Car API level: " + line);
+            }
+        } catch (IOException e) {
+            throw new IllegalStateException("Unable to read Car API level file");
+        }
     }
 
     /**
diff --git a/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java b/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
index b46b476..faf1163 100644
--- a/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/CarAppServiceTest.java
@@ -103,7 +103,8 @@
                         mCarContext = TestCarContext.createCarContext(
                                 ApplicationProvider.getApplicationContext());
                         Session session = createTestSession();
-                        SessionController.of(session, mCarContext);
+                        // Injects the TestCarContext into the test session.
+                        new SessionController(session, mCarContext);
                         return session;
                     }
                 };
diff --git a/car/app/app/src/test/java/androidx/car/app/ScreenTest.java b/car/app/app/src/test/java/androidx/car/app/ScreenTest.java
index cdda07c..13084ad 100644
--- a/car/app/app/src/test/java/androidx/car/app/ScreenTest.java
+++ b/car/app/app/src/test/java/androidx/car/app/ScreenTest.java
@@ -83,7 +83,7 @@
             }
         };
 
-        ScreenController.of(mCarContext, mScreen).create().start().resume();
+        new ScreenController(mCarContext, mScreen).create().start().resume();
     }
 
     @Test
diff --git a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
index 806f096..f842dd9 100644
--- a/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
+++ b/compose/desktop/desktop/samples/src/jvmMain/kotlin/androidx/compose/desktop/examples/example1/Main.jvm.kt
@@ -21,10 +21,12 @@
 import androidx.compose.desktop.DesktopMaterialTheme
 import androidx.compose.desktop.LocalAppWindow
 import androidx.compose.desktop.Window
+import androidx.compose.foundation.ExperimentalDesktopApi
 import androidx.compose.foundation.ExperimentalFoundationApi
 import androidx.compose.foundation.Image
 import androidx.compose.foundation.ScrollState
 import androidx.compose.foundation.VerticalScrollbar
+import androidx.compose.foundation.mouseClickable
 import androidx.compose.foundation.background
 import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Arrangement
@@ -82,10 +84,6 @@
 import androidx.compose.ui.input.key.Key
 import androidx.compose.ui.input.key.plus
 import androidx.compose.ui.input.key.shortcuts
-import androidx.compose.ui.input.pointer.PointerEvent
-import androidx.compose.ui.input.pointer.PointerEventPass
-import androidx.compose.ui.input.pointer.PointerInputFilter
-import androidx.compose.ui.input.pointer.PointerInputModifier
 import androidx.compose.ui.input.pointer.pointerMoveFilter
 import androidx.compose.ui.platform.LocalUriHandler
 import androidx.compose.ui.res.imageResource
@@ -107,7 +105,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.em
 import androidx.compose.ui.unit.sp
-import java.awt.event.MouseEvent
 
 private const val title = "Desktop Compose Elements"
 
@@ -186,7 +183,10 @@
     )
 }
 
-@OptIn(ExperimentalComposeUiApi::class)
+@OptIn(
+    ExperimentalComposeUiApi::class,
+    ExperimentalDesktopApi::class
+)
 @Composable
 private fun ScrollableContent(scrollState: ScrollState) {
     val amount = remember { mutableStateOf(0f) }
@@ -351,7 +351,6 @@
         }
 
         Row(verticalAlignment = Alignment.CenterVertically) {
-            var lastEvent by remember { mutableStateOf<MouseEvent?>(null) }
             Button(
                 modifier = Modifier.padding(4.dp),
                 onClick = {
@@ -361,22 +360,27 @@
                 Text("Base")
             }
 
-            Text(
-                modifier = object : PointerInputModifier {
-                    override val pointerInputFilter = object : PointerInputFilter() {
-                        override fun onPointerEvent(
-                            pointerEvent: PointerEvent,
-                            pass: PointerEventPass,
-                            bounds: IntSize
-                        ) {
-                            lastEvent = pointerEvent.mouseEvent
-                        }
+            var clickableText by remember { mutableStateOf("Click me!") }
 
-                        override fun onCancel() {
+            Text(
+                modifier = Modifier.mouseClickable(
+                    onClick = {
+                        clickableText = buildString {
+                            append("Buttons pressed:\n")
+                            append("primary: ${buttons.isPrimaryPressed}\t")
+                            append("secondary: ${buttons.isSecondaryPressed}\t")
+                            append("tertiary: ${buttons.isTertiaryPressed}\t")
+
+                            append("\n\nKeyboard modifiers pressed:\n")
+
+                            append("alt: ${keyboardModifiers.isAltPressed}\t")
+                            append("ctrl: ${keyboardModifiers.isCtrlPressed}\t")
+                            append("meta: ${keyboardModifiers.isMetaPressed}\t")
+                            append("shift: ${keyboardModifiers.isShiftPressed}\t")
                         }
                     }
-                },
-                text = "Custom mouse event button: ${lastEvent?.button}"
+                ),
+                text = clickableText
             )
         }
 
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 a3e36fe..036d159 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
@@ -45,9 +45,9 @@
 import androidx.compose.ui.platform.LocalViewConfiguration
 import androidx.compose.ui.unit.dp
 import androidx.test.filters.LargeTest
-import androidx.test.filters.SdkSuppress
 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
@@ -107,11 +107,11 @@
         }
     }
 
-    // this test makes sense only when run on the Android version which supports RenderNodes
-    // as this tests how efficiently we move RenderNodes.
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @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 {
             ListRemeasureTestCase(
                 addNewItemOnToggle = false,
@@ -121,11 +121,11 @@
         }
     }
 
-    // this test makes sense only when run on the Android version which supports RenderNodes
-    // as this tests how efficiently we move RenderNodes.
-    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.Q)
     @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 {
             ListRemeasureTestCase(
                 addNewItemOnToggle = true,
@@ -143,6 +143,11 @@
                 LazyColumn,
                 LazyRow
             )
+
+        // 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
     }
 }
 
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
index 5e11763..a4e93b2 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/lazy/LazyGridTest.kt
@@ -16,13 +16,19 @@
 
 package androidx.compose.foundation.lazy
 
+import androidx.compose.foundation.AutoTestFrameClock
 import androidx.compose.foundation.ExperimentalFoundationApi
+import androidx.compose.foundation.layout.Box
 import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxWidth
 import androidx.compose.foundation.layout.height
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.layout.requiredWidth
 import androidx.compose.foundation.layout.size
 import androidx.compose.foundation.layout.width
+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
@@ -34,6 +40,8 @@
 import androidx.compose.ui.unit.dp
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
+import com.google.common.truth.Truth
+import kotlinx.coroutines.runBlocking
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -304,4 +312,38 @@
             .assertTopPositionInRootIsEqualTo(itemSize)
             .assertLeftPositionInRootIsEqualTo(itemSize)
     }
+
+    @Test
+    fun changeItemsCountAndScrollImmediately() {
+        lateinit var state: LazyListState
+        var count by mutableStateOf(100)
+        val composedIndexes = mutableListOf<Int>()
+        rule.setContent {
+            state = rememberLazyListState()
+            LazyVerticalGrid(
+                GridCells.Fixed(1),
+                Modifier.fillMaxWidth().height(10.dp),
+                state
+            ) {
+                items(count) { index ->
+                    composedIndexes.add(index)
+                    Box(Modifier.size(20.dp))
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            composedIndexes.clear()
+            count = 10
+            runBlocking(AutoTestFrameClock()) {
+                // we try to scroll to the index after 10, but we expect that the component will
+                // already be aware there is a new count and not compose items with index > 10
+                state.scrollToItem(50)
+            }
+            composedIndexes.forEach {
+                Truth.assertThat(it).isLessThan(count)
+            }
+            Truth.assertThat(state.firstVisibleItemIndex).isEqualTo(9)
+        }
+    }
 }
diff --git a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
index 6288c86..b0a860e 100644
--- a/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
+++ b/compose/foundation/foundation/src/androidAndroidTest/kotlin/androidx/compose/foundation/text/selection/SelectionContainerFocusTest.kt
@@ -36,6 +36,7 @@
 import androidx.compose.ui.platform.LocalLayoutDirection
 import androidx.compose.ui.platform.LocalTextToolbar
 import androidx.compose.ui.platform.testTag
+import androidx.compose.ui.test.center
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.hasTestTag
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
@@ -49,7 +50,6 @@
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.unit.sp
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.LargeTest
 import com.google.common.truth.Truth.assertThat
 import com.nhaarman.mockitokotlin2.mock
@@ -78,11 +78,9 @@
 
     private val hapticFeedback = mock<HapticFeedback>()
 
-    @FlakyTest(bugId = 179770443)
     @Test
-    fun click_anywhere_to_cancel() {
+    fun tap_to_cancel() {
         // Setup. Long press to create a selection.
-        // A reasonable number.
         createSelectionContainer()
         // Touch position. In this test, each character's width and height equal to fontSize.
         // Position is computed so that (position, position) is the center of the first character.
@@ -95,14 +93,9 @@
             assertThat(selection1.value).isNotNull()
         }
 
-        // Touch position. In this test, each character's width and height equal to fontSize.
-        // Position is computed so that (position, position) is the center of the first character.
-        val positionInBox = with(Density(view.context)) {
-            boxSize.toPx() / 2
-        }
         // Act.
         rule.onNode(hasTestTag("box"))
-            .performGesture { click(Offset(x = positionInBox, y = positionInBox)) }
+            .performGesture { click(center) }
 
         // Assert.
         rule.runOnIdle {
@@ -118,7 +111,6 @@
     @Test
     fun select_anotherContainer_cancelOld() {
         // Setup. Long press to create a selection.
-        // A reasonable number.
         createSelectionContainer()
         // Touch position. In this test, each character's width and height equal to fontSize.
         // Position is computed so that (position, position) is the center of the first character.
@@ -168,16 +160,19 @@
                             selection1.value = it
                         }
                     ) {
-                        CoreText(
-                            AnnotatedString(textContent),
-                            Modifier.fillMaxWidth(),
-                            style = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
-                            softWrap = true,
-                            overflow = TextOverflow.Clip,
-                            maxLines = Int.MAX_VALUE,
-                            inlineContent = mapOf(),
-                            onTextLayout = {}
-                        )
+                        Column {
+                            CoreText(
+                                AnnotatedString(textContent),
+                                Modifier.fillMaxWidth(),
+                                style = TextStyle(fontFamily = fontFamily, fontSize = fontSize),
+                                softWrap = true,
+                                overflow = TextOverflow.Clip,
+                                maxLines = Int.MAX_VALUE,
+                                inlineContent = mapOf(),
+                                onTextLayout = {}
+                            )
+                            Box(Modifier.size(boxSize, boxSize).testTag("box"))
+                        }
                     }
 
                     SelectionContainer(
@@ -200,8 +195,6 @@
                             onTextLayout = {}
                         )
                     }
-
-                    Box(Modifier.size(boxSize, boxSize).testTag("box"))
                 }
             }
         }
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
index f0dcd0d..2ec5116 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/lazy/LazyGrid.kt
@@ -47,9 +47,6 @@
     contentPadding: PaddingValues = PaddingValues(0.dp),
     content: LazyGridScope.() -> Unit
 ) {
-    val scope = LazyGridScopeImpl()
-    scope.apply(content)
-
     when (cells) {
         is GridCells.Fixed ->
             FixedLazyGrid(
@@ -57,7 +54,7 @@
                 modifier = modifier,
                 state = state,
                 contentPadding = contentPadding,
-                scope = scope
+                content = content
             )
         is GridCells.Adaptive ->
             BoxWithConstraints(
@@ -68,7 +65,7 @@
                     nColumns = nColumns,
                     state = state,
                     contentPadding = contentPadding,
-                    scope = scope
+                    content = content
                 )
             }
     }
@@ -185,14 +182,17 @@
     modifier: Modifier = Modifier,
     state: LazyListState = rememberLazyListState(),
     contentPadding: PaddingValues = PaddingValues(0.dp),
-    scope: LazyGridScopeImpl
+    content: LazyGridScope.() -> Unit
 ) {
-    val rows = (scope.totalSize + nColumns - 1) / nColumns
     LazyColumn(
         modifier = modifier,
         state = state,
         contentPadding = contentPadding
     ) {
+        val scope = LazyGridScopeImpl()
+        scope.apply(content)
+
+        val rows = (scope.totalSize + nColumns - 1) / nColumns
         items(rows) { rowIndex ->
             Row {
                 for (columnIndex in 0 until nColumns) {
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
index 1ed3722..3b0261d 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/CoreText.kt
@@ -33,6 +33,7 @@
 import androidx.compose.runtime.DisposableEffectScope
 import androidx.compose.runtime.Stable
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.saveable.Saver
 import androidx.compose.runtime.saveable.rememberSaveable
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.draw.drawBehind
@@ -127,9 +128,12 @@
     // When text is updated, the selection on this CoreText becomes invalid. It can be treated
     // as a brand new CoreText.
     // When SelectionRegistrar is updated, CoreText have to request a new ID to avoid ID collision.
-    val selectableId = rememberSaveable(text, selectionRegistrar) {
-        selectionRegistrar?.nextSelectableId() ?: SelectionRegistrar.InvalidSelectableId
-    }
+
+    val selectableId =
+        rememberSaveable(text, selectionRegistrar, saver = selectionIdSaver(selectionRegistrar)) {
+            selectionRegistrar?.nextSelectableId() ?: SelectionRegistrar.InvalidSelectableId
+        }
+
     val state = remember {
         TextState(
             TextDelegate(
@@ -404,9 +408,13 @@
                 dragTotalDistance += delta
 
                 if (!outOfBoundary(dragBeginPosition, dragBeginPosition + dragTotalDistance)) {
+                    // Notice that only the end position needs to be updated here.
+                    // Start position is left unchanged. This is typically important when
+                    // long-press is using SelectionAdjustment.WORD or
+                    // SelectionAdjustment.PARAGRAPH that updates the start handle position from
+                    // the dragBeginPosition.
                     selectionRegistrar?.notifySelectionUpdate(
                         layoutCoordinates = it,
-                        startPosition = dragBeginPosition,
                         endPosition = dragBeginPosition + dragTotalDistance,
                         adjustment = SelectionAdjustment.CHARACTER
                     )
@@ -639,3 +647,11 @@
     }
     return Pair(placeholders, inlineComposables)
 }
+
+/**
+ * A custom saver that won't save if no selection is active.
+ */
+private fun selectionIdSaver(selectionRegistrar: SelectionRegistrar?) = Saver<Long, Long>(
+    save = { if (selectionRegistrar.hasSelection(it)) it else null },
+    restore = { it }
+)
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
index dc4b353..acba3a7 100644
--- a/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/Clickable.desktop.kt
@@ -16,5 +16,137 @@
 
 package androidx.compose.foundation
 
+import androidx.compose.foundation.gestures.forEachGesture
+import androidx.compose.foundation.interaction.MutableInteractionSource
+import androidx.compose.runtime.Immutable
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberUpdatedState
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.composed
+import androidx.compose.ui.input.pointer.AwaitPointerEventScope
+import androidx.compose.ui.input.pointer.PointerButtons
+import androidx.compose.ui.input.pointer.PointerEvent
+import androidx.compose.ui.input.pointer.PointerInputScope
+import androidx.compose.ui.input.pointer.PointerKeyboardModifiers
+import androidx.compose.ui.input.pointer.changedToDown
+import androidx.compose.ui.input.pointer.changedToUp
+import androidx.compose.ui.input.pointer.consumeDownChange
+import androidx.compose.ui.input.pointer.isOutOfBounds
+import androidx.compose.ui.input.pointer.pointerInput
+import androidx.compose.ui.platform.debugInspectorInfo
+import androidx.compose.ui.semantics.Role
+import androidx.compose.ui.util.fastAll
+import kotlinx.coroutines.coroutineScope
+
 // TODO: b/168524931 - should this depend on the input device?
-internal actual val TapIndicationDelay: Long = 0L
\ No newline at end of file
+internal actual val TapIndicationDelay: Long = 0L
+
+@Immutable @ExperimentalDesktopApi
+class MouseClickScope constructor(
+    val buttons: PointerButtons,
+    val keyboardModifiers: PointerKeyboardModifiers
+)
+
+@ExperimentalDesktopApi
+internal val EmptyClickContext = MouseClickScope(
+    PointerButtons(0), PointerKeyboardModifiers(0)
+)
+
+/**
+ * Creates modifier similar to [Modifier.clickable] but provides additional context with
+ * information about pressed buttons and keyboard modifiers
+ *
+ */
+@ExperimentalDesktopApi
+fun Modifier.mouseClickable(
+    enabled: Boolean = true,
+    onClickLabel: String? = null,
+    role: Role? = null,
+    onClick: MouseClickScope.() -> Unit
+) = composed(
+    factory = {
+        val onClickState = rememberUpdatedState(onClick)
+        val gesture = if (enabled) {
+            Modifier.pointerInput(Unit) {
+                detectTapWithContext(
+                    onTap = { down, _ ->
+                        onClickState.value.invoke(
+                            MouseClickScope(
+                                down.buttons,
+                                down.keyboardModifiers
+                            )
+                        )
+                    }
+                )
+            }
+        } else {
+            Modifier
+        }
+        Modifier
+            .genericClickableWithoutGesture(
+                gestureModifiers = gesture,
+                enabled = enabled,
+                onClickLabel = onClickLabel,
+                role = role,
+                onLongClickLabel = null,
+                onLongClick = null,
+                indication = null,
+                interactionSource = remember { MutableInteractionSource() },
+                onClick = { onClick(EmptyClickContext) }
+            )
+    },
+    inspectorInfo = debugInspectorInfo {
+        name = "clickable"
+        properties["enabled"] = enabled
+        properties["onClickLabel"] = onClickLabel
+        properties["role"] = role
+        properties["onClick"] = onClick
+    }
+)
+
+@OptIn(ExperimentalDesktopApi::class)
+internal suspend fun PointerInputScope.detectTapWithContext(
+    onTap: ((PointerEvent, PointerEvent) -> Unit)? = null
+) {
+    forEachGesture {
+        coroutineScope {
+            awaitPointerEventScope {
+
+                val down = awaitEventFirstDown().also {
+                    it.changes.forEach { it.consumeDownChange() }
+                }
+
+                val up = waitForFirstInboundUp()
+                if (up != null) {
+                    up.changes.forEach { it.consumeDownChange() }
+                    onTap?.invoke(down, up)
+                }
+            }
+        }
+    }
+}
+
+@ExperimentalDesktopApi
+suspend fun AwaitPointerEventScope.awaitEventFirstDown(): PointerEvent {
+    var event: PointerEvent
+    do {
+        event = awaitPointerEvent()
+    } while (
+        !event.changes.fastAll { it.changedToDown() }
+    )
+    return event
+}
+
+private suspend fun AwaitPointerEventScope.waitForFirstInboundUp(): PointerEvent? {
+    while (true) {
+        val event = awaitPointerEvent()
+        val change = event.changes[0]
+        if (change.changedToUp()) {
+            return if (change.isOutOfBounds(size)) {
+                null
+            } else {
+                event
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/BenchmarkClass.kt b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ExperimentalDesktopApi.desktop.kt
similarity index 72%
rename from benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/BenchmarkClass.kt
rename to compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ExperimentalDesktopApi.desktop.kt
index 88234c7..2ab52fb 100644
--- a/benchmark/integration-tests/crystalball-experiment/src/androidTest/java/androidx/benchmark/macro/BenchmarkClass.kt
+++ b/compose/foundation/foundation/src/desktopMain/kotlin/androidx/compose/foundation/ExperimentalDesktopApi.desktop.kt
@@ -1,5 +1,5 @@
 /*
- * Copyright 2020 The Android Open Source Project
+ * 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.
@@ -14,10 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.benchmark.macro
+package androidx.compose.foundation
 
-import android.platform.test.rule.DropCachesRule
-
-class BenchmarkClass {
-    val rule = DropCachesRule()
-}
+@RequiresOptIn("This API is experimental and is likely to change in the future.")
+annotation class ExperimentalDesktopApi
\ No newline at end of file
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
index 9c742d1..dae3c2e 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/TextSelectionLongPressDragTest.kt
@@ -160,7 +160,6 @@
         verify(selectionRegistrar, times(1))
             .notifySelectionUpdate(
                 layoutCoordinates = layoutCoordinates,
-                startPosition = beginPosition2,
                 endPosition = beginPosition2 + dragDistance2,
                 adjustment = SelectionAdjustment.CHARACTER
             )
@@ -181,7 +180,6 @@
         verify(selectionRegistrar, times(1))
             .notifySelectionUpdate(
                 layoutCoordinates = layoutCoordinates,
-                startPosition = beginPosition,
                 endPosition = beginPosition + dragDistance,
                 adjustment = SelectionAdjustment.CHARACTER
             )
@@ -202,7 +200,6 @@
         verify(selectionRegistrar, times(0))
             .notifySelectionUpdate(
                 layoutCoordinates = layoutCoordinates,
-                startPosition = beginPosition,
                 endPosition = beginPosition + dragDistance,
                 adjustment = SelectionAdjustment.CHARACTER
             )
diff --git a/compose/material/material/api/public_plus_experimental_current.txt b/compose/material/material/api/public_plus_experimental_current.txt
index e1e3e04..4332f18 100644
--- a/compose/material/material/api/public_plus_experimental_current.txt
+++ b/compose/material/material/api/public_plus_experimental_current.txt
@@ -72,7 +72,8 @@
   }
 
   public final class BadgeKt {
-    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BadgeBox(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? badgeContent, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void Badge(optional androidx.compose.ui.Modifier modifier, optional long backgroundColor, optional long contentColor, optional kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.RowScope,kotlin.Unit>? content);
+    method @androidx.compose.material.ExperimentalMaterialApi @androidx.compose.runtime.Composable public static void BadgedBox(kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> badge, optional androidx.compose.ui.Modifier modifier, kotlin.jvm.functions.Function1<? super androidx.compose.foundation.layout.BoxScope,kotlin.Unit> content);
   }
 
   @androidx.compose.material.ExperimentalMaterialApi public final class BottomDrawerState extends androidx.compose.material.SwipeableState<androidx.compose.material.BottomDrawerValue> {
diff --git a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
index 7d39a3b..10c0360 100644
--- a/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
+++ b/compose/material/material/integration-tests/material-demos/src/main/java/androidx/compose/material/demos/BadgeDemo.kt
@@ -22,7 +22,8 @@
 import androidx.compose.foundation.layout.requiredHeight
 import androidx.compose.foundation.rememberScrollState
 import androidx.compose.foundation.verticalScroll
-import androidx.compose.material.BadgeBox
+import androidx.compose.material.Badge
+import androidx.compose.material.BadgedBox
 import androidx.compose.material.BottomNavigation
 import androidx.compose.material.BottomNavigationItem
 import androidx.compose.material.Button
@@ -104,7 +105,7 @@
         navigationIcon = {
             IconButton(onClick = { showNavigationIconBadge = false }) {
                 if (showNavigationIconBadge) {
-                    DemoBadgeBox(null) {
+                    DemoBadgedBox(null) {
                         Icon(Icons.Filled.Menu, contentDescription = "Localized description")
                     }
                 } else {
@@ -118,7 +119,7 @@
                 onClick = onActionIcon1BadgeClick
             ) {
                 if (actionIcon1BadgeCount > 0) {
-                    DemoBadgeBox(actionIcon1BadgeCount.toString()) {
+                    DemoBadgedBox(actionIcon1BadgeCount.toString()) {
                         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                     }
                 } else {
@@ -129,7 +130,7 @@
                 onClick = { showActionIcon2Badge = false }
             ) {
                 if (showActionIcon2Badge) {
-                    DemoBadgeBox("99+") {
+                    DemoBadgedBox("99+") {
                         Icon(Icons.Filled.Favorite, contentDescription = "Localized description")
                     }
                 } else {
@@ -172,7 +173,7 @@
                         } else {
                             when (item) {
                                 "Artists" -> {
-                                    DemoBadgeBox(artistsBadgeCount.toString()) {
+                                    DemoBadgedBox(artistsBadgeCount.toString()) {
                                         Icon(
                                             Icons.Filled.Favorite,
                                             contentDescription = "Localized description"
@@ -180,7 +181,7 @@
                                     }
                                 }
                                 else -> {
-                                    DemoBadgeBox(
+                                    DemoBadgedBox(
                                         when (index) {
                                             2 -> "99+"
                                             else -> null
@@ -241,7 +242,7 @@
                         if (!showBadge) {
                             Text(title)
                         } else {
-                            DemoBadgeBox(
+                            DemoBadgedBox(
                                 when (index) {
                                     1 -> tab1BadgeCount.toString()
                                     2 -> "99+"
@@ -305,7 +306,7 @@
                                 contentDescription = "Localized description"
                             )
                         } else {
-                            DemoBadgeBox(
+                            DemoBadgedBox(
                                 when (index) {
                                     1 -> tab1BadgeCount.toString()
                                     2 -> "99+"
@@ -341,25 +342,28 @@
 
 @OptIn(ExperimentalMaterialApi::class)
 @Composable
-private fun DemoBadgeBox(
+private fun DemoBadgedBox(
     badgeText: String?,
     content: @Composable () -> Unit
 ) {
-    if (badgeText.isNullOrEmpty()) {
-        BadgeBox { content() }
-    } else {
-        BadgeBox(
-            badgeContent = {
-                Text(
-                    badgeText,
-                    textAlign = TextAlign.Center,
-                    modifier = Modifier.semantics {
-                        this.contentDescription = "$badgeText notifications"
-                    }
-                )
-            }
-        ) {
-            content()
+    BadgedBox(
+        badge = {
+            Badge(
+                content =
+                    if (!badgeText.isNullOrEmpty()) {
+                        {
+                            Text(
+                                badgeText,
+                                textAlign = TextAlign.Center,
+                                modifier = Modifier.semantics {
+                                    this.contentDescription = "$badgeText notifications"
+                                }
+                            )
+                        }
+                    } else null
+            )
         }
+    ) {
+        content()
     }
 }
diff --git a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
index bdcda98..78666af 100644
--- a/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
+++ b/compose/material/material/samples/src/main/java/androidx/compose/material/samples/BadgeSamples.kt
@@ -17,7 +17,8 @@
 package androidx.compose.material.samples
 
 import androidx.annotation.Sampled
-import androidx.compose.material.BadgeBox
+import androidx.compose.material.Badge
+import androidx.compose.material.BadgedBox
 import androidx.compose.material.BottomNavigation
 import androidx.compose.material.BottomNavigationItem
 import androidx.compose.material.ExperimentalMaterialApi
@@ -34,7 +35,7 @@
     BottomNavigation {
         BottomNavigationItem(
             icon = {
-                BadgeBox(badgeContent = { Text("8") }) {
+                BadgedBox(badge = { Badge { Text("8") } }) {
                     Icon(
                         Icons.Filled.Favorite,
                         contentDescription = "Favorite"
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
index 4bb77c2..791a081 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeScreenshotTest.kt
@@ -60,7 +60,7 @@
                     Modifier.size(56.dp).semantics(mergeDescendants = true) {}.testTag(TestTag),
                     contentAlignment = Alignment.Center
                 ) {
-                    BadgeBox(badgeContent = { Text("8") }) {
+                    BadgedBox(badge = { Badge { Text("8") } }) {
                         Icon(Icons.Filled.Favorite, null)
                     }
                 }
@@ -81,7 +81,7 @@
                 ) {
                     BottomNavigationItem(
                         icon = {
-                            BadgeBox {
+                            BadgedBox(badge = { Badge() }) {
                                 Icon(Icons.Filled.Favorite, null)
                             }
                         },
@@ -106,12 +106,14 @@
                 ) {
                     BottomNavigationItem(
                         icon = {
-                            BadgeBox(
-                                badgeContent = {
-                                    Text(
-                                        "8",
-                                        textAlign = TextAlign.Center
-                                    )
+                            BadgedBox(
+                                badge = {
+                                    Badge {
+                                        Text(
+                                            "8",
+                                            textAlign = TextAlign.Center
+                                        )
+                                    }
                                 }
                             ) {
                                 Icon(Icons.Filled.Favorite, null)
@@ -138,12 +140,14 @@
                 ) {
                     BottomNavigationItem(
                         icon = {
-                            BadgeBox(
-                                badgeContent = {
-                                    Text(
-                                        "99+",
-                                        textAlign = TextAlign.Center
-                                    )
+                            BadgedBox(
+                                badge = {
+                                    Badge {
+                                        Text(
+                                            "99+",
+                                            textAlign = TextAlign.Center
+                                        )
+                                    }
                                 }
                             ) {
                                 Icon(Icons.Filled.Favorite, null)
@@ -167,7 +171,7 @@
             MaterialTheme(lightColors()) {
                 Tab(
                     text = {
-                        BadgeBox {
+                        BadgedBox(badge = { Badge() }) {
                             Text("TAB")
                         }
                     },
@@ -190,12 +194,14 @@
                 // A round badge with the text `8` attached to a tab.
                 Tab(
                     text = {
-                        BadgeBox(
-                            badgeContent = {
-                                Text(
-                                    "8",
-                                    textAlign = TextAlign.Center
-                                )
+                        BadgedBox(
+                            badge = {
+                                Badge {
+                                    Text(
+                                        "8",
+                                        textAlign = TextAlign.Center
+                                    )
+                                }
                             }
                         ) {
                             Text("TAB")
@@ -220,12 +226,14 @@
                 // Tab with a pilled shape badge with the text `99+`.
                 Tab(
                     text = {
-                        BadgeBox(
-                            badgeContent = {
-                                Text(
-                                    "99+",
-                                    textAlign = TextAlign.Center
-                                )
+                        BadgedBox(
+                            badge = {
+                                Badge {
+                                    Text(
+                                        "99+",
+                                        textAlign = TextAlign.Center
+                                    )
+                                }
                             }
                         ) {
                             Text("TAB")
@@ -250,12 +258,14 @@
                 // A round badge with the text `8` attached to a leading icon tab.
                 LeadingIconTab(
                     icon = {
-                        BadgeBox(
-                            badgeContent = {
-                                Text(
-                                    "8",
-                                    textAlign = TextAlign.Center
-                                )
+                        BadgedBox(
+                            badge = {
+                                Badge {
+                                    Text(
+                                        "8",
+                                        textAlign = TextAlign.Center
+                                    )
+                                }
                             }
                         ) {
                             Icon(Icons.Filled.Favorite, null)
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
index 9dade12..1160bf1 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/BadgeTest.kt
@@ -132,10 +132,33 @@
     }
 
     @Test
+    fun badgeBox_noContent_position() {
+        rule
+            .setMaterialContent {
+                BadgedBox(badge = { Badge(Modifier.testTag(TestBadgeTag)) }) {
+                    Icon(
+                        icon,
+                        null,
+                        modifier = Modifier.testTag(TestAnchorTag)
+                    )
+                }
+            }
+        val badge = rule.onNodeWithTag(TestBadgeTag)
+        val anchorBounds = rule.onNodeWithTag(TestAnchorTag).getUnclippedBoundsInRoot()
+        val badgeBounds = badge.getUnclippedBoundsInRoot()
+        badge.assertPositionInRootIsEqualTo(
+            expectedLeft =
+                anchorBounds.right + BadgeHorizontalOffset +
+                    max((BadgeRadius - badgeBounds.width) / 2, 0.dp),
+            expectedTop = -badgeBounds.height / 2
+        )
+    }
+
+    @Test
     fun badgeBox_shortContent_position() {
         rule
             .setMaterialContent {
-                BadgeBox(badgeContent = { Text("8") }) {
+                BadgedBox(badge = { Badge { Text("8") } }) {
                     Icon(
                         icon,
                         null,
@@ -162,7 +185,7 @@
     fun badgeBox_longContent_position() {
         rule
             .setMaterialContent {
-                BadgeBox(badgeContent = { Text("999+") }) {
+                BadgedBox(badge = { Badge { Text("999+") } }) {
                     Icon(
                         icon,
                         null,
@@ -253,12 +276,12 @@
     @Test
     fun badge_notMergingDescendants_withOwnContentDescription() {
         rule.setMaterialContent {
-            BadgeBox(
+            BadgedBox(
+                badge = {
+                    Badge { Text("99+") }
+                },
                 modifier = Modifier.testTag(TestBadgeTag).semantics {
                     this.contentDescription = "more than 99 new email"
-                },
-                badgeContent = {
-                    Text("99+")
                 }
             ) {
                 Text(
@@ -277,7 +300,7 @@
     @Test
     fun badgeBox_size() {
         rule.setMaterialContentForSizeAssertions {
-            BadgeBox(badgeContent = { Text("999+") }) {
+            BadgedBox(badge = { Badge { Text("999+") } }) {
                 Icon(icon, null)
             }
         }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
new file mode 100644
index 0000000..2efce3a
--- /dev/null
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/FloatingActionButtonScreenshotTest.kt
@@ -0,0 +1,126 @@
+/*
+ * 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.material
+
+import android.os.Build
+import androidx.compose.foundation.layout.Box
+import androidx.compose.foundation.layout.requiredSize
+import androidx.compose.foundation.layout.wrapContentSize
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.Favorite
+import androidx.compose.testutils.assertAgainstGolden
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.test.ExperimentalTestApi
+import androidx.compose.ui.test.captureToImage
+import androidx.compose.ui.test.center
+import androidx.compose.ui.test.down
+import androidx.compose.ui.test.hasClickAction
+import androidx.compose.ui.test.junit4.createComposeRule
+import androidx.compose.ui.test.onRoot
+import androidx.compose.ui.test.performGesture
+import androidx.compose.ui.unit.dp
+import androidx.test.ext.junit.runners.AndroidJUnit4
+import androidx.test.filters.MediumTest
+import androidx.test.filters.SdkSuppress
+import androidx.test.screenshot.AndroidXScreenshotTestRule
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+
+@MediumTest
+@RunWith(AndroidJUnit4::class)
+@SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
+@OptIn(ExperimentalTestApi::class)
+class FloatingActionButtonScreenshotTest {
+
+    @get:Rule
+    val rule = createComposeRule()
+
+    @get:Rule
+    val screenshotRule = AndroidXScreenshotTestRule(GOLDEN_MATERIAL)
+
+    @Test
+    fun icon() {
+        rule.setMaterialContent {
+            FloatingActionButton(onClick = { }) {
+                Icon(Icons.Filled.Favorite, contentDescription = null)
+            }
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_icon")
+    }
+
+    @Test
+    fun text() {
+        rule.setMaterialContent {
+            ExtendedFloatingActionButton(
+                text = { Text("EXTENDED") },
+                onClick = {}
+            )
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_text")
+    }
+
+    @Test
+    fun textAndIcon() {
+        rule.setMaterialContent {
+            ExtendedFloatingActionButton(
+                text = { Text("EXTENDED") },
+                icon = { Icon(Icons.Filled.Favorite, contentDescription = null) },
+                onClick = {}
+            )
+        }
+
+        rule.onNode(hasClickAction())
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_textAndIcon")
+    }
+
+    @Test
+    fun ripple() {
+        rule.mainClock.autoAdvance = false
+
+        rule.setMaterialContent {
+            Box(Modifier.requiredSize(100.dp, 100.dp).wrapContentSize()) {
+                FloatingActionButton(onClick = { }) {
+                    Icon(Icons.Filled.Favorite, contentDescription = null)
+                }
+            }
+        }
+
+        // Start ripple
+        rule.onNode(hasClickAction())
+            .performGesture { down(center) }
+
+        // Advance past the tap timeout
+        rule.mainClock.advanceTimeBy(100)
+
+        rule.waitForIdle()
+        // Ripples are drawn on the RenderThread, not the main (UI) thread, so we can't
+        // properly wait for synchronization. Instead just wait until after the ripples are
+        // finished animating.
+        Thread.sleep(300)
+
+        rule.onRoot()
+            .captureToImage()
+            .assertAgainstGolden(screenshotRule, "fab_ripple")
+    }
+}
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
index 1fda1bc..071c5f2 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/OutlinedTextFieldScreenshotTest.kt
@@ -44,6 +44,7 @@
 import androidx.compose.ui.test.performGesture
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.up
+import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.style.TextAlign
@@ -82,8 +83,9 @@
     fun outlinedTextField_withInput() {
         rule.setMaterialContent {
             Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+                val text = "Text"
                 OutlinedTextField(
-                    value = "Text",
+                    value = TextFieldValue(text = text, selection = TextRange(text.length)),
                     onValueChange = {},
                     label = { Text("Label") },
                     modifier = Modifier.requiredWidth(280.dp)
@@ -152,8 +154,9 @@
     fun outlinedTextField_error_focused() {
         rule.setMaterialContent {
             Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+                val text = "Input"
                 OutlinedTextField(
-                    value = "Input",
+                    value = TextFieldValue(text = text, selection = TextRange(text.length)),
                     onValueChange = {},
                     label = { Text("Label") },
                     isError = true,
@@ -188,8 +191,9 @@
     fun outlinedTextField_textColor_fallbackToContentColor() {
         rule.setMaterialContent {
             CompositionLocalProvider(LocalContentColor provides Color.Magenta) {
+                val text = "Hello, world!"
                 OutlinedTextField(
-                    value = "Hello, world!",
+                    value = TextFieldValue(text = text, selection = TextRange(text.length)),
                     onValueChange = {},
                     modifier = Modifier.testTag(TextFieldTag).requiredWidth(280.dp)
                 )
@@ -202,8 +206,9 @@
     @Test
     fun outlinedTextField_multiLine_withLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             OutlinedTextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 label = { Text("Label") },
                 modifier = Modifier.requiredHeight(300.dp)
@@ -218,8 +223,9 @@
     @Test
     fun outlinedTextField_multiLine_withoutLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             OutlinedTextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.requiredHeight(300.dp)
                     .requiredWidth(280.dp)
@@ -286,8 +292,9 @@
     @Test
     fun outlinedTextField_singleLine_withLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             OutlinedTextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 singleLine = true,
                 label = { Text("Label") },
@@ -301,8 +308,9 @@
     @Test
     fun outlinedTextField_singleLine_withoutLabel_textCenteredVertically() {
         rule.setMaterialContent {
+            val text = "Text"
             OutlinedTextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 singleLine = true,
                 modifier = Modifier.testTag(TextFieldTag).requiredWidth(280.dp)
@@ -484,8 +492,9 @@
     @Test
     fun outlinedTextField_textCenterAligned() {
         rule.setMaterialContent {
+            val text = "Hello world"
             OutlinedTextField(
-                value = "Hello world",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.width(300.dp).testTag(TextFieldTag),
                 textStyle = TextStyle(textAlign = TextAlign.Center),
@@ -499,8 +508,9 @@
     @Test
     fun outlinedTextField_textAlignedToEnd() {
         rule.setMaterialContent {
+            val text = "Hello world"
             OutlinedTextField(
-                value = "Hello world",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.fillMaxWidth().testTag(TextFieldTag),
                 textStyle = TextStyle(textAlign = TextAlign.End),
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
index ee34435..42a0998 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/textfield/TextFieldScreenshotTest.kt
@@ -44,6 +44,7 @@
 import androidx.compose.ui.test.performGesture
 import androidx.compose.ui.test.swipeLeft
 import androidx.compose.ui.test.up
+import androidx.compose.ui.text.TextRange
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.input.TextFieldValue
 import androidx.compose.ui.text.style.TextAlign
@@ -81,8 +82,9 @@
     fun textField_withInput() {
         rule.setMaterialContent {
             Box(Modifier.semantics(mergeDescendants = true) {}.testTag(TextFieldTag)) {
+                val text = "Text"
                 TextField(
-                    value = "Text",
+                    value = TextFieldValue(text = text, selection = TextRange(text.length)),
                     onValueChange = {},
                     label = { Text("Label") },
                     modifier = Modifier.requiredWidth(280.dp)
@@ -150,8 +152,9 @@
     @Test
     fun textField_error_focused() {
         rule.setMaterialContent {
+            val text = "Input"
             TextField(
-                value = "Input",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 label = { Text("Label") },
                 isError = true,
@@ -183,8 +186,9 @@
     fun textField_textColor_fallbackToContentColor() {
         rule.setMaterialContent {
             CompositionLocalProvider(LocalContentColor provides Color.Green) {
+                val text = "Hello, world!"
                 TextField(
-                    value = "Hello, world!",
+                    value = TextFieldValue(text = text, selection = TextRange(text.length)),
                     onValueChange = {},
                     modifier = Modifier.requiredWidth(280.dp).testTag(TextFieldTag)
                 )
@@ -197,8 +201,9 @@
     @Test
     fun textField_multiLine_withLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             TextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 label = { Text("Label") },
                 modifier = Modifier.requiredHeight(300.dp)
@@ -213,8 +218,9 @@
     @Test
     fun textField_multiLine_withoutLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             TextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.requiredHeight(300.dp)
                     .requiredWidth(280.dp)
@@ -281,8 +287,9 @@
     @Test
     fun textField_singleLine_withLabel_textAlignedToTop() {
         rule.setMaterialContent {
+            val text = "Text"
             TextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 singleLine = true,
                 label = { Text("Label") },
@@ -296,8 +303,9 @@
     @Test
     fun textField_singleLine_withoutLabel_textCenteredVertically() {
         rule.setMaterialContent {
+            val text = "Text"
             TextField(
-                value = "Text",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 singleLine = true,
                 modifier = Modifier.requiredWidth(280.dp).testTag(TextFieldTag)
@@ -472,8 +480,9 @@
     @Test
     fun textField_textCenterAligned() {
         rule.setMaterialContent {
+            val text = "Hello world"
             TextField(
-                value = "Hello world",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.width(300.dp).testTag(TextFieldTag),
                 textStyle = TextStyle(textAlign = TextAlign.Center),
@@ -487,8 +496,9 @@
     @Test
     fun textField_textAlignedToEnd() {
         rule.setMaterialContent {
+            val text = "Hello world"
             TextField(
-                value = "Hello world",
+                value = TextFieldValue(text = text, selection = TextRange(text.length)),
                 onValueChange = {},
                 modifier = Modifier.fillMaxWidth().testTag(TextFieldTag),
                 textStyle = TextStyle(textAlign = TextAlign.End),
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
index 185fa4e..780bd0f 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Badge.kt
@@ -39,34 +39,29 @@
 import androidx.compose.ui.unit.sp
 
 /**
- * A BadgeBox is used to decorate [content] with a badge that can contain dynamic information, such
+ * A BadgeBox is used to decorate [content] with a [badge] that can contain dynamic information,
+ * such
  * as the presence of a new notification or a number of pending requests. Badges can be icon only
  * or contain short text.
  *
- * A common use case is to display a badge in the upper right corner of bottom navigation items.
+ * A common use case is to display a badge with bottom navigation items.
  * For more information, see [Bottom Navigation](https://material.io/components/bottom-navigation#behavior)
  *
  * A simple icon with badge example looks like:
  * @sample androidx.compose.material.samples.BottomNavigationItemWithBadge
  *
+ * @param badge the badge to be displayed - typically a [Badge]
  * @param modifier optional [Modifier] for this item
- * @param backgroundColor the background color for the badge
- * @param contentColor the color of label text rendered in the badge
- * @param badgeContent optional content to be rendered inside the badge
  * @param content the anchor to which this badge will be positioned
  *
  */
 @ExperimentalMaterialApi
 @Composable
-fun BadgeBox(
+fun BadgedBox(
+    badge: @Composable BoxScope.() -> Unit,
     modifier: Modifier = Modifier,
-    backgroundColor: Color = MaterialTheme.colors.error,
-    contentColor: Color = contentColorFor(backgroundColor),
-    badgeContent: @Composable (RowScope.() -> Unit)? = null,
     content: @Composable BoxScope.() -> Unit,
 ) {
-    val badgeHorizontalOffset =
-        if (badgeContent != null) BadgeWithContentHorizontalOffset else BadgeHorizontalOffset
     Layout(
         {
             Box(
@@ -74,11 +69,9 @@
                 contentAlignment = Alignment.Center,
                 content = content
             )
-            Badge(
+            Box(
                 modifier = Modifier.layoutId("badge"),
-                backgroundColor = backgroundColor,
-                contentColor = contentColor,
-                content = badgeContent
+                content = badge
             )
         },
         modifier = modifier
@@ -107,6 +100,12 @@
                 LastBaseline to lastBaseline
             )
         ) {
+            // Use the width of the badge to infer whether it has any content (based on radius used
+            // in [Badge]) and determine its horizontal offset.
+            val hasContent = badgePlaceable.width > (2 * BadgeRadius.roundToPx())
+            val badgeHorizontalOffset =
+                if (hasContent) BadgeWithContentHorizontalOffset else BadgeHorizontalOffset
+
             anchorPlaceable.placeRelative(0, 0)
             val badgeX = anchorPlaceable.width + badgeHorizontalOffset.roundToPx()
             val badgeY = -badgePlaceable.height / 2
@@ -116,18 +115,21 @@
 }
 
 /**
- * Internal badge implementation to help [BadgeBox].
+ * Badge is a component that can contain dynamic information, such as the presence of a new
+ * notification or a number of pending requests. Badges can be icon only or contain short text.
+ *
+ * See [BadgedBox] for a top level layout that will properly place the badge relative to content
+ * such as text or an icon.
  *
  * @param modifier optional [Modifier] for this item
  * @param backgroundColor the background color for the badge
  * @param contentColor the color of label text rendered in the badge
  * @param content optional content to be rendered inside the badge
  *
- * @see BadgeBox
  */
 @ExperimentalMaterialApi
 @Composable
-internal fun Badge(
+fun Badge(
     modifier: Modifier = Modifier,
     backgroundColor: Color = MaterialTheme.colors.error,
     contentColor: Color = contentColorFor(backgroundColor),
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
index 50d21a7..9dffc43 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/FloatingActionButton.kt
@@ -168,22 +168,19 @@
         contentColor = contentColor,
         elevation = elevation
     ) {
-        Box(
+        val startPadding = if (icon == null) ExtendedFabTextPadding else ExtendedFabIconPadding
+        Row(
             modifier = Modifier.padding(
-                start = ExtendedFabTextPadding,
+                start = startPadding,
                 end = ExtendedFabTextPadding
             ),
-            contentAlignment = Alignment.Center
+            verticalAlignment = Alignment.CenterVertically
         ) {
-            if (icon == null) {
-                text()
-            } else {
-                Row(verticalAlignment = Alignment.CenterVertically) {
-                    icon()
-                    Spacer(Modifier.width(ExtendedFabIconPadding))
-                    text()
-                }
+            if (icon != null) {
+                icon()
+                Spacer(Modifier.width(ExtendedFabIconPadding))
             }
+            text()
         }
     }
 }
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
index bcdf4be..761fe64 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/ListSaver.kt
@@ -27,7 +27,7 @@
  *
  * @sample androidx.compose.runtime.saveable.samples.ListSaverSample
  */
-fun <Original : Any, Saveable : Any> listSaver(
+fun <Original, Saveable> listSaver(
     save: SaverScope.(value: Original) -> List<Saveable>,
     restore: (list: List<Saveable>) -> Original?
 ): Saver<Original, Any> = @Suppress("UNCHECKED_CAST") Saver(
@@ -35,7 +35,9 @@
         val list = save(it)
         for (index in list.indices) {
             val item = list[index]
-            require(canBeSaved(item))
+            if (item != null) {
+                require(canBeSaved(item))
+            }
         }
         if (list.isNotEmpty()) ArrayList(list) else null
     },
diff --git a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/MapSaver.kt b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/MapSaver.kt
index 6ad0912..9bfa09b 100644
--- a/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/MapSaver.kt
+++ b/compose/runtime/runtime-saveable/src/commonMain/kotlin/androidx/compose/runtime/saveable/MapSaver.kt
@@ -27,12 +27,12 @@
  *
  * @sample androidx.compose.runtime.saveable.samples.MapSaverSample
  */
-fun <T : Any> mapSaver(
-    save: SaverScope.(value: T) -> Map<String, Any>,
-    restore: (Map<String, Any>) -> T?
-) = listSaver<T, Any>(
+fun <T> mapSaver(
+    save: SaverScope.(value: T) -> Map<String, Any?>,
+    restore: (Map<String, Any?>) -> T?
+) = listSaver<T, Any?>(
     save = {
-        mutableListOf<Any>().apply {
+        mutableListOf<Any?>().apply {
             save(it).forEach { entry ->
                 add(entry.key)
                 add(entry.value)
@@ -40,7 +40,7 @@
         }
     },
     restore = { list ->
-        val map = mutableMapOf<String, Any>()
+        val map = mutableMapOf<String, Any?>()
         check(list.size.rem(2) == 0)
         var index = 0
         while (index < list.size) {
diff --git a/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/ListSaverTest.kt b/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/ListSaverTest.kt
index ffd8d9a..99d6d4d 100644
--- a/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/ListSaverTest.kt
+++ b/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/ListSaverTest.kt
@@ -58,11 +58,41 @@
         assertThat(savedList).isInstanceOf(ArrayList::class.java)
         assertThat(savedList).isEqualTo(listOf("One", "Two"))
     }
+
+    @Test
+    fun nullableListItemsAreSupported() {
+        val original = NullableSize(null, 3)
+        val saved = with(NullableSizeSaver) {
+            allowingScope.save(original)
+        }
+
+        assertThat(saved).isNotNull()
+        assertThat(NullableSizeSaver.restore(saved!!))
+            .isEqualTo(original)
+    }
+
+    @Test
+    fun nullableTypeIsSupported() {
+        val saved = with(NullableSizeSaver) {
+            allowingScope.save(null)
+        }
+
+        assertThat(saved).isNotNull()
+        assertThat(NullableSizeSaver.restore(saved!!))
+            .isEqualTo(NullableSize(null, null))
+    }
 }
 
 private data class Size(val x: Int, val y: Int)
 
+private data class NullableSize(val x: Int?, val y: Int?)
+
 private val SizeSaver = listSaver<Size, Int>(
     save = { listOf(it.x, it.y) },
     restore = { Size(it[0], it[1]) }
 )
+
+private val NullableSizeSaver = listSaver<NullableSize?, Int?>(
+    save = { listOf(it?.x, it?.y) },
+    restore = { NullableSize(it[0], it[1]) }
+)
diff --git a/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/MapSaverTest.kt b/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/MapSaverTest.kt
index 8c939d2..375b6b9 100644
--- a/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/MapSaverTest.kt
+++ b/compose/runtime/runtime-saveable/src/test/java/androidx/compose/runtime/saveable/MapSaverTest.kt
@@ -53,6 +53,29 @@
             onlyInts.save(User("John", 30))
         }
     }
+
+    @Test
+    fun nullableMapItemsAreSupported() {
+        val original = NullableUser(null, 30)
+        val saved = with(NullableUserSaver) {
+            allowingScope.save(original)
+        }
+
+        assertThat(saved).isNotNull()
+        assertThat(NullableUserSaver.restore(saved!!))
+            .isEqualTo(original)
+    }
+
+    @Test
+    fun nullableTypeIsSupported() {
+        val saved = with(NullableUserSaver) {
+            allowingScope.save(null)
+        }
+
+        assertThat(saved).isNotNull()
+        assertThat(NullableUserSaver.restore(saved!!))
+            .isEqualTo(NullableUser(null, null))
+    }
 }
 
 private data class User(val name: String, val age: Int)
@@ -64,4 +87,15 @@
         save = { mapOf(nameKey to it.name, ageKey to it.age) },
         restore = { User(it[nameKey] as String, it[ageKey] as Int) }
     )
+}
+
+private data class NullableUser(val name: String?, val age: Int?)
+
+private val NullableUserSaver = run {
+    val nameKey = "Name"
+    val ageKey = "Age"
+    mapSaver<NullableUser?>(
+        save = { mapOf(nameKey to it?.name, ageKey to it?.age) },
+        restore = { NullableUser(it[nameKey] as String?, it[ageKey] as Int?) }
+    )
 }
\ No newline at end of file
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 864628b..23b73ce 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
@@ -1216,7 +1216,9 @@
         parentProvider = parentContext.getCompositionLocalScope()
         providersInvalidStack.push(providersInvalid.asInt())
         providersInvalid = changed(parentProvider)
-        collectParameterInformation = parentContext.collectingParameterInformation
+        if (!collectParameterInformation) {
+            collectParameterInformation = parentContext.collectingParameterInformation
+        }
         resolveCompositionLocal(LocalInspectionTables, parentProvider)?.let {
             it.add(slotTable)
             parentContext.recordInspectionTable(it)
diff --git a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
index aec9166..8affa82 100644
--- a/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
+++ b/compose/runtime/runtime/src/commonMain/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserver.kt
@@ -53,7 +53,9 @@
      */
     private val readObserver: (Any) -> Unit = { state ->
         if (!isPaused) {
-            currentMap!!.addValue(state)
+            synchronized(applyMaps) {
+                currentMap!!.addValue(state)
+            }
         }
     }
 
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
index 66b607b..af48238 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/snapshots/SnapshotStateObserverTests.kt
@@ -324,6 +324,44 @@
         assertNull(threadException)
     }
 
+    @Test // regression test for 192677711, second case
+    fun tryToReproduceSecondRaceCondtion() {
+        var running = true
+        var threadException: Exception? = null
+        try {
+            thread {
+                try {
+                    while (running) {
+                        Snapshot.sendApplyNotifications()
+                    }
+                } catch (e: Exception) {
+                    threadException = e
+                }
+            }
+
+            for (i in 1..10000) {
+                val state1 by mutableStateOf(0)
+                var state2 by mutableStateOf(true)
+                val observer = SnapshotStateObserver({}).apply {
+                    start()
+                }
+                observer.observeReads(Unit, {}) {
+                    repeat(1000) {
+                        @Suppress("UNUSED_EXPRESSION")
+                        state1
+                        if (state2) {
+                            state2 = false
+                        }
+                    }
+                }
+                assertNull(threadException)
+            }
+        } finally {
+            running = false
+        }
+        assertNull(threadException)
+    }
+
     private fun runSimpleTest(
         block: (modelObserver: SnapshotStateObserver, data: MutableState<Int>) -> Unit
     ) {
diff --git a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
index 3bdf133..001f11a 100644
--- a/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
+++ b/compose/ui/ui-graphics/src/androidAndroidTest/kotlin/androidx/compose/ui/graphics/AndroidCanvasTest.kt
@@ -29,21 +29,21 @@
 import android.view.View
 import android.view.ViewGroup
 import android.widget.FrameLayout
+import androidx.compose.testutils.captureToImage
 import androidx.compose.ui.geometry.Offset
 import androidx.compose.ui.geometry.Rect
 import androidx.compose.ui.geometry.Size
-import androidx.compose.testutils.captureToImage
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotEquals
-import org.junit.Assert.assertTrue
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
 import java.util.concurrent.CountDownLatch
 import java.util.concurrent.TimeUnit
+import org.junit.Assert.assertTrue
 
 @MediumTest
 @RunWith(AndroidJUnit4::class)
@@ -270,6 +270,21 @@
         assertEquals(bg, pixelMap[75, 76])
     }
 
+    /**
+     * Verify the difference in rgb channels is within the given threshold. This assumes
+     * a fully blended color within a bitmap so only RGB channels are verified
+     */
+    private fun verifyPixelWithThreshold(
+        color: Color,
+        expectedColor: Color,
+        threshold: Int
+    ): Boolean {
+        val diff = Math.abs(color.red - expectedColor.red) +
+            Math.abs(color.green - expectedColor.green) +
+            Math.abs(color.blue - expectedColor.blue)
+        return (diff * 255) <= threshold
+    }
+
     @Test
     fun testCornerPathEffect() {
         val width = 80
@@ -305,12 +320,15 @@
         )
 
         val composePixels = imageBitmap.toPixelMap()
-        for (i in 0 until 80) {
-            for (j in 0 until 80) {
-                assertEquals(
+        for (i in 0 until width) {
+            for (j in 0 until height) {
+                assertTrue(
                     "invalid color at i: " + i + ", " + j,
-                    composePixels[i, j].toArgb(),
-                    androidBitmap.getPixel(i, j)
+                    verifyPixelWithThreshold(
+                        composePixels[i, j],
+                        Color(androidBitmap.getPixel(i, j)),
+                        3
+                    )
                 )
             }
         }
diff --git a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
index 61b4ae8..35d0218 100644
--- a/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
+++ b/compose/ui/ui-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Brush.kt
@@ -51,7 +51,7 @@
          *
          * @param colorStops Colors and their offset in the gradient area
          * @param start Starting position of the linear gradient. This can be set to
-         * [Offset.Infinite] to position at the far right and bottom of the drawing area
+         * [Offset.Zero] to position at the far left and top of the drawing area
          * @param end Ending position of the linear gradient. This can be set to
          * [Offset.Infinite] to position at the far right and bottom of the drawing area
          * @param tileMode Determines the behavior for how the shader is to fill a region outside
@@ -87,7 +87,7 @@
          *
          * @param colors Colors to be rendered as part of the gradient
          * @param start Starting position of the linear gradient. This can be set to
-         * [Offset.Infinite] to position at the far right and bottom of the drawing area
+         * [Offset.Zero] to position at the far left and top of the drawing area
          * @param end Ending position of the linear gradient. This can be set to
          * [Offset.Infinite] to position at the far right and bottom of the drawing area
          * @param tileMode Determines the behavior for how the shader is to fill a region outside
diff --git a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
index 6904996..6370dc3 100644
--- a/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
+++ b/compose/ui/ui-inspection/src/androidTest/java/androidx/compose/ui/inspection/inspector/LayoutInspectorTreeTest.kt
@@ -49,6 +49,7 @@
 import androidx.compose.runtime.getValue
 import androidx.compose.runtime.mutableStateOf
 import androidx.compose.runtime.remember
+import androidx.compose.runtime.setValue
 import androidx.compose.runtime.tooling.CompositionData
 import androidx.compose.runtime.tooling.LocalInspectionTables
 import androidx.compose.ui.Alignment
@@ -66,6 +67,8 @@
 import androidx.compose.ui.semantics.semantics
 import androidx.compose.ui.semantics.text
 import androidx.compose.ui.test.junit4.createAndroidComposeRule
+import androidx.compose.ui.test.onNodeWithText
+import androidx.compose.ui.test.performClick
 import androidx.compose.ui.text.AnnotatedString
 import androidx.compose.ui.text.TextStyle
 import androidx.compose.ui.text.style.TextDecoration
@@ -763,12 +766,14 @@
     }
 
     @Composable
-    fun First() {
+    @Suppress("UNUSED_PARAMETER")
+    fun First(p1: Int) {
         Text("First")
     }
 
     @Composable
-    fun Second() {
+    @Suppress("UNUSED_PARAMETER")
+    fun Second(p2: Int) {
         Text("Second")
     }
 
@@ -778,11 +783,16 @@
 
         show {
             Inspectable(slotTableRecord) {
-                val showFirst by remember { mutableStateOf(true) }
-                Crossfade(showFirst) {
-                    when (it) {
-                        true -> First()
-                        false -> Second()
+                Column {
+                    var showFirst by remember { mutableStateOf(true) }
+                    Button(onClick = { showFirst = !showFirst }) {
+                        Text("Button")
+                    }
+                    Crossfade(showFirst) {
+                        when (it) {
+                            true -> First(p1 = 1)
+                            false -> Second(p2 = 2)
+                        }
                     }
                 }
             }
@@ -797,6 +807,17 @@
         val hash = packageNameHash(this.javaClass.name.substringBeforeLast('.'))
         assertThat(first.fileName).isEqualTo("LayoutInspectorTreeTest.kt")
         assertThat(first.packageHash).isEqualTo(hash)
+        assertThat(first.parameters.map { it.name }).contains("p1")
+
+        composeTestRule.onNodeWithText("Button").performClick()
+        composeTestRule.runOnIdle {
+            val second = builder.convert(androidComposeView)
+                .flatMap { flatten(it) }
+                .first { it.name == "Second" }
+            assertThat(second.fileName).isEqualTo("LayoutInspectorTreeTest.kt")
+            assertThat(second.packageHash).isEqualTo(hash)
+            assertThat(second.parameters.map { it.name }).contains("p2")
+        }
     }
 
     @Composable
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
index ba91108..9bf8205 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidLayoutDrawTest.kt
@@ -106,7 +106,6 @@
 import androidx.compose.ui.unit.offset
 import androidx.compose.ui.unit.toOffset
 import androidx.test.ext.junit.runners.AndroidJUnit4
-import androidx.test.filters.FlakyTest
 import androidx.test.filters.MediumTest
 import androidx.test.filters.SdkSuppress
 import org.junit.Assert.assertEquals
@@ -2916,12 +2915,11 @@
         validateSquareColors(outerColor = Color.Blue, innerColor = Color.White, size = 10)
     }
 
-    @FlakyTest
     @Test
     fun makingItemLarger() {
         var height by mutableStateOf(30)
-        var actualHeight = 0
         var latch = CountDownLatch(1)
+        var composeView: View? = null
         activityTestRule.runOnUiThread {
             val linearLayout = LinearLayout(activity)
             linearLayout.orientation = LinearLayout.VERTICAL
@@ -2943,29 +2941,30 @@
                     10000f
                 )
             )
-            child.viewTreeObserver.addOnPreDrawListener {
-                actualHeight = child.measuredHeight
-                latch.countDown()
-                true
-            }
             child.setContent {
-                Layout({}) { _, constraints ->
+                Layout(
+                    {},
+                    Modifier.onGloballyPositioned {
+                        latch.countDown()
+                    }
+                ) { _, constraints ->
                     layout(constraints.maxWidth, height.coerceAtMost(constraints.maxHeight)) {}
                 }
             }
+            composeView = child
         }
 
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         latch = CountDownLatch(1)
 
         activityTestRule.runOnUiThread {
-            assertEquals(height, actualHeight)
+            assertEquals(height, composeView!!.measuredHeight)
             height = 60
         }
 
         assertTrue(latch.await(1, TimeUnit.SECONDS))
         activityTestRule.runOnUiThread {
-            assertEquals(height, actualHeight)
+            assertEquals(height, composeView!!.measuredHeight)
         }
     }
 
diff --git a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
index 215c40a..eb603a3 100644
--- a/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
+++ b/compose/ui/ui/src/desktopMain/kotlin/androidx/compose/ui/input/pointer/PointerEvent.desktop.kt
@@ -16,6 +16,8 @@
 
 package androidx.compose.ui.input.pointer
 
+import androidx.compose.ui.platform.DesktopPlatform
+import java.awt.event.InputEvent
 import java.awt.event.MouseEvent
 
 /**
@@ -37,8 +39,46 @@
         internalPointerEvent: InternalPointerEvent?
     ) : this(changes, internalPointerEvent?.mouseEvent)
 
+    val buttons = PointerButtons(mouseEvent?.modifiersEx ?: 0)
+
+    val keyboardModifiers = PointerKeyboardModifiers(mouseEvent?.modifiersEx ?: 0)
+
     /**
      * @param changes The changes.
      */
     actual constructor(changes: List<PointerInputChange>) : this(changes, mouseEvent = null)
+}
+
+@Suppress("INLINE_CLASS_DEPRECATED")
+inline class PointerButtons(val value: Int) {
+    private val isMacOsCtrlClick
+        get() = (
+            DesktopPlatform.Current == DesktopPlatform.MacOS &&
+                ((value and InputEvent.BUTTON1_DOWN_MASK) != 0) &&
+                ((value and InputEvent.CTRL_DOWN_MASK) != 0)
+            )
+
+    val isPrimaryPressed
+        get() = (value and InputEvent.BUTTON1_DOWN_MASK) != 0 && !isMacOsCtrlClick
+
+    val isSecondaryPressed: Boolean
+        get() = ((value and InputEvent.BUTTON3_DOWN_MASK) != 0) || isMacOsCtrlClick
+
+    val isTertiaryPressed: Boolean
+        get() = (value and InputEvent.BUTTON2_DOWN_MASK) != 0
+}
+
+@Suppress("INLINE_CLASS_DEPRECATED")
+inline class PointerKeyboardModifiers(val value: Int) {
+    val isAltPressed
+        get() = value and InputEvent.ALT_DOWN_MASK != 0
+
+    val isCtrlPressed
+        get() = value and InputEvent.CTRL_DOWN_MASK != 0
+
+    val isMetaPressed
+        get() = value and InputEvent.META_DOWN_MASK != 0
+
+    val isShiftPressed
+        get() = value and InputEvent.SHIFT_DOWN_MASK != 0
 }
\ No newline at end of file
diff --git a/core/core/api/current.txt b/core/core/api/current.txt
index cbc8cc2..d3880fa 100644
--- a/core/core/api/current.txt
+++ b/core/core/api/current.txt
@@ -989,20 +989,12 @@
 
   public final class IntentCompat {
     method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
-    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
     method public static android.content.Intent makeMainSelectorActivity(String, String);
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
-    field public static final String ACTION_UNUSED_APP_RESTRICTIONS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
-    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
-    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
     field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
     field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
     field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
     field public static final String EXTRA_TIME = "android.intent.extra.TIME";
-    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
-    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
-    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
-    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
   }
 
   public final class LocusIdCompat {
@@ -1019,6 +1011,17 @@
     method public static String![] matchesMany(String![]?, String);
   }
 
+  public final class PackageManagerCompat {
+    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
+    field public static final String ACTION_PERMISSION_REVOCATION_SETTINGS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
+    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
+    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
+    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
+    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
+    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
+  }
+
   public final class PermissionChecker {
     method public static int checkCallingOrSelfPermission(android.content.Context, String);
     method public static int checkCallingPermission(android.content.Context, String, String?);
diff --git a/core/core/api/public_plus_experimental_current.txt b/core/core/api/public_plus_experimental_current.txt
index 334f7dd..bfe8097 100644
--- a/core/core/api/public_plus_experimental_current.txt
+++ b/core/core/api/public_plus_experimental_current.txt
@@ -989,20 +989,12 @@
 
   public final class IntentCompat {
     method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
-    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
     method public static android.content.Intent makeMainSelectorActivity(String, String);
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
-    field public static final String ACTION_UNUSED_APP_RESTRICTIONS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
-    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
-    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
     field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
     field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
     field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
     field public static final String EXTRA_TIME = "android.intent.extra.TIME";
-    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
-    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
-    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
-    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
   }
 
   public final class LocusIdCompat {
@@ -1019,6 +1011,17 @@
     method public static String![] matchesMany(String![]?, String);
   }
 
+  public final class PackageManagerCompat {
+    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
+    field public static final String ACTION_PERMISSION_REVOCATION_SETTINGS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
+    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
+    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
+    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
+    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
+    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
+  }
+
   public final class PermissionChecker {
     method public static int checkCallingOrSelfPermission(android.content.Context, String);
     method public static int checkCallingPermission(android.content.Context, String, String?);
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 61cafa1..1712a00 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1092,20 +1092,12 @@
 
   public final class IntentCompat {
     method public static android.content.Intent createManageUnusedAppRestrictionsIntent(android.content.Context, String);
-    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
     method public static android.content.Intent makeMainSelectorActivity(String, String);
     field public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
-    field public static final String ACTION_UNUSED_APP_RESTRICTIONS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
-    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
-    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
     field public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
     field public static final String EXTRA_HTML_TEXT = "android.intent.extra.HTML_TEXT";
     field public static final String EXTRA_START_PLAYBACK = "android.intent.extra.START_PLAYBACK";
     field public static final String EXTRA_TIME = "android.intent.extra.TIME";
-    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
-    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
-    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
-    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
   }
 
   public final class LocusIdCompat {
@@ -1122,6 +1114,17 @@
     method public static String![] matchesMany(String![]?, String);
   }
 
+  public final class PackageManagerCompat {
+    method public static int getUnusedAppRestrictionsStatus(android.content.Context);
+    field public static final String ACTION_PERMISSION_REVOCATION_SETTINGS = "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+    field public static final int APP_HIBERNATION_DISABLED = 5; // 0x5
+    field public static final int APP_HIBERNATION_ENABLED = 4; // 0x4
+    field public static final int PERMISSION_REVOCATION_DISABLED = 3; // 0x3
+    field public static final int PERMISSION_REVOCATION_ENABLED = 2; // 0x2
+    field public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1; // 0x1
+    field public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0; // 0x0
+  }
+
   public final class PermissionChecker {
     method @androidx.core.content.PermissionChecker.PermissionResult public static int checkCallingOrSelfPermission(android.content.Context, String);
     method @androidx.core.content.PermissionChecker.PermissionResult public static int checkCallingPermission(android.content.Context, String, String?);
diff --git a/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
index 4685da9..0b98b7c 100644
--- a/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/content/IntentCompatTest.java
@@ -26,13 +26,6 @@
 import static android.os.Build.VERSION_CODES.R;
 import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
 
-import static androidx.core.content.IntentCompat.APP_HIBERNATION_DISABLED;
-import static androidx.core.content.IntentCompat.APP_HIBERNATION_ENABLED;
-import static androidx.core.content.IntentCompat.PERMISSION_REVOCATION_DISABLED;
-import static androidx.core.content.IntentCompat.PERMISSION_REVOCATION_ENABLED;
-import static androidx.core.content.IntentCompat.UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE;
-import static androidx.core.content.IntentCompat.UNUSED_APP_RESTRICTION_STATUS_UNKNOWN;
-
 import static com.google.common.truth.Truth.assertThat;
 
 import static org.junit.Assert.assertThrows;
@@ -50,7 +43,9 @@
 import android.content.pm.ProviderInfo;
 import android.content.pm.ResolveInfo;
 import android.net.Uri;
+import android.os.Build;
 
+import androidx.annotation.RequiresApi;
 import androidx.test.core.app.ApplicationProvider;
 import androidx.test.ext.junit.runners.AndroidJUnit4;
 import androidx.test.filters.SdkSuppress;
@@ -201,105 +196,11 @@
                         mContext, PACKAGE_NAME));
     }
 
-    @Test
-    @SdkSuppress(minSdkVersion = 31)
-    public void getUnusedAppRestrictionsStatus_api31Plus_disabled_returnsAppHibernationDisabled() {
-        // Mark the application as exempt from app hibernation, so the feature is disabled
-        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(true);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                APP_HIBERNATION_DISABLED);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = 31)
-    public void getUnusedAppRestrictionsStatus_api31Plus_enabled_returnsAppHibernationEnabled() {
-        // Mark the application as _not_ exempt from app hibernation, so the feature is enabled
-        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(false);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                APP_HIBERNATION_ENABLED);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = R, maxSdkVersion = R)
-    public void getUnusedAppRestrictionsStatus_api30_disabled_returnsPermRevocationDisabled() {
-        // Mark the application as exempt from permission revocation, so the feature is disabled
-        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(true);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                PERMISSION_REVOCATION_DISABLED);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = R)
-    public void getUnusedAppRestrictionsStatus_api30Plus_enabled_returnsPermRevocationEnabled() {
-        // Mark the application as _not_ exempt from permission revocation, so the feature is
-        // enabled
-        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(false);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                PERMISSION_REVOCATION_ENABLED);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
-    public void getUnusedAppRestrictionsStatus_preApi30_noRevocationApp_returnsNotAvailable() {
-        // Don't install an app that can resolve the permission auto-revocation intent
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
-    public void getUnusedAppRestrictionsStatus_preApi30_noVerifierRevokeApp_returnsNotAvailable() {
-        setupPermissionRevocationApps(Arrays.asList(NON_VERIFIER_PACKAGE_NAME));
-        // Do not set this app as the Verifier on the device
-        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
-                NON_VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_DENIED);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
-    public void getUnusedAppRestrictionsStatus_preApi30_verifierRevocationApp_returnsUnknown() {
-        setupPermissionRevocationApps(Arrays.asList(VERIFIER_PACKAGE_NAME));
-        // Set this app as the Verifier on the device
-        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
-                VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_GRANTED);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                UNUSED_APP_RESTRICTION_STATUS_UNKNOWN);
-    }
-
-    @Test
-    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
-    public void getUnusedAppRestrictionsStatus_preApi30_manyVerifierRevocationApps_doesNotThrow() {
-        setupPermissionRevocationApps(Arrays.asList(VERIFIER_PACKAGE_NAME, VERIFIER_PACKAGE_NAME2));
-        // Set both apps as the Verifier on the device, but we should have a graceful failure.
-        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
-                VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_GRANTED);
-        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
-                VERIFIER_PACKAGE_NAME2)).thenReturn(PERMISSION_GRANTED);
-
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                UNUSED_APP_RESTRICTION_STATUS_UNKNOWN);
-    }
-
-    @Test
-    @SdkSuppress(maxSdkVersion = LOLLIPOP)
-    public void getUnusedAppRestrictionsStatus_preApi23_returnsFeatureNotAvailable() {
-        assertThat(IntentCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
-                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
-    }
-
     /**
      * Setup applications that can handle unused app restriction features. In this case,
      * they are permission revocation apps.
      */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
     private void setupPermissionRevocationApps(List<String> packageNames) {
         List<ResolveInfo> resolveInfos = new ArrayList<>();
 
diff --git a/core/core/src/androidTest/java/androidx/core/content/PackageManagerCompatTest.java b/core/core/src/androidTest/java/androidx/core/content/PackageManagerCompatTest.java
new file mode 100644
index 0000000..bdf5820
--- /dev/null
+++ b/core/core/src/androidTest/java/androidx/core/content/PackageManagerCompatTest.java
@@ -0,0 +1,206 @@
+/*
+ * 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.core.content;
+
+import static android.content.pm.PackageManager.PERMISSION_DENIED;
+import static android.content.pm.PackageManager.PERMISSION_GRANTED;
+import static android.os.Build.VERSION_CODES.LOLLIPOP;
+import static android.os.Build.VERSION_CODES.M;
+import static android.os.Build.VERSION_CODES.Q;
+import static android.os.Build.VERSION_CODES.R;
+
+import static androidx.core.content.PackageManagerCompat.APP_HIBERNATION_DISABLED;
+import static androidx.core.content.PackageManagerCompat.APP_HIBERNATION_ENABLED;
+import static androidx.core.content.PackageManagerCompat.PERMISSION_REVOCATION_DISABLED;
+import static androidx.core.content.PackageManagerCompat.PERMISSION_REVOCATION_ENABLED;
+import static androidx.core.content.PackageManagerCompat.UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE;
+import static androidx.core.content.PackageManagerCompat.UNUSED_APP_RESTRICTION_STATUS_UNKNOWN;
+
+import static com.google.common.truth.Truth.assertThat;
+
+import static org.mockito.ArgumentMatchers.eq;
+import static org.mockito.ArgumentMatchers.nullable;
+import static org.mockito.Mockito.mock;
+import static org.mockito.Mockito.spy;
+import static org.mockito.Mockito.when;
+
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.ActivityInfo;
+import android.content.pm.ApplicationInfo;
+import android.content.pm.PackageManager;
+import android.content.pm.ProviderInfo;
+import android.content.pm.ResolveInfo;
+import android.os.Build;
+
+import androidx.annotation.RequiresApi;
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.ext.junit.runners.AndroidJUnit4;
+import androidx.test.filters.SdkSuppress;
+import androidx.test.filters.SmallTest;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+@SmallTest
+@RunWith(AndroidJUnit4.class)
+/** Tests for {@link PackageManagerCompat}. */
+public class PackageManagerCompatTest {
+
+    private Context mContext;
+    private PackageManager mPackageManager = mock(PackageManager.class);
+    private static final String VERIFIER_PACKAGE_NAME = "verifier.package.name";
+    private static final String VERIFIER_PACKAGE_NAME2 = "verifier.package.name.2";
+    private static final String NON_VERIFIER_PACKAGE_NAME = "non.verifier.package.name";
+
+    @Before
+    public void setUp() {
+        mContext = spy(ApplicationProvider.getApplicationContext());
+        when(mContext.getPackageManager()).thenReturn(mPackageManager);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 31)
+    public void getUnusedAppRestrictionsStatus_api31Plus_disabled_returnsAppHibernationDisabled() {
+        // Mark the application as exempt from app hibernation, so the feature is disabled
+        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(true);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                APP_HIBERNATION_DISABLED);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = 31)
+    public void getUnusedAppRestrictionsStatus_api31Plus_enabled_returnsAppHibernationEnabled() {
+        // Mark the application as _not_ exempt from app hibernation, so the feature is enabled
+        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(false);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                APP_HIBERNATION_ENABLED);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = R, maxSdkVersion = R)
+    public void getUnusedAppRestrictionsStatus_api30_disabled_returnsPermRevocationDisabled() {
+        // Mark the application as exempt from permission revocation, so the feature is disabled
+        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(true);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                PERMISSION_REVOCATION_DISABLED);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = R)
+    public void getUnusedAppRestrictionsStatus_api30Plus_enabled_returnsPermRevocationEnabled() {
+        // Mark the application as _not_ exempt from permission revocation, so the feature is
+        // enabled
+        when(mPackageManager.isAutoRevokeWhitelisted()).thenReturn(false);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                PERMISSION_REVOCATION_ENABLED);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
+    public void getUnusedAppRestrictionsStatus_preApi30_noRevocationApp_returnsNotAvailable() {
+        // Don't install an app that can resolve the permission auto-revocation intent
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
+    public void getUnusedAppRestrictionsStatus_preApi30_noVerifierRevokeApp_returnsNotAvailable() {
+        setupPermissionRevocationApps(Arrays.asList(NON_VERIFIER_PACKAGE_NAME));
+        // Do not set this app as the Verifier on the device
+        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
+                NON_VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_DENIED);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
+    public void getUnusedAppRestrictionsStatus_preApi30_verifierRevocationApp_returnsUnknown() {
+        setupPermissionRevocationApps(Arrays.asList(VERIFIER_PACKAGE_NAME));
+        // Set this app as the Verifier on the device
+        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
+                VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_GRANTED);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                UNUSED_APP_RESTRICTION_STATUS_UNKNOWN);
+    }
+
+    @Test
+    @SdkSuppress(minSdkVersion = M, maxSdkVersion = Q)
+    public void getUnusedAppRestrictionsStatus_preApi30_manyVerifierRevocationApps_doesNotThrow() {
+        setupPermissionRevocationApps(Arrays.asList(VERIFIER_PACKAGE_NAME, VERIFIER_PACKAGE_NAME2));
+        // Set both apps as the Verifier on the device, but we should have a graceful failure.
+        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
+                VERIFIER_PACKAGE_NAME)).thenReturn(PERMISSION_GRANTED);
+        when(mPackageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
+                VERIFIER_PACKAGE_NAME2)).thenReturn(PERMISSION_GRANTED);
+
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                UNUSED_APP_RESTRICTION_STATUS_UNKNOWN);
+    }
+
+    @Test
+    @SdkSuppress(maxSdkVersion = LOLLIPOP)
+    public void getUnusedAppRestrictionsStatus_preApi23_returnsFeatureNotAvailable() {
+        assertThat(PackageManagerCompat.getUnusedAppRestrictionsStatus(mContext)).isEqualTo(
+                UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE);
+    }
+
+    /**
+     * Setup applications that can handle unused app restriction features. In this case,
+     * they are permission revocation apps.
+     */
+    @RequiresApi(api = Build.VERSION_CODES.KITKAT)
+    private void setupPermissionRevocationApps(List<String> packageNames) {
+        List<ResolveInfo> resolveInfos = new ArrayList<>();
+
+        for (String packageName : packageNames) {
+            ApplicationInfo appInfo = new ApplicationInfo();
+            appInfo.uid = 12345;
+            appInfo.packageName = packageName;
+
+            ActivityInfo activityInfo = new ActivityInfo();
+            activityInfo.packageName = packageName;
+            activityInfo.name = "Name needed to keep toString() happy :)";
+            activityInfo.applicationInfo = appInfo;
+
+            ResolveInfo resolveInfo = new ResolveInfo();
+            resolveInfo.activityInfo = activityInfo;
+            resolveInfo.providerInfo = new ProviderInfo();
+            resolveInfo.providerInfo.name = "Name needed to keep toString() happy :)";
+
+            resolveInfos.add(resolveInfo);
+        }
+
+        // Mark the applications as being able to resolve the AUTO_REVOKE_PERMISSIONS intent
+        when(mPackageManager.queryIntentActivities(
+                nullable(Intent.class), eq(0))).thenReturn(resolveInfos);
+    }
+}
diff --git a/core/core/src/main/java/androidx/core/content/IntentCompat.java b/core/core/src/main/java/androidx/core/content/IntentCompat.java
index 33a93db..9ba25b6 100644
--- a/core/core/src/main/java/androidx/core/content/IntentCompat.java
+++ b/core/core/src/main/java/androidx/core/content/IntentCompat.java
@@ -18,27 +18,19 @@
 
 import static android.provider.Settings.ACTION_APPLICATION_DETAILS_SETTINGS;
 
-import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.core.content.PackageManagerCompat.ACTION_PERMISSION_REVOCATION_SETTINGS;
+import static androidx.core.content.PackageManagerCompat.UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE;
+import static androidx.core.content.PackageManagerCompat.getUnusedAppRestrictionsStatus;
+import static androidx.core.content.PackageManagerCompat.getVerifierRolePackageName;
 import static androidx.core.util.Preconditions.checkNotNull;
 
-import static java.lang.annotation.RetentionPolicy.SOURCE;
-
 import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.Intent;
-import android.content.pm.PackageManager;
-import android.content.pm.ResolveInfo;
 import android.net.Uri;
 import android.os.Build;
 
-import androidx.annotation.IntDef;
 import androidx.annotation.NonNull;
-import androidx.annotation.Nullable;
-import androidx.annotation.RequiresApi;
-import androidx.annotation.RestrictTo;
-
-import java.lang.annotation.Retention;
-import java.util.List;
 
 /**
  * Helper for accessing features in {@link android.content.Intent}.
@@ -68,14 +60,6 @@
     public static final String ACTION_CREATE_REMINDER = "android.intent.action.CREATE_REMINDER";
 
     /**
-     * Activity action: creates an intent to redirect the user to UI to turn on/off their
-     * unused app restriction settings.
-     */
-    @SuppressLint("ActionValue")
-    public static final String ACTION_UNUSED_APP_RESTRICTIONS =
-            "android.intent.action.AUTO_REVOKE_PERMISSIONS";
-
-    /**
      * A constant String that is associated with the Intent, used with
      * {@link android.content.Intent#ACTION_SEND} to supply an alternative to
      * {@link android.content.Intent#EXTRA_TEXT}
@@ -107,55 +91,6 @@
      */
     public static final String CATEGORY_LEANBACK_LAUNCHER = "android.intent.category.LEANBACK_LAUNCHER";
 
-    /** The status of Unused App Restrictions features is unknown for this app. */
-    public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0;
-
-    /** There are no available Unused App Restrictions features for this app. */
-    public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1;
-
-    /**
-     * Permission revocation is enabled for this app (i.e. permissions will be automatically
-     * reset if the app is unused).
-     *
-     * Note: this also means that app hibernation is not available for this app.
-     */
-    public static final int PERMISSION_REVOCATION_ENABLED = 2;
-
-    /**
-     * Permission revocation is disabled for this app (i.e. this app is exempt from having
-     * its permissions automatically removed).
-     *
-     * Note: this also means that app hibernation is not available for this app.
-     */
-    public static final int PERMISSION_REVOCATION_DISABLED = 3;
-
-    /**
-     * App hibernation is enabled for this app (i.e. this app will be hibernated and have its
-     * permissions revoked if the app is unused).
-     *
-     * Note: this also means that permission revocation is enabled for this app.
-     */
-    public static final int APP_HIBERNATION_ENABLED = 4;
-
-    /**
-     * App hibernation is disabled for this app (i.e. this app is exempt from being hibernated).
-     *
-     * Note: this also means that permission revocation is disabled for this app.
-     */
-    public static final int APP_HIBERNATION_DISABLED = 5;
-
-    /**
-     * The status of Unused App Restrictions features for this app.
-     * @hide
-     */
-    @IntDef({UNUSED_APP_RESTRICTION_STATUS_UNKNOWN, UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE,
-            PERMISSION_REVOCATION_ENABLED, PERMISSION_REVOCATION_DISABLED,
-            APP_HIBERNATION_ENABLED, APP_HIBERNATION_DISABLED})
-    @Retention(SOURCE)
-    @RestrictTo(LIBRARY)
-    public @interface UnusedAppRestrictionsStatus {
-    }
-
     /**
      * Make an Intent for the main activity of an application, without
      * specifying a specific activity to run but giving a selector to find
@@ -195,13 +130,14 @@
      * Make an Intent to redirect the user to UI to manage their unused app restriction settings
      * for a particular app (e.g. permission revocation, app hibernation).
      *
-     * Note: developers must first call {@link #getUnusedAppRestrictionsStatus(Context)} to make
+     * Note: developers must first call
+     * {@link PackageManagerCompat#getUnusedAppRestrictionsStatus(Context)} to make
      * sure that unused app restriction features are available on the device before attempting to
      * create an intent using this method. Any return value of this method besides
-     * {@link #UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE} indicates that at least one
-     * unused app restriction feature is available on the device. If the return value _is_
-     * {@link #UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE}, this method will throw an
-     * {@link UnsupportedOperationException}.
+     * {@link PackageManagerCompat#UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE} indicates that at
+     * least one unused app restriction feature is available on the device. If the return value _is_
+     * {@link PackageManagerCompat#UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE}, this method will
+     * throw an {@link UnsupportedOperationException}.
      *
      * Compatibility behavior:
      * <ul>
@@ -238,125 +174,22 @@
                     .setData(Uri.fromParts("package", packageName, /* fragment= */ null));
         }
 
-        Intent unusedAppRestrictionsIntent =
-                new Intent(ACTION_UNUSED_APP_RESTRICTIONS)
+        Intent permissionRevocationSettingsIntent =
+                new Intent(ACTION_PERMISSION_REVOCATION_SETTINGS)
                         .setData(Uri.fromParts(
                                 "package", packageName, /* fragment= */ null));
 
         // If the OS version is R, then no need to add any other data or flags, since we're
         // relying on the Android R system feature.
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
-            return unusedAppRestrictionsIntent;
+            return permissionRevocationSettingsIntent;
         } else {
             // Only allow apps with the Verifier role to resolve the intent.
             String verifierPackageName = getVerifierRolePackageName(context.getPackageManager());
             // The Verifier package name shouldn't be null since we've already checked that there
             // exists a Verifier on the device, but nonetheless we double-check here.
-            return unusedAppRestrictionsIntent
+            return permissionRevocationSettingsIntent
                     .setPackage(checkNotNull(verifierPackageName));
         }
     }
-
-    /**
-     * Returns the package name of the one and only Verifier on the device. If none exist, this
-     * will return {@code null}. Likewise, if multiple Verifiers exist, this method will return
-     * the first Verifier's package name.
-     */
-    @Nullable
-    private static String getVerifierRolePackageName(PackageManager packageManager) {
-        Intent unusedAppRestrictionsIntent =
-                new Intent(ACTION_UNUSED_APP_RESTRICTIONS)
-                        .setData(Uri.fromParts(
-                                "package", "com.example", /* fragment= */ null));
-        List<ResolveInfo> intentResolvers =
-                packageManager.queryIntentActivities(unusedAppRestrictionsIntent, /* flags= */ 0);
-
-        String verifierPackageName = null;
-
-        for (ResolveInfo intentResolver: intentResolvers) {
-            String packageName = intentResolver.activityInfo.packageName;
-            if (packageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
-                    packageName) != PackageManager.PERMISSION_GRANTED) {
-                continue;
-            }
-
-            if (verifierPackageName != null) {
-                // This shouldn't happen, but we fail gracefully nonetheless and avoid throwing an
-                // exception, instead returning the first package name with the Verifier role
-                // that we found.
-                return verifierPackageName;
-            }
-            verifierPackageName = packageName;
-        }
-
-        return verifierPackageName;
-    }
-
-    /**
-     * Returns the status of Unused App Restriction features for the current application, i.e.
-     * whether the features are available and if so, enabled for the application.
-     *
-     * Compatibility behavior:
-     * <ul>
-     * <li>SDK 31 and above, if {@link PackageManager#isAutoRevokeWhitelisted()} is true, this
-     * will return {@link #APP_HIBERNATION_ENABLED}. Else, it will return
-     * {@link #APP_HIBERNATION_DISABLED}.</li>
-     * <li>SDK 30, if {@link PackageManager#isAutoRevokeWhitelisted()} is true, this will return
-     * {@link #PERMISSION_REVOCATION_ENABLED}. Else, it will return
-     * {@link #PERMISSION_REVOCATION_DISABLED}.</li>
-     * <li>SDK 23 through 29, if there exists an app with the Verifier role that can resolve the
-     * {@code Intent.ACTION_AUTO_REVOKE_PERMISSIONS} action.
-     * <li>SDK 22 and below, this method always returns
-     * {@link #UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE} as runtime permissions did not exist
-     * yet.
-     * </ul>
-     */
-    public static @UnusedAppRestrictionsStatus int getUnusedAppRestrictionsStatus(
-            @NonNull Context context) {
-        // Return false if the Android OS version is before M, because Android M introduced runtime
-        // permissions
-        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
-            return UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE;
-        }
-
-        // TODO: replace with VERSION_CODES.S once it's defined
-        if (Build.VERSION.SDK_INT >= 31) {
-            return Api30Impl.areUnusedAppRestrictionsEnabled(context)
-                    ? APP_HIBERNATION_ENABLED
-                    : APP_HIBERNATION_DISABLED;
-        }
-
-        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
-            return Api30Impl.areUnusedAppRestrictionsEnabled(context)
-                    ? PERMISSION_REVOCATION_ENABLED
-                    : PERMISSION_REVOCATION_DISABLED;
-        }
-
-        // Else, check for an app with the verifier role that can resolve the intent
-        String verifierPackageName = getVerifierRolePackageName(context.getPackageManager());
-        // Check that we were able to get the one Verifier's package name. If no Verifier or
-        // more than one Verifier exists on the device, unused app restrictions are not available
-        // on the device.
-        return (verifierPackageName == null)
-                ? UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE
-                // TODO(b/177234481): Implement the backport behavior of this API
-                : UNUSED_APP_RESTRICTION_STATUS_UNKNOWN;
-    }
-
-    /**
-     * We create this static class to avoid Class Verification Failures from referencing a method
-     * only added in Android R.
-     *
-     * <p>Gating references on SDK checks does not address class verification failures, hence the
-     * need for this inner class.
-     */
-    @RequiresApi(Build.VERSION_CODES.R)
-    private static class Api30Impl {
-        private Api30Impl() {}
-        static boolean areUnusedAppRestrictionsEnabled(@NonNull Context context) {
-            // If the app is allowlisted, that means that it is exempt from unused app restriction
-            // features, and thus the features are _disabled_.
-            return !context.getPackageManager().isAutoRevokeWhitelisted();
-        }
-    }
 }
diff --git a/core/core/src/main/java/androidx/core/content/PackageManagerCompat.java b/core/core/src/main/java/androidx/core/content/PackageManagerCompat.java
new file mode 100644
index 0000000..2303a65
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/content/PackageManagerCompat.java
@@ -0,0 +1,208 @@
+/*
+ * Copyright (C) 2011 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR 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.content;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import static java.lang.annotation.RetentionPolicy.SOURCE;
+
+import android.annotation.SuppressLint;
+import android.content.Context;
+import android.content.Intent;
+import android.content.pm.PackageManager;
+import android.content.pm.ResolveInfo;
+import android.net.Uri;
+import android.os.Build;
+
+import androidx.annotation.IntDef;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RequiresApi;
+import androidx.annotation.RestrictTo;
+
+import java.lang.annotation.Retention;
+import java.util.List;
+
+/**
+ * Helper for accessing features in {@link PackageManager}.
+ */
+public final class PackageManagerCompat {
+    private PackageManagerCompat() {
+        /* Hide constructor */
+    }
+
+    /**
+     * Activity action: creates an intent to redirect the user to UI to turn on/off their
+     * permission revocation settings.
+     */
+    @SuppressLint("ActionValue")
+    public static final String ACTION_PERMISSION_REVOCATION_SETTINGS =
+            "android.intent.action.AUTO_REVOKE_PERMISSIONS";
+
+    /** The status of Unused App Restrictions features is unknown for this app. */
+    public static final int UNUSED_APP_RESTRICTION_STATUS_UNKNOWN = 0;
+
+    /** There are no available Unused App Restrictions features for this app. */
+    public static final int UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE = 1;
+
+    /**
+     * Permission revocation is enabled for this app (i.e. permissions will be automatically
+     * reset if the app is unused).
+     *
+     * Note: this also means that app hibernation is not available for this app.
+     */
+    public static final int PERMISSION_REVOCATION_ENABLED = 2;
+
+    /**
+     * Permission revocation is disabled for this app (i.e. this app is exempt from having
+     * its permissions automatically removed).
+     *
+     * Note: this also means that app hibernation is not available for this app.
+     */
+    public static final int PERMISSION_REVOCATION_DISABLED = 3;
+
+    /**
+     * App hibernation is enabled for this app (i.e. this app will be hibernated and have its
+     * permissions revoked if the app is unused).
+     *
+     * Note: this also means that permission revocation is enabled for this app.
+     */
+    public static final int APP_HIBERNATION_ENABLED = 4;
+
+    /**
+     * App hibernation is disabled for this app (i.e. this app is exempt from being hibernated).
+     *
+     * Note: this also means that permission revocation is disabled for this app.
+     */
+    public static final int APP_HIBERNATION_DISABLED = 5;
+
+    /**
+     * The status of Unused App Restrictions features for this app.
+     * @hide
+     */
+    @IntDef({UNUSED_APP_RESTRICTION_STATUS_UNKNOWN, UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE,
+            PERMISSION_REVOCATION_ENABLED, PERMISSION_REVOCATION_DISABLED,
+            APP_HIBERNATION_ENABLED, APP_HIBERNATION_DISABLED})
+    @Retention(SOURCE)
+    @RestrictTo(LIBRARY)
+    public @interface UnusedAppRestrictionsStatus {
+    }
+
+    /**
+     * Returns the status of Unused App Restriction features for the current application, i.e.
+     * whether the features are available and if so, enabled for the application.
+     *
+     * Compatibility behavior:
+     * <ul>
+     * <li>SDK 31 and above, if {@link PackageManager#isAutoRevokeWhitelisted()} is true, this
+     * will return {@link #APP_HIBERNATION_ENABLED}. Else, it will return
+     * {@link #APP_HIBERNATION_DISABLED}.</li>
+     * <li>SDK 30, if {@link PackageManager#isAutoRevokeWhitelisted()} is true, this will return
+     * {@link #PERMISSION_REVOCATION_ENABLED}. Else, it will return
+     * {@link #PERMISSION_REVOCATION_DISABLED}.</li>
+     * <li>SDK 23 through 29, if there exists an app with the Verifier role that can resolve the
+     * {@code Intent.ACTION_AUTO_REVOKE_PERMISSIONS} action.
+     * <li>SDK 22 and below, this method always returns
+     * {@link #UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE} as runtime permissions did not exist
+     * yet.
+     * </ul>
+     */
+    public static @UnusedAppRestrictionsStatus int getUnusedAppRestrictionsStatus(
+            @NonNull Context context) {
+        // Return false if the Android OS version is before M, because Android M introduced runtime
+        // permissions
+        if (Build.VERSION.SDK_INT < Build.VERSION_CODES.M) {
+            return UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE;
+        }
+
+        // TODO: replace with VERSION_CODES.S once it's defined
+        if (Build.VERSION.SDK_INT >= 31) {
+            return Api30Impl.areUnusedAppRestrictionsEnabled(context)
+                    ? APP_HIBERNATION_ENABLED
+                    : APP_HIBERNATION_DISABLED;
+        }
+
+        if (Build.VERSION.SDK_INT == Build.VERSION_CODES.R) {
+            return Api30Impl.areUnusedAppRestrictionsEnabled(context)
+                    ? PERMISSION_REVOCATION_ENABLED
+                    : PERMISSION_REVOCATION_DISABLED;
+        }
+
+        // Else, check for an app with the verifier role that can resolve the intent
+        String verifierPackageName = getVerifierRolePackageName(context.getPackageManager());
+        // Check that we were able to get the one Verifier's package name. If no Verifier or
+        // more than one Verifier exists on the device, unused app restrictions are not available
+        // on the device.
+        return (verifierPackageName == null)
+                ? UNUSED_APP_RESTRICTION_FEATURE_NOT_AVAILABLE
+                // TODO(b/177234481): Implement the backport behavior of this API
+                : UNUSED_APP_RESTRICTION_STATUS_UNKNOWN;
+    }
+
+    /**
+     * Returns the package name of the one and only Verifier on the device. If none exist, this
+     * will return {@code null}. Likewise, if multiple Verifiers exist, this method will return
+     * the first Verifier's package name.
+     */
+    @Nullable
+    static String getVerifierRolePackageName(@NonNull PackageManager packageManager) {
+        Intent permissionRevocationSettingsIntent =
+                new Intent(ACTION_PERMISSION_REVOCATION_SETTINGS)
+                        .setData(Uri.fromParts(
+                                "package", "com.example", /* fragment= */ null));
+        List<ResolveInfo> intentResolvers =
+                packageManager.queryIntentActivities(
+                        permissionRevocationSettingsIntent, /* flags= */ 0);
+
+        String verifierPackageName = null;
+
+        for (ResolveInfo intentResolver: intentResolvers) {
+            String packageName = intentResolver.activityInfo.packageName;
+            if (packageManager.checkPermission("android.permission.PACKAGE_VERIFICATION_AGENT",
+                    packageName) != PackageManager.PERMISSION_GRANTED) {
+                continue;
+            }
+
+            if (verifierPackageName != null) {
+                // This shouldn't happen, but we fail gracefully nonetheless and avoid throwing an
+                // exception, instead returning the first package name with the Verifier role
+                // that we found.
+                return verifierPackageName;
+            }
+            verifierPackageName = packageName;
+        }
+
+        return verifierPackageName;
+    }
+
+    /**
+     * We create this static class to avoid Class Verification Failures from referencing a method
+     * only added in Android R.
+     *
+     * <p>Gating references on SDK checks does not address class verification failures, hence the
+     * need for this inner class.
+     */
+    @RequiresApi(Build.VERSION_CODES.R)
+    private static class Api30Impl {
+        private Api30Impl() {}
+        static boolean areUnusedAppRestrictionsEnabled(@NonNull Context context) {
+            // If the app is allowlisted, that means that it is exempt from unused app restriction
+            // features, and thus the features are _disabled_.
+            return !context.getPackageManager().isAutoRevokeWhitelisted();
+        }
+    }
+}
diff --git a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt
index 3882a04..a24bcfa 100644
--- a/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt
+++ b/datastore/datastore-rxjava2/src/main/java/androidx/datastore/rxjava2/RxSharedPreferencesMigration.kt
@@ -46,6 +46,9 @@
      * SharedPreferences to migrate from (limited to [keysToMigrate] and a T which represent
      * the current data. The function must return the migrated data.
      *
+     * If SharedPreferences is empty or does not contain any keys which you specified, this
+     * callback will not run.
+     *
      * @param sharedPreferencesView the current state of the SharedPreferences
      * @param currentData the most recently persisted data
      * @return a Single of the updated data
diff --git a/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt b/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
index 6b83eaa..0c64b55 100644
--- a/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
+++ b/datastore/datastore-rxjava3/src/main/java/androidx/datastore/rxjava3/RxSharedPreferencesMigration.kt
@@ -46,6 +46,9 @@
      * SharedPreferences to migrate from (limited to [keysToMigrate]) and a T which represent
      * the current data. The function must return the migrated data.
      *
+     * If SharedPreferences is empty or does not contain any keys which you specified, this
+     * callback will not run.
+     *
      * @param sharedPreferencesView the current state of the SharedPreferences
      * @param currentData the most recently persisted data
      * @return a Single of the updated data
diff --git a/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt b/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
index 52604f60..0387583 100644
--- a/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
+++ b/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
@@ -154,6 +154,21 @@
     }
 
     @Test
+    fun testSharedPrefsViewWithAllKeysSpecified_doesntThrowErrorWhenKeyDoesntExist() =
+        runBlockingTest {
+            assertThat(sharedPrefs.edit().putInt("unrelated_key", -123).commit()).isTrue()
+
+            val migration = SharedPreferencesMigration(
+                produceSharedPreferences = { sharedPrefs },
+            ) { prefs: SharedPreferencesView, _: Byte ->
+                prefs.getInt("this_key_doesnt_exist_yet", 123).toByte()
+            }
+
+            val dataStore = getDataStoreWithMigrations(listOf(migration))
+            assertThat(dataStore.data.first()).isEqualTo(123)
+        }
+
+    @Test
     fun producedSharedPreferencesIsUsed() = runBlockingTest {
         assertThat(sharedPrefs.edit().putInt("integer_key", 123).commit()).isTrue()
 
diff --git a/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt b/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt
index 76cfae9..4fc7b7c 100644
--- a/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt
+++ b/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt
@@ -66,7 +66,8 @@
      * since this may be called multiple times. See [DataMigration.migrate] for more
      * information. The lambda accepts a SharedPreferencesView which is the view of the
      * SharedPreferences to migrate from (limited to [keysToMigrate] and a T which represent
-     * the current data. The function must return the migrated data.
+     * the current data. The function must return the migrated data. If SharedPreferences is
+     * empty or does not contain any keys which you specified, this callback will not run.
      */
     @JvmOverloads // Generate constructors for default params for java users.
     public constructor(
@@ -111,7 +112,8 @@
      * since this may be called multiple times. See [DataMigration.migrate] for more
      * information. The lambda accepts a SharedPreferencesView which is the view of the
      * SharedPreferences to migrate from (limited to [keysToMigrate] and a T which represent
-     * the current data. The function must return the migrated data.
+     * the current data. The function must return the migrated data. If SharedPreferences is
+     * empty or does not contain any keys which you specified, this callback will not run.
      */
     @JvmOverloads // Generate constructors for default params for java users.
     public constructor(
@@ -131,20 +133,26 @@
 
     private val sharedPrefs: SharedPreferences by lazy(produceSharedPreferences)
 
-    private val keySet: MutableSet<String> by lazy {
+    /**
+     * keySet is null if the user specified [MIGRATE_ALL_KEYS].
+     */
+    private val keySet: MutableSet<String>? =
         if (keysToMigrate === MIGRATE_ALL_KEYS) {
-            sharedPrefs.all.keys
+            null
         } else {
-            keysToMigrate
-        }.toMutableSet()
-    }
+            keysToMigrate.toMutableSet()
+        }
 
     override suspend fun shouldMigrate(currentData: T): Boolean {
         if (!shouldRunMigration(currentData)) {
             return false
         }
 
-        return keySet.any(sharedPrefs::contains)
+        return if (keySet == null) {
+            sharedPrefs.all.isNotEmpty()
+        } else {
+            keySet.any(sharedPrefs::contains)
+        }
     }
 
     override suspend fun migrate(currentData: T): T =
@@ -160,8 +168,12 @@
     override suspend fun cleanUp() {
         val sharedPrefsEditor = sharedPrefs.edit()
 
-        for (key in keySet) {
-            sharedPrefsEditor.remove(key)
+        if (keySet == null) {
+            sharedPrefsEditor.clear()
+        } else {
+            keySet.forEach { key ->
+                sharedPrefsEditor.remove(key)
+            }
         }
 
         if (!sharedPrefsEditor.commit()) {
@@ -172,7 +184,7 @@
             deleteSharedPreferences(context, name)
         }
 
-        keySet.clear()
+        keySet?.clear()
     }
 
     private fun deleteSharedPreferences(context: Context, name: String) {
@@ -211,12 +223,11 @@
 }
 
 /**
- *  Read-only wrapper around SharedPreferences. This will be passed in to your migration. The
- *  constructor is public to enable easier testing of migrations.
+ *  Read-only wrapper around SharedPreferences. This will be passed in to your migration.
  */
 public class SharedPreferencesView internal constructor(
     private val prefs: SharedPreferences,
-    private val keySet: Set<String>
+    private val keySet: Set<String>?
 ) {
     /**
      * Checks whether the preferences contains a preference.
@@ -285,18 +296,22 @@
         prefs.getStringSet(checkKey(key), defValues)?.toMutableSet()
 
     /** Retrieve all values from the preferences that are in the specified keySet. */
-    public fun getAll(): Map<String, Any?> = prefs.all.filter { (key, _) ->
-        key in keySet
-    }.mapValues { (_, value) ->
-        if (value is Set<*>) {
-            value.toSet()
-        } else {
-            value
+    public fun getAll(): Map<String, Any?> =
+        prefs.all.filter { (key, _) ->
+            keySet?.contains(key) ?: true
+        }.mapValues { (_, value) ->
+            if (value is Set<*>) {
+                value.toSet()
+            } else {
+                value
+            }
         }
-    }
 
     private fun checkKey(key: String): String {
-        check(key in keySet) { "Can't access key outside migration: $key" }
+        keySet?.let {
+            check(key in it) { "Can't access key outside migration: $key" }
+        }
+
         return key
     }
 }
diff --git a/development/build_log_simplifier/message-flakes.ignore b/development/build_log_simplifier/message-flakes.ignore
index 110863e..f9748fc 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -20,13 +20,18 @@
 # > Task :compose:compiler:compiler-hosted:integration-tests:testDebugUnitTest
 # If a test fails, we don't want the build to fail, we want to pass the test output to the tests server and for the tests server to report the failure
 [0-9]+ test.*failed.*
+There were failing tests\. See the report at: .*.html
 # Some status messages
 Starting a Gradle Daemon, [0-9]+ busy and [0-9]+ incompatible Daemons could not be reused, use \-\-status for details
 Starting a Gradle Daemon, [0-9]+ busy and [0-9]+ incompatible and [0-9]+ stopped Daemons could not be reused, use \-\-status for details
+Starting a Gradle Daemon \(subsequent builds will be faster\)
+To honour the JVM settings for this build a single\-use Daemon process will be forked\. See https://docs\.gradle\.org/[0-9]+\.[0-9]+\.[0-9]+/userguide/gradle_daemon\.html\#sec:disabling_the_daemon\.
 Remote build cache is disabled when running with \-\-offline\.
 [0-9]+ actionable tasks: [0-9]+ up\-to\-date
 [0-9]+ actionable tasks: [0-9]+ executed
+[0-9]+ actionable task: [0-9]+ executed
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ from cache, [0-9]+ up\-to\-date
+The remote build cache was disabled during the build due to errors\.
 # Some messages that encode the number of a certain type of other error
 [0-9]+ errors, [0-9]+ warnings \([0-9]+ warnings filtered by baseline lint\-baseline\.xml\)
 [0-9]+ errors, [0-9]+ warnings \([0-9]+ warning filtered by baseline lint\-baseline\.xml\)
diff --git a/development/build_log_simplifier/messages.ignore b/development/build_log_simplifier/messages.ignore
index 88d5612..3196854 100644
--- a/development/build_log_simplifier/messages.ignore
+++ b/development/build_log_simplifier/messages.ignore
@@ -9,7 +9,6 @@
 PROGRESS: Rendering
 No docs found on supertype with \{@inheritDoc\} method .*
 Can't find node by signature .*
-Exception while resolving link to Module: Package:androidx\.compose\.material Function:animateElevation Receiver:<this>
 java\.lang\.IllegalStateException: Unsupported Receiver
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlUriProvider\$DefaultImpls\.mainUri\(JavaLayoutHtmlFormat\.kt:[0-9]+\)
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatGenerator\.mainUri\(JavaLayoutHtmlGenerator\.kt:[0-9]+\)
@@ -108,7 +107,6 @@
 at kotlinx\.html\.Gen_tag_unionsKt\.code\$default\(gen\-tag\-unions\.kt:[0-9]+\)
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatOutputBuilder\$functionLikeSummaryRow\$[0-9]+\$[0-9]+\$[0-9]+\.invoke\(JavaLayoutHtmlFormatOutputBuilder\.kt:[0-9]+\)
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics Function:compositeOver Receiver:<this>
-Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics GroupNode:NativeTileMode
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics GroupNode:NativeCanvas
 at kotlinx\.html\.Gen_tag_groupsKt\.pre\(gen\-tag\-groups\.kt:[0-9]+\)
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics GroupNode:NativePaint
@@ -116,12 +114,6 @@
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatOutputBuilder\$propertyLikeSummaryRow\$[0-9]+\.invoke\(JavaLayoutHtmlFormatOutputBuilder\.kt:[0-9]+\)
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatOutputBuilder\.propertyLikeSummaryRow\(JavaLayoutHtmlFormatOutputBuilder\.kt:[0-9]+\)
 at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatOutputBuilder\.propertyLikeSummaryRow\$default\(JavaLayoutHtmlFormatOutputBuilder\.kt:[0-9]+\)
-at org\.jetbrains\.dokka\.Formats\.JavaLayoutHtmlFormatOutputBuilder\.qualifiedTypeReference\(JavaLayoutHtmlFormatOutputBuilder\.kt:[0-9]+\)
-at org\.jetbrains\.dokka\.Formats\.DevsiteLayoutHtmlFormatOutputBuilder\$classHierarchy\$[0-9]+\$\$special\$\$inlined\$forEach\$lambda\$[0-9]+\$[0-9]+\.invoke\(DacHtmlFormat\.kt:[0-9]+\)
-at org\.jetbrains\.dokka\.Formats\.DevsiteLayoutHtmlFormatOutputBuilder\$classHierarchy\$[0-9]+\$\$special\$\$inlined\$forEach\$lambda\$[0-9]+\.invoke\(DacHtmlFormat\.kt:[0-9]+\)
-at org\.jetbrains\.dokka\.Formats\.DevsiteLayoutHtmlFormatOutputBuilder\$classHierarchy\$[0-9]+\.invoke\(DacHtmlFormat\.kt:[0-9]+\)
-at org\.jetbrains\.dokka\.Formats\.DevsiteLayoutHtmlFormatOutputBuilder\.classHierarchy\(DacHtmlFormat\.kt:[0-9]+\)
-Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics GroupNode:NativeVertexMode
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics\.vector Function:group Receiver:<this>
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.graphics\.vector Function:path Receiver:<this>
 Exception while resolving link to Module: Package:androidx\.compose\.ui\.unit ExternalClass:kotlin\.Int Function:times Receiver:<this>
@@ -133,7 +125,6 @@
 CHECKOUT=\$CHECKOUT
 GRADLE_USER_HOME=\$GRADLE_USER_HOME
 To honour the JVM settings for this build a single\-use Daemon process will be forked\. See https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/gradle_daemon\.html\#sec:disabling_the_daemon\.
-Starting a Gradle Daemon \(subsequent builds will be faster\)
 Downloading file\:\$SUPPORT\/gradle\/wrapper\/.*
 \.\.\.\.\.\.\.\.\.\.10%\.\.\.\.\.\.\.\.\.\.\.20%\.\.\.\.\.\.\.\.\.\.\.30%\.\.\.\.\.\.\.\.\.\.40%\.\.\.\.\.\.\.\.\.\.\.50%\.\.\.\.\.\.\.\.\.\.\.60%\.\.\.\.\.\.\.\.\.\.70%\.\.\.\.\.\.\.\.\.\.\.80%\.\.\.\.\.\.\.\.\.\.\.90%\.\.\.\.\.\.\.\.\.\.\.100%
 Welcome to Gradle .*
@@ -141,7 +132,6 @@
 \- Faster incremental Java compilation
 \- Easier source set configuration in the Kotlin DSL
 For more details see .*
-To honour the JVM settings for this build a single\-use Daemon process will be forked\. See https://docs\.gradle\.org/[0-9]+\.[0-9]+\.[0-9]+/userguide/gradle_daemon\.html\#sec:disabling_the_daemon\.
 Daemon will be stopped at the end of the build
 # > Task :buildSrc:build
 # > Configure project :appsearch:appsearch\-local\-backend
@@ -151,20 +141,12 @@
 updated local\.properties
 # > Configure project :compose:test\-utils
 The following Kotlin source sets were configured but not added to any Kotlin compilation:
-\* iosArm[0-9]+Main
 \* iosArm[0-9]+Test
-\* iosX[0-9]+Main
 \* iosX[0-9]+Test
-\* jsMain
-\* jsTest
-\* linuxX[0-9]+Main
 \* linuxX[0-9]+Test
-\* macosX[0-9]+Main
 \* macosX[0-9]+Test
-\* mingwX[0-9]+Main
 \* mingwX[0-9]+Test
 \* nativeTest
-\* nonJvmMain
 \* androidAndroidTestRelease
 \* androidTestFixtures
 \* androidTestFixturesDebug
@@ -173,22 +155,14 @@
 \* test
 You can add a source set to a target's compilation by connecting it with the compilation's default source set using 'dependsOn'\.
 See https://kotlinlang\.org/docs/reference/building\-mpp\-with\-gradle\.html\#connecting\-source\-sets
-Some Kotlin/Native targets cannot be built on this macos_x[0-9]+ machine and are disabled:
-\* In project ':collection:collection[0-9]+':
-\* target 'mingwX[0-9]+' \(can be built with a mingw_x[0-9]+ host\)
-To hide this message, add 'kotlin\.native\.ignoreDisabledTargets=true' to the Gradle properties\.
 \* jvmMain
 # > Task :listTaskOutputs
 Wrote \$DIST_DIR/task_outputs\.txt
 Deprecated Gradle features were used in this build, making it incompatible with Gradle [0-9]+\.[0-9]+\.
-Use '\-\-warning\-mode all' to show the individual deprecation warnings\.
 See https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/command_line_interface\.html\#sec:command_line_warnings
 Execution optimizations have been disabled for [0-9]+ invalid unit\(s\) of work during this build to ensure correctness\.
 Please consult deprecation warnings for more details\.
-See https\:\/\/docs\.gradle\.org\/[0-9]+\.[0-9]+\.[0-9]+\/userguide\/command_line_interface\.html\#sec\:command_line_warnings
 BUILD SUCCESSFUL in .*
-The remote build cache was disabled during the build due to errors\.
-[0-9]+ actionable task: [0-9]+ executed
 # > Task :doclava:compileJava
 Note\: Some input files use or override a deprecated API\.
 Note: Some input files use or override a deprecated API that is marked for removal\.
@@ -202,8 +176,6 @@
 application@android:debuggable was tagged at .*\.xml:[0-9]+ to replace other declarations but no other declaration present
 \$OUT_DIR/androidx/wear/compose/compose\-material\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$OUT_DIR/androidx/profileinstaller/profileinstaller\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/slice\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/ads\-identifier\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$OUT_DIR/androidx/appcompat/appcompat\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$OUT_DIR/androidx/benchmark/benchmark\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$OUT_DIR/androidx/collection/collection\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
@@ -228,29 +200,16 @@
 \$OUT_DIR/androidx/compose/ui/ui\-graphics/ui\-graphics\-benchmark/build/intermediates/tmp/manifest/androidTest/release/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :compose:foundation:foundation-layout:processDebugAndroidTestManifest
 \$SUPPORT/compose/foundation/foundation\-layout/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/compose/foundation/foundation\-layout/build/intermediates/tmp/manifest/androidTest/debug/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :compose:integration-tests:benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/compose/integration\-tests/benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :compose:navigation:navigation:processDebugAndroidTestManifest
 # > Task :compose:runtime:runtime-saveable:processDebugAndroidTestManifest
 \$SUPPORT/compose/runtime/runtime\-saveable/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/compose/runtime/runtime\-saveable/build/intermediates/tmp/manifest/androidTest/debug/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :compose:ui:ui-graphics:compileKotlinMetadata
-w: \$SUPPORT/compose/ui/ui\-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Color\.kt: \([0-9]+, [0-9]+\): The feature "inline classes" is experimental
-w: \$SUPPORT/compose/ui/ui\-graphics/src/commonMain/kotlin/androidx/compose/ui/graphics/Float[0-9]+\.kt: \([0-9]+, [0-9]+\): The feature "inline classes" is experimental
 # > Task :compose:runtime:runtime:benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/runtime/runtime/compose\-runtime\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/compose/runtime/runtime/benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :compose:material:material-icons-extended:processDebugAndroidTestManifest
-\[:compose:material:material\-icons\-extended\] \$OUT_DIR/androidx/compose/material/material\-icons\-extended/build/intermediates/merged_manifest/debug/AndroidManifest\.xml Warning:
-Package name 'androidx\.compose\.material\.icons' used in: :compose:material:material\-icons\-extended, :compose:material:material\-icons\-core\.
 # > Task :compose:ui:ui:compileKotlinMetadata
 w: Runtime JAR files in the classpath should have the same version\. These files were found in the classpath:
 \$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib/[0-9]+\.[0-9]+\.[0-9]+/kotlin\-stdlib\-[0-9]+\.[0-9]+\.[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
 \$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib\-common/[0-9]+\.[0-9]+\.[0-9]+/kotlin\-stdlib\-common\-[0-9]+\.[0-9]+\.[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
 w: Some runtime JAR files in the classpath have an incompatible version\. Consider removing them from the classpath
-w: \$SUPPORT/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/input/key/Key\.kt: \([0-9]+, [0-9]+\): The feature "inline classes" is experimental
-w: \$SUPPORT/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/layout/Measured\.kt: \([0-9]+, [0-9]+\): The feature "inline classes" is experimental
 w: \$SUPPORT/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet\.kt: \([0-9]+, [0-9]+\): The corresponding parameter in the supertype 'Comparator' is named 'a'\. This may cause problems when calling this function with named arguments\.
 w: \$SUPPORT/compose/ui/ui/src/commonMain/kotlin/androidx/compose/ui/node/DepthSortedSet\.kt: \([0-9]+, [0-9]+\): The corresponding parameter in the supertype 'Comparator' is named 'b'\. This may cause problems when calling this function with named arguments\.
 # > Task :benchmark:benchmark-common:runErrorProne
@@ -258,13 +217,6 @@
 symbol\:   static FLAG_MUTABLE
 location\: class PendingIntent
 \$OUT_DIR\/androidx\/docs\-public\/build\/unzippedDocsSources\/androidx\/work\/impl\/utils\/ForceStopRunnable\.java\:[0-9]+\: error\: cannot find symbol
-symbol:   class ExtensionDeviceState
-location: package androidx\.window\.extensions
-DeviceState translate\(ExtensionDeviceState deviceState\) \{
-location: class ExtensionAdapter
-\$OUT_DIR/androidx/docs\-public/build/unzippedDocsSources/androidx/window/ExtensionTranslatingCallback\.java:[0-9]+: error: cannot find symbol
-public void onDeviceStateChanged\(@NonNull ExtensionDeviceState newDeviceState\) \{
-location: class ExtensionTranslatingCallback
 # > Task :buildOnServer
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ up\-to\-date
 [0-9]+ actionable tasks: [0-9]+ executed, [0-9]+ from cache
@@ -281,28 +233,6 @@
 # > Task :textclassifier:textclassifier:compileReleaseJavaWithJavac
 # > Task :publicDocsTask
 [0-9]+ warnings
-# > Task :appsearch:appsearch-local-storage:externalNativeBuildRelease
-ninja: .*
-Build icing_.*
-In file included from \$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.cc:[0-9]+:
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'StartObject' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/object_writer\.h:[0-9]+:[0-9]+: note: overridden virtual function is here
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'EndObject' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'StartList' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'EndList' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderBool' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderInt[0-9]+' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderUint[0-9]+' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderDouble' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderFloat' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderString' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderBytes' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/json_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderNull' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-In file included from \$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/type_info_test_helper\.cc:[0-9]+:
-In file included from \$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/type_info_test_helper\.h:[0-9]+:
-\$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/default_value_objectwriter\.h:[0-9]+:[0-9]+: warning: 'RenderNull' overrides a member function but is not marked 'override' \[\-Winconsistent\-missing\-override\]
-In file included from \$CHECKOUT/external/protobuf/src/google/protobuf/util/json_util\.cc:[0-9]+:
-In file included from \$CHECKOUT/external/protobuf/src/google/protobuf/util/internal/default_value_objectwriter\.cc:[0-9]+:
 # > Task :support-animation-demos:compileDebugJavaWithJavac
 # > Task :lint-demos:lint-demo-appcompat:compileDebugJavaWithJavac
 # > Task :support-transition-demos:compileDebugJavaWithJavac
@@ -311,7 +241,6 @@
 # > Task :startup:integration-tests:first-library:processDebugManifest
 \$SUPPORT/startup/integration\-tests/first\-library/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 meta\-data\#androidx\.work\.WorkManagerInitializer was tagged at AndroidManifest\.xml\:[0-9]+ to remove other declarations but no other declaration present
-provider\#androidx\.work\.WorkManagerInitializer was tagged at AndroidManifest\.xml:[0-9]+ to remove other declarations but no other declaration present
 # > Task :camera:integration-tests:camera-testapp-extensions:compileDebugJavaWithJavac
 # > Task :camera:integration-tests:camera-testapp-core:compileDebugJavaWithJavac
 # > Task :room:integration-tests:room-testapp:processDebugMainManifest
@@ -320,63 +249,20 @@
 # > Task :lifecycle:integration-tests:lifecycle-testapp:compileDebugJavaWithJavac
 # > Task :support-slices-demos:compileDebugJavaWithJavac
 Note: \$SUPPORT/samples/SupportSliceDemos/src/main/java/com/example/androidx/slice/demos/SliceBrowser\.java uses unchecked or unsafe operations\.
-# > Task :ads-identifier:processDebugAndroidTestManifest
-# > Task :sharetarget:integration-tests:testapp:compileDebugJavaWithJavac
-# > Task :ads-identifier-provider:processDebugAndroidTestManifest
-# > Task :hilt:integration-tests:hilt-testapp-viewmodel:compileDebugJavaWithJavac
-# > Task :ads-identifier-benchmark:processReleaseAndroidTestManifest
-\$SUPPORT/ads/ads\-identifier\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/ads\-identifier\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :internal-testutils-runtime:processDebugAndroidTestManifest
-# > Task :slice-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/slice\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :activity:activity:processDebugAndroidTestManifest
 # > Task :appcompat:appcompat:processDebugAndroidTestManifest
 Package name 'androidx\.testutils' used in: :internal\-testutils\-appcompat, :internal\-testutils\-runtime\.
-# > Task :appcompat:appcompat-resources:processDebugAndroidTestManifest
-# > Task :appcompat:appcompat-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/appcompat/appcompat\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :benchmark:benchmark-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/benchmark/benchmark\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :camera:camera-camera2:processDebugAndroidTestManifest
-# > Task :camera:camera-core:processDebugAndroidTestManifest
-# > Task :camera:camera-extensions:processDebugAndroidTestManifest
-# > Task :camera:camera-lifecycle:processDebugAndroidTestManifest
-# > Task :camera:camera-testing:externalNativeBuildDebug
-Build testing_surface_format_.*
-# > Task :collection:collection-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/collection/collection\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :emoji2:emoji2-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/emoji[0-9]+/emoji[0-9]+\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/emoji2/emoji2\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :gridlayout:gridlayout:compileDebugAndroidTestJavaWithJavac
 # > Task :leanback:leanback:processDebugAndroidTestManifest
 Package name 'androidx\.testutils' used in: :internal\-testutils\-runtime, :internal\-testutils\-espresso\.
 # > Task :leanback:leanback-tab:processDebugAndroidTestManifest
 # > Task :lifecycle:lifecycle-extensions:processDebugAndroidTestManifest
 # > Task :leanback:leanback:compileDebugAndroidTestJavaWithJavac
-warning: unknown enum constant AnnotationTarget\.CLASS
-warning: unknown enum constant AnnotationTarget\.PROPERTY
-warning: unknown enum constant AnnotationTarget\.LOCAL_VARIABLE
-warning: unknown enum constant AnnotationTarget\.VALUE_PARAMETER
-warning: unknown enum constant AnnotationTarget\.CONSTRUCTOR
 reason: class file for kotlin\.annotation\.AnnotationTarget not found
-warning: unknown enum constant AnnotationTarget\.FUNCTION
-warning: unknown enum constant AnnotationTarget\.PROPERTY_GETTER
-warning: unknown enum constant AnnotationTarget\.PROPERTY_SETTER
-warning: unknown enum constant AnnotationTarget\.FILE
-warning: unknown enum constant AnnotationTarget\.TYPEALIAS
 reason: class file for kotlin\.annotation\.AnnotationRetention not found
 warning: unknown enum constant AnnotationTarget\.ANNOTATION_CLASS
-# > Task :lifecycle:lifecycle-service:processDebugAndroidTestManifest
-# > Task :lifecycle:lifecycle-viewmodel-savedstate:processDebugAndroidTestManifest
-# > Task :leanback:leanback-tab:compileDebugAndroidTestJavaWithJavac
-# > Task :media2:media2-common:processDebugAndroidTestManifest
-# > Task :media2:media2-player:processDebugAndroidTestManifest
-# > Task :media2:media2-session:processDebugAndroidTestManifest
-# > Task :media2:media2-common:compileDebugAndroidTestJavaWithJavac
-# > Task :navigation:navigation-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/navigation/navigation\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :navigation:navigation-dynamic-features-runtime:processDebugAndroidTestManifest
 # > Task :navigation:navigation-fragment:processDebugAndroidTestManifest
 Package name 'androidx\.testutils' used in: :internal\-testutils\-runtime, :internal\-testutils\-navigation\.
@@ -386,31 +272,16 @@
 # > Task :preference:preference-ktx:processDebugAndroidTestManifest
 # > Task :recyclerview:recyclerview-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/recyclerview/recyclerview\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/recyclerview/recyclerview\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :remotecallback:remotecallback:processDebugAndroidTestManifest
 # > Task :recyclerview:recyclerview-selection:compileDebugAndroidTestJavaWithJavac
 # > Task :savedstate:savedstate:processDebugAndroidTestManifest
 # > Task :room:room-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/room/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR/androidx/room/room\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Task :room:room-benchmark:kaptReleaseAndroidTestKotlin
 \$OUT_DIR/androidx/room/room\-benchmark/build/tmp/kapt[0-9]+/stubs/releaseAndroidTest/androidx/room/benchmark/RelationBenchmark\.java:[0-9]+: warning: The return value includes a POJO with a @Relation\. It is usually desired to annotate this method with @Transaction to avoid possibility of inconsistent results between the POJO and its relations\. See https://developer\.android\.com/reference/androidx/room/Transaction\.html for details\.
 public abstract java\.util\.List<androidx\.room\.benchmark\.RelationBenchmark\.UserWithItems> getUserWithItems\(\);
 # > Task :viewpager2:viewpager2:processDebugAndroidTestManifest
 Package name 'androidx\.testutils' used in: :internal\-testutils\-appcompat, :internal\-testutils\-runtime, :internal\-testutils\-espresso\.
-# > Task :wear:wear-watchface:processDebugAndroidTestManifest
-# > Task :window:window:processDebugAndroidTestManifest
-# > Task :webkit:webkit:compileDebugAndroidTestJavaWithJavac
-# > Task :work:work-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/work/work\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :benchmark:integration-tests:dry-run-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/benchmark/integration\-tests/dry\-run\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :benchmark:integration-tests:startup-benchmark:processReleaseAndroidTestManifest
-\$OUT_DIR/androidx/benchmark/integration\-tests/startup\-benchmark/build/intermediates/tmp/manifest/androidTest/release/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-# > Task :camera:integration-tests:camera-testapp-core:externalNativeBuildDebug
-Build opengl_renderer_jni_.*
-# > Task :camera:integration-tests:camera-testapp-view:minifyReleaseWithR8
-R[0-9]+: Missing class: java\.lang\.ClassValue
 # > Task :room:integration-tests:room-testapp-noappcompat:compileDebugAndroidTestJavaWithJavac
 \$SUPPORT/room/integration\-tests/noappcompattestapp/src/androidTest/java/androidx/room/integration/noappcompat/BareRelationDatabaseTest\.java:[0-9]+: warning: The return value includes a POJO with a @Relation\. It is usually desired to annotate this method with @Transaction to avoid possibility of inconsistent results between the POJO and its relations\. See https://developer\.android\.com/reference/androidx/room/Transaction\.html for details\.
 UserAndPets getUserWithPets\(long id\);
@@ -418,15 +289,8 @@
 # > Task :textclassifier:integration-tests:testapp:compileDebugAndroidTestJavaWithJavac
 # > Task :room:integration-tests:room-testapp:compileDebugAndroidTestJavaWithJavac
 Note: \$SUPPORT/room/integration\-tests/testapp/src/androidTest/java/androidx/room/integration/testapp/test/TestUtil\.java uses unchecked or unsafe operations\.
-# > Task :hilt:integration-tests:hilt-testapp-worker:kaptDebugAndroidTestKotlin
-warning: The following options were not recognized by any processor: '\[dagger\.hilt\.android\.internal\.disableAndroidSuperclassValidation, kapt\.kotlin\.generated\]'
-# > Task :appsearch:appsearch-compiler:lint
-[0-9]+ errors, [0-9]+ warnings \([0-9]+ error filtered by baseline lint\-baseline\.xml\)
 # > Task :activity:integration-tests:testapp:processDebugAndroidTestManifest
 # b/166471969
-.*androidTest.*AndroidManifest.*xml Warning.*:
-Package name.*test' used in: .*AndroidManifest.*xml.*
-\$SUPPORT/slices/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$SUPPORT/appcompat/appcompat\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$SUPPORT/benchmark/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \$SUPPORT/collection/collection\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
@@ -436,7 +300,6 @@
 \$SUPPORT/benchmark/integration\-tests/startup\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \[:internal\-testutils\-espresso\] \$OUT_DIR/androidx/internal\-testutils\-espresso/build/intermediates/merged_manifest/debug/AndroidManifest\.xml Warning:
 Package name 'androidx\.testutils' used in: :internal\-testutils\-espresso, :internal\-testutils\-runtime\.
-\$OUT_DIR/androidx/compose/ui/ui\-tooling/build/intermediates/tmp/manifest/androidTest/debug/manifestMerger[0-9]+\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 \[:internal\-testutils\-runtime\] \$OUT_DIR/androidx/internal\-testutils\-runtime/build/intermediates/merged_manifest/debug/AndroidManifest\.xml Warning:
 # > Task :leanback:leanback-paging:generateApi
 w\: Runtime JAR files in the classpath have the version [0-9]+\.[0-9]+\, which is older than the API version [0-9]+\.[0-9]+\. Consider using the runtime of version [0-9]+\.[0-9]+\, or pass \'\-api\-version [0-9]+\.[0-9]+\' explicitly to restrict the available APIs to the runtime of version [0-9]+\.[0-9]+\. You can also pass \'\-language\-version [0-9]+\.[0-9]+\' instead\, which will restrict not only the APIs to the specified version\, but also the language features
@@ -448,57 +311,21 @@
 WARNING: An illegal reflective access operation has occurred
 WARNING: Please consider reporting this to the maintainers of org\.jetbrains\.kotlin\.kapt[0-9]+\.base\.javac\.KaptJavaFileManager
 WARNING: Illegal reflective access by com\.intellij\.util\.ReflectionUtil \(file:\$CHECKOUT/prebuilts/androidx/external/com/google/devsite/dackka/[0-9]+\.[0-9]+\.[0-9]+/dackka\-[0-9]+\.[0-9]+\.[0-9]+\.jar\) to method java\.util\.ResourceBundle\.setParent\(java\.util\.ResourceBundle\)
-WARNING: Illegal reflective access by com\.intellij\.util\.ReflectionUtil \(file:\$OUT_DIR/androidx/compose/compiler/compiler\-hosted/integration\-tests/kotlin\-compiler\-repackaged/build/repackaged/kotlin\-compiler\-repackaged\.jar\) to method java\.util\.ResourceBundle\.setParent\(java\.util\.ResourceBundle\)
-WARNING\: Illegal reflective access using Lookup on org\.gradle\.internal\.classloader\.ClassLoaderUtils\$AbstractClassLoaderLookuper \(file\:\$GRADLE_USER_HOME\/caches\/[0-9]+\.[0-9]+\.[0-9]+\/generated\-gradle\-jars\/gradle\-api\-[0-9]+\.[0-9]+\.[0-9]+\.jar\) to class java\.lang\.ClassLoader
-WARNING\: Please consider reporting this to the maintainers of org\.gradle\.internal\.classloader\.ClassLoaderUtils\$AbstractClassLoaderLookuper
-WARNING\: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers\$[0-9]+ \(file\:\$CHECKOUT\/prebuilts\/androidx\/external\/org\/robolectric\/shadowapi\/[0-9]+\.[0-9]+\-alpha\-[0-9]+\/shadowapi\-[0-9]+\.[0-9]+\-alpha\-[0-9]+\.jar\) to method java\.lang\.ClassLoader\.getPackage\(java\.lang\.String\)
-WARNING\: Please consider reporting this to the maintainers of org\.robolectric\.util\.ReflectionHelpers\$[0-9]+
-WARNING\: Illegal reflective access by com\.intellij\.util\.ReflectionUtil \(file\:\$CHECKOUT\/prebuilts\/androidx\/external\/com\/android\/tools\/external\/com\-intellij\/intellij\-core\/[0-9]+\.[0-9]+\.[0-9]+\-beta[0-9]+\/intellij\-core\-[0-9]+\.[0-9]+\.[0-9]+\-beta[0-9]+\.jar\) to method java\.util\.ResourceBundle\.setParent\(java\.util\.ResourceBundle\)
-WARNING: Illegal reflective access by org\.jetbrains\.kotlin\.com\.intellij\.util\.ReflectionUtil \(file:\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-compiler\-embeddable/.+/kotlin\-compiler\-embeddable\-.+\.jar\) to method java\.util\.ResourceBundle\.setParent\(java\.util\.ResourceBundle\)
-WARNING\: Please consider reporting this to the maintainers of org\.jetbrains\.kotlin\.com\.intellij\.util\.ReflectionUtil
-WARNING: Illegal reflective access by com\.intellij\.util\.ReflectionUtil \(file:\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-compiler/.+/kotlin\-compiler\-.+\.jar\) to method java\.util\.ResourceBundle\.setParent\(java\.util\.ResourceBundle\)
 WARNING: Please consider reporting this to the maintainers of com\.intellij\.util\.ReflectionUtil
 WARNING: Use \-\-illegal\-access=warn to enable warnings of further illegal reflective access operations
 WARNING: All illegal access operations will be denied in a future release
-# > Task :compose:compiler:compiler-hosted:integration-tests:testDebugUnitTest
-WARNING: Illegal reflective access by org\.robolectric\.util\.ReflectionHelpers \(file:\$CHECKOUT/prebuilts/androidx/external/org/robolectric/shadowapi/[0-9]+\.[0-9]+\-alpha\-[0-9]+/shadowapi\-[0-9]+\.[0-9]+\-alpha\-[0-9]+\.jar\) to field java\.lang\.reflect\.Field\.modifiers
-WARNING: Please consider reporting this to the maintainers of org\.robolectric\.util\.ReflectionHelpers
-# > Task :compose:compiler:compiler-hosted:integration-tests:test
-wrote dependency log to \$DIST_DIR/affected_module_detector_log\.txt
 # > Task :ipc:ipc-compiler:kaptTestKotlin
 Annotation processors discovery from compile classpath is deprecated\.
 Set 'kapt\.includeCompileClasspath = false' to disable discovery\.
 Run the build with '\-\-info' for more details\.
-# > Task :wear:wear-input:compileDebugUnitTestJavaWithJavac
-# > Task :lifecycle:integration-tests:incrementality:compileTestKotlin
-\$GRADLE_USER_HOME\/wrapper\/dists\/gradle\-[^/]*-bin\/[0-9a-z]+\/gradle\-[^/]*/lib\/kotlin\-stdlib\-[0-9.]*.jar \(version [^/]*\)
-\$GRADLE_USER_HOME\/wrapper\/dists\/gradle\-[^/]*-bin\/[0-9a-z]+\/gradle\-[^/]*/lib\/kotlin\-stdlib\-common\-[0-9.]*.jar \(version [^/]*\)
-\$GRADLE_USER_HOME\/wrapper\/dists\/gradle\-[^/]*-bin\/[0-9a-z]+\/gradle\-[^/]*/lib\/kotlin\-stdlib\-jdk[0-9]*-[0-9.]*.jar \(version [^/]*\)
-\$GRADLE_USER_HOME\/wrapper\/dists\/gradle\-[^/]*-bin\/[0-9a-z]+\/gradle\-[^/]*/lib\/kotlin\-reflect\-[0-9.]*.jar \(version [^/]*\)
-w: Consider providing an explicit dependency on kotlin\-reflect [^/]* to prevent strange errors
 # > Task :room:room-compiler:test
-WARNING: Illegal reflective access by androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion\$isFromCompiledClass\$[0-9]+ \(file:\$OUT_DIR/androidx/room/room\-compiler\-processing/build/libs/room\-compiler\-processing\-[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta|rc)[0-9]+)?\.jar\) to field com\.sun\.tools\.javac\.code\.Symbol\$ClassSymbol\.classfile
 WARNING: Illegal reflective access by androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion \(file:\$OUT_DIR/androidx/room/room\-compiler\-processing/build/libs/room\-compiler\-processing\-[0-9]+\.[0-9]+\.[0-9]+(\-(alpha|beta|rc)[0-9]+)?\.jar\) to field com\.sun\.tools\.javac\.code\.Symbol\$ClassSymbol\.classfile
-WARNING: Please consider reporting this to the maintainers of androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion\$isFromCompiledClass\$[0-9]+
 WARNING: Please consider reporting this to the maintainers of androidx\.room\.compiler\.processing\.javac\.JavacProcessingEnvMessager\$Companion
-# > Task :wear:wear-watchface-complications-rendering:compileDebugUnitTestJavaWithJavac
-# > Task :wear:wear-watchface:testDebugUnitTest
-System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'dispose\' not called
-System\.logW\: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable\: Explicit termination method \'release\' not called
-# > Task :benchmark:benchmark-perfetto:mergeDebugAndroidTestJavaResource
-More than one file was found with OS independent path '.*'\. This version of the Android Gradle Plugin chooses the file from the app or dynamic\-feature module, but this can cause unexpected behavior or errors at runtime\. Future versions of the Android Gradle Plugin will throw an error in this case\.
 # > Task :docs-runner:dokkaJavaTipOfTreeDocs
-\$CHECKOUT\/prebuilts\/androidx\/external\/org\/jetbrains\/kotlin\/kotlin\-reflect\/[0-9]+\.[0-9]+\.[0-9]+\/kotlin\-reflect\-[0-9]+\.[0-9]+\.[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
 \$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib\-jdk[0-9]+/[0-9]+\.[0-9]+\.[0-9]+/kotlin\-stdlib\-jdk[0-9]+\-[0-9]+\.[0-9]+\.[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
-\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib\-jdk[0-9]+/[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+/kotlin\-stdlib\-jdk[0-9]+\-[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
-\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib/[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+/kotlin\-stdlib\-[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
-\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib\-common/[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+/kotlin\-stdlib\-common\-[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
-\$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-reflect/[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+/kotlin\-reflect\-[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+\.jar \(version [0-9]+\.[0-9]+\)
 # > Task :compose:ui:ui:processDebugAndroidTestManifest
 \$OUT_DIR/androidx/compose/ui/ui/build/intermediates/tmp/manifest/androidTest/debug/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml Warning:
 Package name 'androidx\.compose\.ui\.test' used in: tempFile[0-9]+ProcessTestManifest[0-9]+\.xml, :compose:ui:ui\-test\.
-\$OUT_DIR\/androidx\/compose\/ui\/ui\/build\/intermediates\/tmp\/manifest\/androidTest\/debug\/manifestMerger[0-9]+\.xml Warning\:
-Package name \'androidx\.compose\.ui\.test\' used in\: manifestMerger[0-9]+\.xml\, \:compose\:ui\:ui\-test\.
 # > Task :buildSrc:build UP-TO-DATE
 See the profiling report at\: file\:\/\/\$OUT_DIR\/buildSrc\/build\/reports\/profile\/profile\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\-[0-9]+\.html
 A fine\-grained performance profile is available\: use the \-\-scan option\.
@@ -513,48 +340,14 @@
 \-XXLanguage\:\+InlineClasses
 # > Task :docs-public:lintDebug
 Wrote HTML report to file://\$OUT_DIR/androidx/docs\-public/build/reports/lint\-results\-debug\.html
-Wrote XML report to file://\$OUT_DIR/androidx/docs\-public/build/reports/lint\-results\-debug\.xml
 # > Task :internal-testutils-appcompat:processDebugAndroidTestManifest
 \[:internal\-testutils\-appcompat\] \$OUT_DIR/androidx/internal\-testutils\-appcompat/build/intermediates/merged_manifest/debug/AndroidManifest\.xml Warning:
-# > Task :support-wear-demos:packageDebugAndroidTest
-PackagingOptions\.jniLibs\.useLegacyPackaging should be set to true because android:extractNativeLibs is set to "true" in AndroidManifest\.xml\.
-# > Task :camera:integration-tests:camera-testapp-camera2-pipe:dexBuilderDebug
-WARNING:\$OUT_DIR/androidx/camera/integration\-tests/camera\-testapp\-camera[0-9]+\-pipe/build/intermediates/jacoco_instrumented_classes/debug/out/androidx/camera/integration/camera[0-9]+/pipe/DebugKt\.class: D[0-9]+: Invalid stack map table at [0-9]+: aload [0-9]+, error: The expected type Initialized\(double\) is not assignable to java\.lang\.Object\.
-\$OUT_DIR/androidx/camera/integration\-tests/camera\-testapp\-camera[0-9]+\-pipe/build/intermediates/jacoco_instrumented_classes/debug/out/androidx/camera/integration/camera[0-9]+/pipe/DebugKt\.class: D[0-9]+: Invalid stack map table at [0-9]+: aload [0-9]+, error: The expected type Initialized\(double\) is not assignable to java\.lang\.Object\.
-# > Task :support-remotecallback-demos:lintDebug
-[0-9]+ errors\, [0-9]+ warnings \([0-9]+ errors filtered by baseline lint\-baseline\.xml\)
-# > Task :camera:camera-camera2-pipe-integration:lintDebug
-[0-9]+ errors, [0-9]+ warnings
 # > Task :camera:camera-core:compileDebugJavaWithJavac
 warning: unknown enum constant AnnotationRetention\.BINARY
-# > Task :navigation:navigation-dynamic-features-fragment:compileDebugKotlin
-w: \$SUPPORT/navigation/navigation\-dynamic\-features\-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment\.kt: \([0-9]+, [0-9]+\): 'startIntentSenderForResult\(IntentSender!, Int, Intent\?, Int, Int, Int, Bundle\?\): Unit' is deprecated\. Deprecated in Java
-w: \$SUPPORT/navigation/navigation\-dynamic\-features\-fragment/src/main/java/androidx/navigation/dynamicfeatures/fragment/ui/AbstractProgressFragment\.kt: \([0-9]+, [0-9]+\): 'onActivityResult\(Int, Int, Intent\?\): Unit' is deprecated\. Deprecated in Java
-# > Task :inspection:inspection-gradle-plugin:test
-There were failing tests\. See the report at: .*.html
 # > Task :compose:ui:ui:processDebugUnitTestManifest
 \$OUT_DIR/androidx/compose/ui/ui/build/intermediates/tmp/manifest/test/debug/tempFile[0-9]+ProcessTestManifest[0-9]+\.xml Warning:
-\$OUT_DIR/androidx/compose/ui/ui/build/intermediates/tmp/manifest/test/debug/manifestMerger[0-9]+\.xml Warning:
 # > Task :compose:foundation:foundation:reportLibraryMetrics
-Stripped invalid locals information from [0-9]+ methods\.
-Methods with invalid locals information:
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.ZoomableKt\$detectZoom\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-Type information in locals\-table is inconsistent\. Cannot constrain type: @Nullable java\.util\.List \{\} for value: v[0-9]+\(\$this\$fastAny\$iv\) by constraint FLOAT\.
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.DragGestureDetectorKt\$awaitLongPressOrCancellation\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.DragGestureDetectorKt\.awaitVerticalTouchSlopOrCancellation\-jO[0-9]+t[0-9]+\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, long, kotlin\.jvm\.functions\.Function[0-9]+, kotlin\.coroutines\.Continuation\)
-Type information in locals\-table is inconsistent\. Cannot constrain type: BOTTOM \(empty\) for value: v[0-9]+ by constraint INT\.
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.TransformableKt\$detectZoom\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.DraggableKt\$draggable\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-Type information in locals\-table is inconsistent\. Cannot constrain type: BOTTOM \(empty\) for value: v[0-9]+\(\$this\$fastAny\$iv\) by constraint FLOAT\.
-Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
-Type information in locals\-table is inconsistent\. Cannot constrain type: BOTTOM \(empty\) for value: v[0-9]+ by constraint FLOAT\.
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.DragGestureDetectorKt\.awaitHorizontalTouchSlopOrCancellation\-jO[0-9]+t[0-9]+\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, long, kotlin\.jvm\.functions\.Function[0-9]+, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.TransformGestureDetectorKt\$detectTransformGestures\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 java\.lang\.Object androidx\.compose\.foundation\.lazy\.LazyListScrollingKt\$doSmoothScrollToItem\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint FLOAT\.
-Type information in locals\-table is inconsistent\. Cannot constrain type: INT for value: v[0-9]+\(index\$iv\$iv\) by constraint FLOAT\.
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.TapGestureDetectorKt\.translatePointerEventsToChannel\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, kotlinx\.coroutines\.CoroutineScope, kotlinx\.coroutines\.channels\.SendChannel, androidx\.compose\.runtime\.State, androidx\.compose\.runtime\.MutableState, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.compose\.foundation\.gestures\.TapGestureDetectorKt\.waitForUpOrCancellation\(androidx\.compose\.ui\.input\.pointer\.AwaitPointerEventScope, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.compose\.foundation\.gestures\.ForEachGestureKt\.forEachGesture\(androidx\.compose\.ui\.input\.pointer\.PointerInputScope, kotlin\.jvm\.functions\.Function[0-9]+, kotlin\.coroutines\.Continuation\)
 # > Task :preference:preference:compileDebugAndroidTestKotlin
 w\: \$SUPPORT\/preference\/preference\/src\/androidTest\/java\/androidx\/preference\/tests\/PreferenceDialogFragmentCompatTest\.kt\: \([0-9]+\, [0-9]+\)\: \'setTargetFragment\(Fragment\?\, Int\)\: Unit\' is deprecated\. Deprecated in Java
@@ -592,11 +385,6 @@
 Found an unresolved type in androidx\.compose\.ui\.graphics\.vector\$group\(androidx\.compose\.ui\.graphics\.vector\.ImageVector\.Builder, kotlin\.String, kotlin\.Float, kotlin\.Float, kotlin\.Float, kotlin\.Float, kotlin\.Float, kotlin\.Float, kotlin\.Float, kotlin\.collections\.List\(\(androidx\.compose\.ui\.graphics\.vector\.PathNode\)\), kotlin\.Function[0-9]+\(\(androidx\.compose\.ui\.graphics\.vector\.ImageVector\.Builder, kotlin\.Unit\)\)\) \(ImageVector\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.ui\.unit\$constrainWidth\(androidx\.compose\.ui\.unit\.Constraints, kotlin\.Int\) \(Constraints\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.ui\.unit\$constrainHeight\(androidx\.compose\.ui\.unit\.Constraints, kotlin\.Int\) \(Constraints\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\$ActivityNavigatorExtras\(androidx\.core\.app\.ActivityOptionsCompat, kotlin\.Int\) \(ActivityNavigatorExtras\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\$navigation\(androidx\.navigation\.NavigatorProvider, kotlin\.Int, kotlin\.Int, kotlin\.Function[0-9]+\(\(androidx\.navigation\.NavGraphBuilder, kotlin\.Unit\)\)\) \(NavGraphBuilder\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\.dynamicfeatures\.DynamicActivityNavigatorDestinationBuilder\$build\(\) \(DynamicActivityNavigatorDestinationBuilder\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\.dynamicfeatures\.fragment\.DynamicFragmentNavigatorDestinationBuilder\$build\(\) \(DynamicFragmentNavigatorDestinationBuilder\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\.fragment\$FragmentNavigatorExtras\(kotlin\.Array\(\(kotlin\.Pair\(\(android\.view\.View, kotlin\.String\)\)\)\)\) \(FragmentNavigatorExtras\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.RxPagedListBuilder\$setInitialLoadKey\(androidx\.paging\.RxPagedListBuilder\.Key\) \(RxPagedListBuilder\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.RxPagedListBuilder\$setBoundaryCallback\(androidx\.paging\.PagedList\.BoundaryCallback\(\(androidx\.paging\.RxPagedListBuilder\.Value\)\)\) \(RxPagedListBuilder\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.RxPagedListBuilder\$setNotifyScheduler\(io\.reactivex\.Scheduler\) \(RxPagedListBuilder\.kt:[0-9]+\)
@@ -604,7 +392,6 @@
 Found an unresolved type in androidx\.security\.crypto\$EncryptedFile\(android\.content\.Context, java\.io\.File, androidx\.security\.crypto\.MasterKey, androidx\.security\.crypto\.EncryptedFile\.FileEncryptionScheme, kotlin\.String, kotlin\.String\) \(EncryptedFile\.kt:[0-9]+\)
 Found an unresolved type in androidx\.slice\.builders\$list\(android\.content\.Context, android\.net\.Uri, kotlin\.Long, kotlin\.Function[0-9]+\(\(androidx\.slice\.builders\.ListBuilderDsl, kotlin\.Unit\)\)\) \(ListBuilder\.kt:[0-9]+\)
 # See b/180023439 for hiltNavGraphViewModel warning.
-Found an unresolved type in androidx\.hilt\.navigation\.compose\$hiltNavGraphViewModel\(androidx\.navigation\.NavController, kotlin\.String\) \(NavHostController\.kt:[0-9]+\)
 Unresolved link to .*
 Found an unresolved type in androidx\.compose\.foundation\.MutatorMutex\$mutate\(androidx\.compose\.foundation\.MutatePriority, kotlin\.coroutines\.SuspendFunction[0-9]+\(\(androidx\.compose\.foundation\.MutatorMutex\.mutate\.R\)\)\) \(MutatorMutex\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.foundation\.MutatorMutex\$mutateWith\(androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.T, androidx\.compose\.foundation\.MutatePriority, kotlin\.coroutines\.SuspendFunction[0-9]+\(\(androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.T, androidx\.compose\.foundation\.MutatorMutex\.mutateWith\.R\)\)\) \(MutatorMutex\.kt:[0-9]+\)
@@ -618,44 +405,29 @@
 Found an unresolved type in androidx\.compose\.runtime\$mutableStateMapOf\(kotlin\.Array\(\(kotlin\.Pair\(\(androidx\.compose\.runtime\.mutableStateMapOf\.K, androidx\.compose\.runtime\.mutableStateMapOf\.V\)\)\)\)\) \(SnapshotState\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.runtime\$toMutableStateMap\(kotlin\.collections\.Iterable\(\(kotlin\.Pair\(\(androidx\.compose\.runtime\.toMutableStateMap\.K, androidx\.compose\.runtime\.toMutableStateMap\.V\)\)\)\)\) \(SnapshotState\.kt:[0-9]+\)
 Found an unresolved type in androidx\.compose\.runtime\.collection\.MutableVector\$sortWith\(\(\(androidx\.compose\.runtime\.collection\.MutableVector\.T\)\)\) \(MutableVector\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\.dynamicfeatures\$navigation\(androidx\.navigation\.NavigatorProvider, kotlin\.Int, kotlin\.Int, kotlin\.Function[0-9]+\(\(androidx\.navigation\.dynamicfeatures\.DynamicNavGraphBuilder, kotlin\.Unit\)\)\) \(DynamicNavGraphBuilder\.kt:[0-9]+\)
-Found an unresolved type in androidx\.navigation\.dynamicfeatures\$createGraph\(androidx\.navigation\.NavController, kotlin\.Int, kotlin\.Int, kotlin\.Function[0-9]+\(\(androidx\.navigation\.dynamicfeatures\.DynamicNavGraphBuilder, kotlin\.Unit\)\)\) \(NavController\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.LivePagedListBuilder\$setCoroutineScope\(kotlinx\.coroutines\.CoroutineScope\) \(LivePagedListBuilder\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.LivePagedListBuilder\$setInitialLoadKey\(androidx\.paging\.LivePagedListBuilder\.Key\) \(LivePagedListBuilder\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.LivePagedListBuilder\$setBoundaryCallback\(androidx\.paging\.PagedList\.BoundaryCallback\(\(androidx\.paging\.LivePagedListBuilder\.Value\)\)\) \(LivePagedListBuilder\.kt:[0-9]+\)
 Found an unresolved type in androidx\.paging\.LivePagedListBuilder\$setFetchExecutor\(java\.util\.concurrent\.Executor\) \(LivePagedListBuilder\.kt:[0-9]+\)
-Unresolved function .*
-# > Task :compose:ui:ui-inspection:externalNativeBuildDebug
-Build compose_inspection_jni_.*
 # > Task :compose:foundation:foundation-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/foundation/foundation/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/foundation\/foundation\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:material:material-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/material/material/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/material\/material\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:foundation:foundation-layout:foundation-layout-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/foundation/foundation\-layout/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/foundation\/foundation\-layout\/foundation\-layout\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:ui:ui-text:ui-text-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/ui/ui\-text/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/ui\/ui\-text\/ui\-text\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:benchmark-utils:benchmark-utils-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/benchmark\-utils/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/benchmark\-utils\/benchmark\-utils\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:ui:ui-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/ui/ui/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/ui\/ui\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:animation:animation-core:animation-core-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/animation/animation\-core/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/animation\/animation\-core\/animation\-core\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :compose:ui:ui-graphics:ui-graphics-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/compose/ui/ui\-graphics/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-\$OUT_DIR\/androidx\/compose\/ui\/ui\-graphics\/ui\-graphics\-benchmark\/build\/intermediates\/tmp\/manifest\/androidTest\/release\/manifestMerger[0-9]+\.xml\:[0-9]+\:[0-9]+\-[0-9]+\:[0-9]+ Warning\:
 # > Task :docs-public:doclavaDocs
 \$OUT_DIR\/androidx\/docs\-public\/build\/unzippedDocsSources\/androidx\/work\/impl\/WorkManagerImpl\.java\:[0-9]+\: error\: cannot find symbol
 import static android\.app\.PendingIntent\.FLAG_MUTABLE\;
-\$OUT_DIR/androidx/docs\-public/build/unzippedDocsSources/androidx/window/ExtensionAdapter\.java:[0-9]+: error: cannot find symbol
-import androidx\.window\.extensions\.ExtensionDeviceState;
 # > Task :docs-public:dackkaDocs
 PROGRESS: Initializing plugins
 Loaded plugins: \[org\.jetbrains\.dokka\.base\.DokkaBase, com\.google\.devsite\.DevsitePlugin\]
@@ -733,6 +505,7 @@
 PROGRESS: Validity check
 PROGRESS: Creating documentation models
 ERROR: An attempt to write .*
+WARN: Unable to find what is referred to by
 Generation completed with.*
 # > Task :docs-tip-of-tree:dackkaDocs
 Conflicting documentation for .*
@@ -742,7 +515,6 @@
 PROGRESS: Creating pages
 PROGRESS: Transforming pages
 Unused extension points found:.*
-generation completed successfully
 PROGRESS:
 === TIME MEASUREMENT ===
 Initializing plugins\: *[0-9]+
@@ -754,371 +526,44 @@
 Creating pages\: *[0-9]+
 Transforming pages\: *[0-9]+
 Rendering\: *[0-9]+
-WARNING\: unable to find what is referred to by
 @param _value
 in DClass ObservableWatchData
 @param currentUserStyleRepository,
 @param watchState
 in DClass Renderer
 @param bounds
-in DClass Complication
 in DClass ComplicationSlot
-@param maxSlotsToRetainForReuse
-in DClass SubcomposeLayoutState
-@param initVal
-in DClass AnimationVector[0-9]+D
-@param a,
-@param b,
-@param c,
-@param d
-in DClass CubicBezierEasing
-@param v[0-9]+,
-@param v[0-9]+
-@param frictionMultiplier,
-@param absVelocityThreshold
-in DClass FloatExponentialDecaySpec
-@param duration,
-@param delay,
-@param easing
-in DClass FloatTweenSpec
-@param dampingRatio,
-@param stiffness,
-@param visibilityThreshold
-in DClass SpringSpec
-@param durationMillis,
-in DClass TweenSpec
-in DClass FloatSpringSpec
-@param iterations,
-@param animation,
-@param repeatMode
-in DClass RepeatableSpec
-in DClass VectorizedInfiniteRepeatableSpec
-@param keyframes,
-@param delayMillis
-in DClass VectorizedKeyframesSpec
-@param delayMillis,
-in DClass VectorizedTweenSpec
-@param animationSpec,
-@param initialValue,
-@param targetValue,
-@param typeConverter,
-@param initialVelocityVector
-in DClass TargetBasedAnimation
-@param delay
-in DClass SnapSpec
-in DClass InfiniteRepeatableSpec
-in DClass VectorizedRepeatableSpec
-@param initialVelocityVector,
-@param lastFrameTimeNanos,
-@param finishedTimeNanos,
-@param isRunning
-in DClass AnimationState
-in DClass VectorizedSnapSpec
-in DClass Animatable
-@param width,
-@param brush
-in DClass BorderStroke
-@param initial
-in DClass ScrollState
-@param firstVisibleItemIndex,
-@param firstVisibleItemScrollOffset
-in DClass LazyListState
-@param builder
-in DClass GenericShape
-@param topLeft,
-@param topRight,
-@param bottomRight,
-@param bottomLeft
-in DClass AbsoluteCutCornerShape
-@param topStart,
-@param topEnd,
-@param bottomEnd,
-@param bottomStart
-in DClass RoundedCornerShape
-in DClass CutCornerShape
-in DClass AbsoluteRoundedCornerShape
-in DClass CornerBasedShape
-@param capitalization,
-@param autoCorrect,
-@param keyboardType,
-@param imeAction
-in DClass KeyboardOptions
-@param drawerState,
-@param bottomSheetState,
-@param snackbarHostState
-in DClass BottomSheetScaffoldState
-@param confirmStateChange,
-in DClass BackdropScaffoldState
-@param confirmStateChange
-in DClass DismissState
-in DClass BottomDrawerState
-in DClass ScaffoldState
-@param basis,
-@param factorAtMin,
-@param factorAtMax
-in DClass ResistanceConfig
-in DClass BottomSheetState
-in DClass DrawerState
-@param offset
-in DClass FixedThreshold
-@param fraction
-in DClass FractionalThreshold
-in DClass ModalBottomSheetState
-@param from,
-@param to,
-in DClass SwipeProgress
-in DClass SwipeableState
-@param file
-in DAnnotation LiveLiteralFileInfo
-@param name,
-@param id
-in DAnnotation DecoyImplementation
-@param key,
-in DAnnotation LiveLiteralInfo
-@param parameters
-in DAnnotation StabilityInferred
-@param targetName,
-@param signature
-in DAnnotation Decoy
-@param left,
-@param top,
-@param right,
-@param bottom
-in DClass MutableRect
-@param buffer,
-@param bufferOffset,
-@param height,
-@param stride
-in DClass PixelMap
-@param image,
-@param srcOffset,
-@param srcSize
-in DClass BitmapPainter
-@param imageVector
-in DClass AnimatedImageVector
-@param keyCode
-in DClass Key
-@param value
-in DClass PointerId
-@param positionChange,
-@param downChange
-in DClass ConsumedData
 @param id,
 @param canvasComplicationFactory,
 @param supportedTypes,
 \@param defaultProviderPolicy\,
 @param defaultDataSourcePolicy,
-@param defaultPolicy,
 @param boundsType,
 @param bounds,
 \@param defaultPolicy
 @param complicationTapFilter
-@param uptimeMillis,
-@param position,
-@param pressed,
-@param previousUptimeMillis,
-@param previousPosition,
-@param previousPressed,
-@param consumed,
-@param type
-in DClass PointerInputChange
-@param merger
-in DClass VerticalAlignmentLine
-in DClass HorizontalAlignmentLine
-@param value,
-@param maxValue,
-@param reverseScrolling
-in DClass ScrollAxisRange
-@param current,
-@param range,
-@param steps
-in DClass ProgressBarRangeInfo
-@param label,
-@param action
-in DClass AccessibilityAction
-in DClass CustomAccessibilityAction
-@param selectedNodes,
-@param customErrorOnNoMatch
-in DClass SelectionResult
-@param description,
-@param requiresExactlyOneNode,
-@param chainedInputSelector,
-@param selector
-in DClass SemanticsSelector
-@param activityRule,
-@param activityProvider
-in DClass AndroidComposeTestRule
-@param placeholderVerticalAlign
-in DClass Placeholder
-@param item,
-@param start,
-@param end,
-@param tag
-in DClass Range
-@param verbatim
-in DClass VerbatimTtsAnnotation
-@param annotatedString,
-@param style,
-@param placeholders,
-@param density,
-@param resourceLoader
-in DClass MultiParagraphIntrinsics
-@param textAlign,
-@param textDirection,
-@param textIndent,
-@param lineHeight
-in DClass ParagraphStyle
-@param color,
-@param fontSize,
-@param fontWeight,
-@param fontStyle,
-@param fontSynthesis,
-@param fontFamily,
-@param fontFeatureSettings,
-@param letterSpacing,
-@param baselineShift,
-@param textGeometricTransform,
-@param localeList,
-@param background,
-@param textDecoration,
-@param shadow
-in DClass SpanStyle
-@param shadow,
-in DClass TextStyle
-@param intrinsics,
-@param maxLines,
-@param ellipsis,
-@param width
-in DClass MultiParagraph
-@param capacity
 in DClass Builder
-@param typeface
-in DClass LoadedFontFamily
-@param resId,
-@param weight,
-@param style
-in DClass ResourceFont
-@param name
-in DClass GenericFontFamily
-@param weight
-in DClass FontWeight
-@param singleLine,
-in DClass ImeOptions
-@param selection,
-@param composition
-in DClass TextFieldValue
-@param mask
-in DClass PasswordVisualTransformation
-@param multiplier
-in DClass BaselineShift
-@param firstLine,
-@param restLine
-in DClass TextIndent
-@param scaleX,
-@param skewX
-in DClass TextGeometricTransform
-@param provider,
-@param limit
-in DAnnotation PreviewParameter
-@param group,
-@param apiLevel,
-@param widthDp,
-@param heightDp,
-@param locale,
-@param fontScale,
-@param showSystemUi,
-@param showBackground,
-@param backgroundColor,
-@param uiMode,
-@param device
-in DAnnotation Preview
-@param words
-in DClass LoremIpsum
-@param securePolicy
-in DClass PopupProperties
-@param rowCount,
-@param columnCount
-in DClass CollectionInfo
-@param rowIndex,
-@param rowSpan,
-@param columnIndex,
-@param columnSpan
-in DClass CollectionItemInfo
-@param endOfPaginationReached
-in DClass LoadState
-in DClass NotLoading
-@param error
-in DClass Error
-@param pagingSourceFactory
-in DClass InvalidatingPagingSourceFactory
-@param flow
-in DClass LazyPagingItems
-@param density
-in DClass SplineBasedFloatDecayAnimationSpec
 Did you make a typo\? Are you trying to refer to something not visible to users\?
 # Wire proto generation, task :generateDebugProtos
 Writing .* to \$OUT_DIR/.*/build/generated/source/wire
 # > Task :docs-tip-of-tree:doclavaDocs
 javadoc\: warning \- Multiple sources of package comments found for package \"javax\.annotation\"
 [0-9]+ warning
-# > Task :profileinstaller:profileinstaller:integration-tests:testapp:compileReleaseKotlin
-w: Flag is not supported by this version of the compiler: \-Xallow\-jvm\-ir\-dependencies
-# > Task :room:room-compiler-processing-testing:sourceJar
-Execution optimizations have been disabled for task ':room:room\-compiler\-processing\-testing:sourceJar' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/room/room\-compiler\-processing\-testing/build/test\-config'\. Reason: Task ':room:room\-compiler\-processing\-testing:sourceJar' uses this output of task ':room:room\-compiler\-processing\-testing:prepareTestConfiguration' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-# > Task :wear:tiles:tiles-proto:sourceJar
-Execution optimizations have been disabled for task ':wear:tiles:tiles\-proto:sourceJar' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/wear/tiles/tiles\-proto/build/generated/source/proto'\. Reason: Task ':wear:tiles:tiles\-proto:sourceJar' uses this output of task ':wear:tiles:tiles\-proto:generateProto' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-# > Task :appsearch:appsearch-local-storage:desugarDebugAndroidTestFileDependencies
-Execution optimizations have been disabled for task ':appsearch:appsearch\-local\-storage:desugarDebugAndroidTestFileDependencies' to ensure correctness due to the following reasons:
-# > Task :appsearch:appsearch:desugarDebugAndroidTestFileDependencies
-Execution optimizations have been disabled for task ':appsearch:appsearch:desugarDebugAndroidTestFileDependencies' to ensure correctness due to the following reasons:
-# > Task :appsearch:appsearch-debug-view:desugarDebugAndroidTestFileDependencies
-Execution optimizations have been disabled for task ':appsearch:appsearch-debug-view:desugar.*AndroidTestFileDependencies' to ensure correctness due to the following reasons:
-# > Task :appsearch:appsearch-debug-view:samples:desugarDebugFileDependencies
-Execution optimizations have been disabled for task ':appsearch:appsearch-debug-view:samples:desugar.*FileDependencies' to ensure correctness due to the following reasons:
 # > Task :stripArchiveForPartialDejetification
 Execution optimizations have been disabled for task ':stripArchiveForPartialDejetification' to ensure correctness due to the following reasons:
 \- Gradle detected a problem with the following location: '\$DIST_DIR/top\-of\-tree\-m2repository\-all\-.*.zip'\. Reason: Task ':stripArchiveForPartialDejetification' uses this output of task ':benchmark:benchmark\-gradle\-plugin:buildOnServer' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
 # > Task :icing:lintDebug
 Wrote HTML report to file://\$OUT_DIR/androidx/icing/build/reports/lint\-results\-debug\.html
-Wrote XML report to file://\$OUT_DIR/androidx/icing/build/reports/lint\-results\-debug\.xml
-# > Task :zipConstrainedTestConfigsWithApks
-Execution optimizations have been disabled for task ':zipConstrainedTestConfigsWithApks' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$DIST_DIR/constrained\-test\-xml\-configs'\. Reason: Task ':zipConstrainedTestConfigsWithApks' uses this output of task ':benchmark:benchmark\-gradle\-plugin:buildOnServer' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-# > Task :zipTestConfigsWithApks
-Execution optimizations have been disabled for task ':zipTestConfigsWithApks' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$DIST_DIR/test\-xml\-configs'\. Reason: Task ':zipTestConfigsWithApks' uses this output of task ':benchmark:benchmark\-gradle\-plugin:buildOnServer' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-# > Task :room:room-compiler:checkArtifactTask
-Execution optimizations have been disabled for task ':room:room\-compiler:checkArtifactTask' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/room/room\-compiler/build/libs/room\-compiler\-[0-9]+\.[0-9]+\.[0-9]+\-alpha[0-9]+\.jar'\. Reason: Task ':room:room\-compiler:checkArtifactTask' uses this output of task ':room:room\-compiler:shadowJar' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-\- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/room/room\-compiler/build/publications/maven/pom\-default\.xml'\. Reason: Task ':room:room\-compiler:checkArtifactTask' uses this output of task ':room:room\-compiler:generatePomFileForMavenPublication' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-\- Gradle detected a problem with the following location: '\$OUT_DIR'\. Reason: Task ':zipEcFiles' uses this output of task.*
-Execution optimizations have been disabled for task ':zipEcFiles' to ensure correctness due to the following reasons:
-\- Gradle detected a problem with the following location: '\$OUT_DIR/.*/build/repackaged/repackaged\-Debug\.jar'\. Reason:.*
-\- Gradle detected a problem with the following location: '\$OUT_DIR/.*/build/repackaged/repackaged\-Release\.jar'\. Reason:.*
-Execution optimizations have been disabled for task ':emoji.*
-Execution optimizations have been disabled for task ':support-emoji-demos:.*
-# https://issues.apache.org/jira/browse/GROOVY-8843
-WARNING: Illegal reflective access by org\.codehaus\.groovy\.vmplugin\.v7\.Java7\$1 .* to constructor java\.lang\.invoke\.MethodHandles\$Lookup\(java\.lang\.Class,int\)
-WARNING: Please consider reporting this to the maintainers of org\.codehaus\.groovy\.vmplugin\.v7\.Java7\$1
 # https://youtrack.jetbrains.com/issue/KT-30589
 WARNING: Illegal reflective access by org\.jetbrains\.kotlin\.kapt3\.base\.javac\.KaptJavaFileManager .* to method com\.sun\.tools\.javac\.file\.BaseFileManager\.handleOption\(com\.sun\.tools\.javac\.main\.Option,java\.lang\.String\)
 # > Task :benchmark:benchmark-macro:compileReleaseKotlin
 Execution optimizations have been disabled for task ':benchmark:benchmark\-macro:.*' to ensure correctness due to the following reasons:
 \- Gradle detected a problem with the following location: '\$OUT_DIR/androidx/benchmark/benchmark\-macro/build/generated/source/wire'\. Reason: Task ':benchmark:benchmark\-macro:.*' uses this output of task ':benchmark:benchmark\-macro:.*' without declaring an explicit or implicit dependency\. This can lead to incorrect results being produced, depending on what order the tasks are executed\. Please refer to https://docs\.gradle\.org/[0-9]+\.[0-9]+/userguide/validation_problems\.html\#implicit_dependency for more details about this problem\.
-# > Task :support-emoji-demos:lintDebug
-Error processing .* broken class file\? \(This feature requires ASM[0-9]+\)
 # > Task :profileinstaller:profileinstaller:processDebugUnitTestManifest
-\$SUPPORT/profileinstaller/profileinstaller/src/test/AndroidManifest\.xml Warning:
-Unknown flag --class-dir: Use --help for usage information
 Scanning .+: \.*
-\.+
 # > Task :compose:ui:ui-tooling:processDebugAndroidTestManifest
 \$SUPPORT/compose/ui/ui\-tooling/src/androidAndroidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 No issues found.*
-Incorrect detector reported disabled issue.*
-Scanning .* \(Phase 2\):
 dagger\.lint\.DaggerIssueRegistry in .*/lint\.jar does not specify a vendor; see IssueRegistry#vendor
 # > Task :camera:camera-camera2-pipe:reportLibraryMetrics
 Info: Stripped invalid locals information from [0-9]+ methods\.
@@ -1126,14 +571,10 @@
 java\.lang\.Object androidx\.wear\.complications\.ComplicationDataSourceInfoRetriever\.retrievePreviewComplicationData\(android\.content\.ComponentName, androidx\.wear\.complications\.data\.ComplicationType, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\.openComplicationDataSourceChooser\$suspendImpl\(androidx\.wear\.watchface\.editor\.BaseEditorSession, int, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.compose\.ui\.platform\.GlobalSnapshotManager\$ensureStarted\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-java\.lang\.Object androidx\.wear\.watchface\.WatchFaceService\$EngineWrapper\.createWatchFaceImpl\(android\.icu\.util\.Calendar, androidx\.wear\.watchface\.ComplicationSlotsManager, androidx\.wear\.watchface\.style\.CurrentUserStyleRepository, kotlinx\.coroutines\.CompletableDeferred, kotlinx\.coroutines\.CompletableDeferred, androidx\.wear\.watchface\.WatchState, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.wear\.watchface\.WatchFaceService\$EngineWrapper\.setUserStyle\$wear_watchface_release\(androidx\.wear\.watchface\.style\.data\.UserStyleWireFormat, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\$fetchComplicationsData\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.CameraDevicesKt\$find\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.VirtualCameraManager\$requestLoop\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint OBJECT\.
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.graph\.Controller[0-9]+A\.submit[0-9]+A\(androidx\.camera\.camera[0-9]+\.pipe\.AeMode, androidx\.camera\.camera[0-9]+\.pipe\.AfMode, androidx\.camera\.camera[0-9]+\.pipe\.AwbMode, java\.util\.List, java\.util\.List, java\.util\.List, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.graph\.Controller[0-9]+A\.lock[0-9]+A\(java\.util\.List, java\.util\.List, java\.util\.List, androidx\.camera\.camera[0-9]+\.pipe\.Lock[0-9]+ABehavior, androidx\.camera\.camera[0-9]+\.pipe\.Lock[0-9]+ABehavior, androidx\.camera\.camera[0-9]+\.pipe\.Lock[0-9]+ABehavior, int, java\.lang\.Long, kotlin\.coroutines\.Continuation\)
 java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.VirtualCameraManager\.openCameraWithRetry\-RzXb[0-9]+QE\(java\.lang\.String, kotlinx\.coroutines\.CoroutineScope, kotlin\.coroutines\.Continuation\)
 Information in locals\-table is invalid with respect to the stack map table\. Local refers to non\-present stack map type for register: [0-9]+ with constraint INT\.
 Info: Some warnings are typically a sign of using an outdated Java toolchain\. To fix, recompile the source with an updated toolchain\.
@@ -1142,40 +583,21 @@
 java\.lang\.Object androidx\.paging\.samples\.RemoteMediatorSampleKt\$remoteMediatorPageKeyedSample\$ExampleRemoteMediator\.load\(androidx\.paging\.LoadType, androidx\.paging\.PagingState, kotlin\.coroutines\.Continuation\)
 # > Task :wear:wear-complications-data:reportLibraryMetrics
 Info: Stripped invalid locals information from [0-9]+ method\.
-java\.lang\.Object androidx\.wear\.complications\.ProviderInfoRetriever\.retrievePreviewComplicationData\(android\.content\.ComponentName, androidx\.wear\.complications\.data\.ComplicationType, kotlin\.coroutines\.Continuation\)
 # > Task :wear:wear-watchface-editor:reportLibraryMetrics
 java\.lang\.Object androidx\.wear\.watchface\.editor\.BaseEditorSession\.getPreviewData\$wear_watchface_editor_release\(androidx\.wear\.complications\.ComplicationDataSourceInfoRetriever, androidx\.wear\.complications\.ComplicationDataSourceInfo, kotlin\.coroutines\.Continuation\)
 # > Task :work:work-runtime-ktx:reportLibraryMetrics
 java\.lang\.Object androidx\.work\.OperationKt\.await\(androidx\.work\.Operation, kotlin\.coroutines\.Continuation\)
 # > Task :compose:material:material:reportLibraryMetrics
 java\.lang\.Object androidx\.compose\.material\.SnackbarHostState\.showSnackbar\(java\.lang\.String, java\.lang\.String, androidx\.compose\.material\.SnackbarDuration, kotlin\.coroutines\.Continuation\)
-java\.lang\.Object androidx\.compose\.material\.SwipeableState\.processNewAnchors\$material_release\(java\.util\.Map, java\.util\.Map, kotlin\.coroutines\.Continuation\)
-# > Task :car:app:app-samples:helloworld-automotive:processReleaseMainManifest
-\[:car:app:app\-automotive\] \$OUT_DIR/androidx/car/app/app\-automotive/build/intermediates/merged_manifest/release/AndroidManifest\.xml Warning:
-Package name 'androidx\.car\.app' used in: :car:app:app\-automotive, :car:app:app\.
-# > Task :car:app:app-automotive:processDebugUnitTestManifest
-\[:car:app:app\-automotive\] \$OUT_DIR/androidx/car/app/app\-automotive/build/intermediates/merged_manifest/debug/AndroidManifest\.xml Warning:
-# > Task :camera:camera-video:lintDebug
-Error processing \$CHECKOUT/prebuilts/androidx/external/org/jetbrains/kotlin/kotlin\-stdlib/[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+/kotlin\-stdlib\-[0-9]+\.[0-9]+\.[0-9]+\-M[0-9]+\.jar:META\-INF/versions/[0-9]+/module\-info\.class: broken class file\? \(This feature requires ASM[0-9]+\)
 # > Task :compose:foundation:foundation:androidReleaseSourcesJar
 Encountered duplicate path "android[a-zA-Z]*/.+" during copy operation configured with DuplicatesStrategy\.WARN
 # > Task :camera:camera-camera2-pipe:reportLibraryMetrics
 java\.lang\.Object androidx\.camera\.camera[0-9]+\.pipe\.compat\.Camera[0-9]+DeviceCache\$getCameras\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
 Type information in locals\-table is inconsistent\. Cannot constrain type: @Nullable androidx\.camera\.camera[0-9]+\.pipe\.core\.Debug \{\} for value: v[0-9]+\(this_\$iv\$iv\) by constraint INT\.
 # > Task :compose:foundation:foundation:integration-tests:foundation-demos:reportLibraryMetrics
-Stripped invalid locals information from [0-9]+ method\.
 java\.lang\.Object androidx\.compose\.foundation\.demos\.ListDemosKt\$ListHoistedStateDemo\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\$[0-9]+\.invokeSuspend\(java\.lang\.Object\)
-# > Task :compose:runtime:runtime-saveable-lint:compileKotlin
-at org\.jetbrains\.kotlin\.compilerRunner\.GradleCompilerRunnerWithWorkers\$GradleKotlinCompilerWorkAction\.execute\(GradleCompilerRunnerWithWorkers\.kt\:[0-9]+\)
-# > Task :buildSrc:jetpad-integration:compileJava
-Could not store entry [0-9a-f]{32} in remote build cache: Storing entry at 'http://gradle\-remote\-cache\.uplink\.goog:[0-9]+/cache/[0-9a-f]{32}' response status [0-9]+: Uplink Bad Gateway
 # > Task :profileinstaller:profileinstaller-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/profileinstaller/profileinstaller\-benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
-WARNING:Software Components will not be created automatically for Maven publishing from Android Gradle Plugin 8\.0\..*
-# > Task :wear:wear-watchface:compileReleaseKotlin
-w: \$SUPPORT/wear/wear\-watchface/src/main/java/androidx/wear/watchface/WatchFaceService\.kt: \([0-9]+, [0-9]+\): Parameter 'surfaceHolder' is never used
-# > Task :lint-checks:integration-tests:copyDebugAndroidLintReports
-Copying lint text report to .*
 # > Task :wear:compose:compose-material-benchmark:processReleaseAndroidTestManifest
 \$SUPPORT/wear/compose/material/benchmark/src/androidTest/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Configure project :ads-identifier
@@ -1190,3 +612,5 @@
 \$SUPPORT/emoji[0-9]+/emoji[0-9]+\-bundled/src/main/AndroidManifest\.xml:[0-9]+:[0-9]+\-[0-9]+:[0-9]+ Warning:
 # > Configure project :emoji2:emoji2-bundled
 WARNING:The option setting 'android\.disableAutomaticComponentCreation=true' is experimental\.
+# > Task :compose:ui:ui-test-junit4:testDebugUnitTest
+System\.logW: A resource was acquired at attached stack trace but never released\. See java\.io\.Closeable for information on avoiding resource leaks\.java\.lang\.Throwable: Explicit termination method 'dispose' not called
\ No newline at end of file
diff --git a/development/referenceDocs/stageReferenceDocs.sh b/development/referenceDocs/stageReferenceDocs.sh
index 0907583..d3c430e 100755
--- a/development/referenceDocs/stageReferenceDocs.sh
+++ b/development/referenceDocs/stageReferenceDocs.sh
@@ -44,12 +44,10 @@
 
 # Remove directories we never publish
 rm en -rf
-rm reference/android -rf
 rm reference/java -rf
 rm reference/org -rf
 rm reference/hierarchy.html
 rm reference/kotlin/org -rf
-rm reference/kotlin/android -rf
 
 # Move package list into the correct location
 mv reference/kotlin/package-list reference/kotlin/androidx/package-list 
diff --git a/development/referenceDocs/stageReferenceDocsWithDackka.sh b/development/referenceDocs/stageReferenceDocsWithDackka.sh
index d3e12d7..1916086 100755
--- a/development/referenceDocs/stageReferenceDocsWithDackka.sh
+++ b/development/referenceDocs/stageReferenceDocsWithDackka.sh
@@ -94,12 +94,10 @@
 
 # Remove directories we never publish
 rm en -rf
-rm reference/android -rf
 rm reference/java -rf
 rm reference/org -rf
 rm reference/hierarchy.html
 rm reference/kotlin/org -rf
-rm reference/kotlin/android -rf
 
 # Move package list into the correct location
 mv reference/kotlin/package-list reference/kotlin/androidx/package-list
diff --git a/docs-public/build.gradle b/docs-public/build.gradle
index 587c7cc..2383267 100644
--- a/docs-public/build.gradle
+++ b/docs-public/build.gradle
@@ -125,6 +125,7 @@
     docs("androidx.hilt:hilt-common:1.0.0-beta01")
     docs("androidx.hilt:hilt-navigation:1.0.0-beta01")
     docs("androidx.hilt:hilt-navigation-compose:1.0.0-alpha03")
+    samples("androidx.hilt:hilt-navigation-compose-samples:1.0.0-alpha04")
     docs("androidx.hilt:hilt-navigation-fragment:1.0.0-beta01")
     docs("androidx.hilt:hilt-work:1.0.0-beta01")
     docs("androidx.interpolator:interpolator:1.0.0")
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
index 5ce276d..45093a9 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentActivity.java
@@ -234,8 +234,8 @@
      */
     @Override
     public void onConfigurationChanged(@NonNull Configuration newConfig) {
-        super.onConfigurationChanged(newConfig);
         mFragments.noteStateNotSaved();
+        super.onConfigurationChanged(newConfig);
         mFragments.dispatchConfigurationChanged(newConfig);
     }
 
@@ -382,8 +382,8 @@
     @Override
     @CallSuper
     protected void onNewIntent(@SuppressLint("UnknownNullness") Intent intent) {
-        super.onNewIntent(intent);
         mFragments.noteStateNotSaved();
+        super.onNewIntent(intent);
     }
 
     /**
@@ -406,9 +406,9 @@
      */
     @Override
     protected void onResume() {
+        mFragments.noteStateNotSaved();
         super.onResume();
         mResumed = true;
-        mFragments.noteStateNotSaved();
         mFragments.execPendingActions();
     }
 
@@ -468,6 +468,7 @@
      */
     @Override
     protected void onStart() {
+        mFragments.noteStateNotSaved();
         super.onStart();
 
         mStopped = false;
@@ -477,7 +478,6 @@
             mFragments.dispatchActivityCreated();
         }
 
-        mFragments.noteStateNotSaved();
         mFragments.execPendingActions();
 
         // NOTE: HC onStart goes here.
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index caf3af8..d44fe56 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -33,7 +33,7 @@
 ksp = "1.5.10-1.0.0-beta01"
 leakcanary = "2.2"
 mockito = "2.25.0"
-skiko = "0.3.5"
+skiko = "0.3.6"
 sqldelight = "1.3.0"
 wire = "3.6.0"
 
diff --git a/navigation/navigation-fragment/api/current.txt b/navigation/navigation-fragment/api/current.txt
index bfb1930..fb744d2 100644
--- a/navigation/navigation-fragment/api/current.txt
+++ b/navigation/navigation-fragment/api/current.txt
@@ -13,7 +13,7 @@
     ctor public AbstractListDetailFragment();
     method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
     method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
-    method @CallSuper public final android.view.View? onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
     method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method public final androidx.navigation.fragment.NavHostFragment requireDetailPaneNavHostFragment();
diff --git a/navigation/navigation-fragment/api/public_plus_experimental_current.txt b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
index bfb1930..fb744d2 100644
--- a/navigation/navigation-fragment/api/public_plus_experimental_current.txt
+++ b/navigation/navigation-fragment/api/public_plus_experimental_current.txt
@@ -13,7 +13,7 @@
     ctor public AbstractListDetailFragment();
     method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
     method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
-    method @CallSuper public final android.view.View? onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
     method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method public final androidx.navigation.fragment.NavHostFragment requireDetailPaneNavHostFragment();
diff --git a/navigation/navigation-fragment/api/restricted_current.txt b/navigation/navigation-fragment/api/restricted_current.txt
index bfb1930..fb744d2 100644
--- a/navigation/navigation-fragment/api/restricted_current.txt
+++ b/navigation/navigation-fragment/api/restricted_current.txt
@@ -13,7 +13,7 @@
     ctor public AbstractListDetailFragment();
     method public androidx.navigation.fragment.NavHostFragment onCreateDetailPaneNavHostFragment();
     method public abstract android.view.View onCreateListPaneView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
-    method @CallSuper public final android.view.View? onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
+    method @CallSuper public final android.view.View onCreateView(android.view.LayoutInflater inflater, android.view.ViewGroup? container, android.os.Bundle? savedInstanceState);
     method public void onListPaneViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method @CallSuper public final void onViewCreated(android.view.View view, android.os.Bundle? savedInstanceState);
     method public final androidx.navigation.fragment.NavHostFragment requireDetailPaneNavHostFragment();
diff --git a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
index e7cb787..d62f9df 100644
--- a/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
+++ b/navigation/navigation-fragment/src/main/java/androidx/navigation/fragment/AbstractListDetailFragment.kt
@@ -22,12 +22,15 @@
 import android.view.LayoutInflater
 import android.view.View
 import android.view.ViewGroup
+import android.view.ViewGroup.LayoutParams.MATCH_PARENT
+import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
 import android.widget.FrameLayout
 import androidx.activity.OnBackPressedCallback
 import androidx.annotation.CallSuper
 import androidx.core.content.res.use
 import androidx.core.view.doOnLayout
 import androidx.fragment.app.Fragment
+import androidx.fragment.app.FragmentContainerView
 import androidx.fragment.app.commit
 import androidx.slidingpanelayout.widget.SlidingPaneLayout
 
@@ -42,7 +45,7 @@
  * be overridden by [AbstractListDetailFragment.onCreateDetailPaneNavHostFragment] and provide
  * custom NavHostFragment.
  */
-abstract class AbstractListDetailFragment : Fragment(R.layout.abstract_list_detail_fragment) {
+abstract class AbstractListDetailFragment : Fragment() {
     private var onBackPressedCallback: OnBackPressedCallback? = null
     private var detailPaneNavHostFragment: NavHostFragment? = null
     private var graphId = 0
@@ -133,18 +136,42 @@
         inflater: LayoutInflater,
         container: ViewGroup?,
         savedInstanceState: Bundle?
-    ): View? {
+    ): View {
         if (savedInstanceState != null) {
             graphId = savedInstanceState.getInt(NavHostFragment.KEY_GRAPH_ID)
         }
-        val slidingPaneLayout = super.onCreateView(inflater, container, savedInstanceState) as
-            SlidingPaneLayout
-        val listContainer = slidingPaneLayout.findViewById<FrameLayout>(R.id.list_container)
+        val slidingPaneLayout = SlidingPaneLayout(inflater.context).apply {
+            id = R.id.sliding_pane_layout
+        }
+
+        // Set up the list container
+        val listContainer = FrameLayout(inflater.context).apply {
+            id = R.id.sliding_pane_list_container
+        }
+        val listLayoutParams = SlidingPaneLayout.LayoutParams(WRAP_CONTENT, MATCH_PARENT)
+        slidingPaneLayout.addView(listContainer, listLayoutParams)
+
+        // Now create and add the list pane itself
         val listPaneView = onCreateListPaneView(inflater, listContainer, savedInstanceState)
         if (listPaneView.parent != listContainer) {
             listContainer.addView(listPaneView)
         }
-        val existingNavHostFragment = childFragmentManager.findFragmentById(R.id.detail_nav_host)
+
+        // Set up the detail container
+        val detailContainer = FragmentContainerView(inflater.context).apply {
+            id = R.id.sliding_pane_detail_container
+        }
+        val detailWidth = inflater.context.resources.getDimensionPixelSize(
+            R.dimen.sliding_pane_detail_pane_width
+        )
+        val detailLayoutParams = SlidingPaneLayout.LayoutParams(detailWidth, MATCH_PARENT).apply {
+            weight = 1F
+        }
+        slidingPaneLayout.addView(detailContainer, detailLayoutParams)
+
+        // Now create the NavHostFragment for the detail container
+        val existingNavHostFragment =
+            childFragmentManager.findFragmentById(R.id.sliding_pane_detail_container)
         detailPaneNavHostFragment = if (existingNavHostFragment != null) {
             existingNavHostFragment as NavHostFragment
         } else {
@@ -152,7 +179,7 @@
                 childFragmentManager
                     .commit {
                         setReorderingAllowed(true)
-                        add(R.id.detail_nav_host, newNavHostFragment)
+                        add(R.id.sliding_pane_detail_container, newNavHostFragment)
                     }
             }
         }
@@ -207,8 +234,7 @@
     @CallSuper
     final override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
         super.onViewCreated(view, savedInstanceState)
-        val listContainer =
-            requireSlidingPaneLayout().findViewById<FrameLayout>(R.id.list_container)
+        val listContainer = view.findViewById<FrameLayout>(R.id.sliding_pane_list_container)
         val listPaneView = listContainer.getChildAt(0)
         onListPaneViewCreated(listPaneView, savedInstanceState)
     }
diff --git a/navigation/navigation-fragment/src/main/res/layout/abstract_list_detail_fragment.xml b/navigation/navigation-fragment/src/main/res/layout/abstract_list_detail_fragment.xml
deleted file mode 100644
index 50df6c0..0000000
--- a/navigation/navigation-fragment/src/main/res/layout/abstract_list_detail_fragment.xml
+++ /dev/null
@@ -1,31 +0,0 @@
-<?xml version="1.0" encoding="utf-8"?><!--
-  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.
-  -->
-
-<androidx.slidingpanelayout.widget.SlidingPaneLayout
-    xmlns:android="http://schemas.android.com/apk/res/android"
-    android:layout_width="match_parent"
-    android:layout_height="match_parent">
-    <FrameLayout
-        android:id="@+id/list_container"
-        android:layout_width="wrap_content"
-        android:layout_height="match_parent"/>
-    <androidx.fragment.app.FragmentContainerView
-        android:id="@+id/detail_nav_host"
-        android:layout_width="300dp"
-        android:layout_height="match_parent"
-        android:layout_weight="1"/>
-
-</androidx.slidingpanelayout.widget.SlidingPaneLayout>
\ No newline at end of file
diff --git a/navigation/navigation-fragment/src/main/res/values/dimens.xml b/navigation/navigation-fragment/src/main/res/values/dimens.xml
new file mode 100644
index 0000000..b1906d9
--- /dev/null
+++ b/navigation/navigation-fragment/src/main/res/values/dimens.xml
@@ -0,0 +1,19 @@
+<?xml version="1.0" encoding="utf-8"?><!--
+  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.
+  -->
+
+<resources>
+    <dimen name="sliding_pane_detail_pane_width">300dp</dimen>
+</resources>
diff --git a/navigation/navigation-fragment/src/main/res/values/ids.xml b/navigation/navigation-fragment/src/main/res/values/ids.xml
index 28a8854..8ab2900 100644
--- a/navigation/navigation-fragment/src/main/res/values/ids.xml
+++ b/navigation/navigation-fragment/src/main/res/values/ids.xml
@@ -16,4 +16,7 @@
   -->
 <resources>
     <item type="id" name="nav_host_fragment_container" />
+    <item type="id" name="sliding_pane_layout" />
+    <item type="id" name="sliding_pane_list_container" />
+    <item type="id" name="sliding_pane_detail_container" />
 </resources>
diff --git a/paging/common/api/current.txt b/paging/common/api/current.txt
index 0da1a47..e006221 100644
--- a/paging/common/api/current.txt
+++ b/paging/common/api/current.txt
@@ -364,6 +364,10 @@
     property public final Throwable throwable;
   }
 
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
   public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
diff --git a/paging/common/api/public_plus_experimental_current.txt b/paging/common/api/public_plus_experimental_current.txt
index 38fc7c5..62e15c2 100644
--- a/paging/common/api/public_plus_experimental_current.txt
+++ b/paging/common/api/public_plus_experimental_current.txt
@@ -368,6 +368,10 @@
     property public final Throwable throwable;
   }
 
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
   public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
diff --git a/paging/common/api/restricted_current.txt b/paging/common/api/restricted_current.txt
index 0da1a47..e006221 100644
--- a/paging/common/api/restricted_current.txt
+++ b/paging/common/api/restricted_current.txt
@@ -364,6 +364,10 @@
     property public final Throwable throwable;
   }
 
+  public static final class PagingSource.LoadResult.Invalid<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
+    ctor public PagingSource.LoadResult.Invalid();
+  }
+
   public static final class PagingSource.LoadResult.Page<Key, Value> extends androidx.paging.PagingSource.LoadResult<Key,Value> {
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsBefore, optional @IntRange(from=COUNT_UNDEFINED.toLong()) int itemsAfter);
     ctor public PagingSource.LoadResult.Page(java.util.List<? extends Value> data, Key? prevKey, Key? nextKey);
diff --git a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
index 6f942fc..47cce21 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PageFetcherSnapshot.kt
@@ -329,6 +329,7 @@
                     pageEventCh.send(LoadStateUpdate(REFRESH, false, loadState))
                 }
             }
+            is LoadResult.Invalid -> onInvalidLoad()
         }
     }
 
@@ -443,6 +444,10 @@
                     }
                     return
                 }
+                is LoadResult.Invalid -> {
+                    onInvalidLoad()
+                    return
+                }
             }
 
             val dropType = when (loadType) {
@@ -529,6 +534,12 @@
             pages.last().nextKey
         }
     }
+
+    // the handler for LoadResult.Invalid for both doInitialLoad and doLoad
+    private fun onInvalidLoad() {
+        close()
+        pagingSource.invalidate()
+    }
 }
 
 /**
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
index 149958d..35ca80a 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagedList.kt
@@ -185,6 +185,15 @@
                         when (initialResult) {
                             is PagingSource.LoadResult.Page -> initialResult
                             is PagingSource.LoadResult.Error -> throw initialResult.throwable
+                            is PagingSource.LoadResult.Invalid ->
+                                throw IllegalStateException(
+                                    "Failed to create PagedList. The provided PagingSource " +
+                                        "returned LoadResult.Invalid, but a LoadResult.Page was " +
+                                        "expected. To use a PagingSource which supports " +
+                                        "invalidation, use a PagedList builder that accepts a " +
+                                        "factory method for PagingSource or DataSource.Factory, " +
+                                        "such as LivePagedList."
+                                )
                         }
                     }
                 }
diff --git a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
index 05aa243..f5c2707 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PagingSource.kt
@@ -204,6 +204,24 @@
         ) : LoadResult<Key, Value>()
 
         /**
+         * Invalid result object for [PagingSource.load]
+         *
+         * This return type can be used to terminate future load requests on this [PagingSource]
+         * when the [PagingSource] is not longer valid due to changes in the underlying dataset.
+         *
+         * For example, if the underlying database gets written into but the [PagingSource] does
+         * not invalidate in time, it may return inconsistent results if its implementation depends
+         * on the immutability of the backing dataset it loads from (e.g., LIMIT OFFSET style db
+         * implementations). In this scenario, it is recommended to check for invalidation after
+         * loading and to return LoadResult.Invalid, which causes Paging to discard any
+         * pending or future load requests to this PagingSource and invalidate it.
+         *
+         * Returning [Invalid] will trigger Paging to [invalidate] this [PagingSource] and
+         * terminate any future attempts to [load] from this [PagingSource]
+         */
+        public class Invalid<Key : Any, Value : Any> : LoadResult<Key, Value>()
+
+        /**
          * Success result object for [PagingSource.load].
          *
          * @sample androidx.paging.samples.pageKeyedPage
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
index e1562f4..9f4fdf3 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherSnapshotTest.kt
@@ -29,6 +29,7 @@
 import androidx.paging.PageEvent.Insert.Companion.Refresh
 import androidx.paging.PageEvent.LoadStateUpdate
 import androidx.paging.PagingSource.LoadResult.Page
+import androidx.paging.PagingSource.LoadResult
 import androidx.paging.RemoteMediatorMock.LoadEvent
 import androidx.paging.TestPagingSource.Companion.LOAD_ERROR
 import com.google.common.truth.Truth.assertThat
@@ -3437,6 +3438,138 @@
         assertFalse { initialHint.shouldPrioritizeOver(accessHint, APPEND) }
     }
 
+    @Test
+    fun close_cancelsCollectionFromLoadResultInvalid() = testScope.runBlockingTest {
+        val pagingSource = object : PagingSource<Int, Int>() {
+            override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+                return LoadResult.Invalid()
+            }
+
+            override fun getRefreshKey(state: PagingState<Int, Int>): Int? {
+                fail("should not reach here")
+            }
+        }
+        val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+        collectSnapshotData(pager) { _, job ->
+
+            // Start initial load but this load should return LoadResult.Invalid
+            // wait some time for the invalid result handler to close the page event flow
+            advanceTimeBy(1000)
+
+            assertTrue { !job.isActive }
+        }
+    }
+
+    @Test
+    fun refresh_cancelsCollectionFromLoadResultInvalid() = testScope.runBlockingTest {
+        val pagingSource = TestPagingSource()
+        pagingSource.nextLoadResult = LoadResult.Invalid()
+
+        val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+        collectSnapshotData(pager) { state, job ->
+
+            // Start initial load but this load should return LoadResult.Invalid
+            // Wait some time for the result handler to close the page event flow
+            advanceUntilIdle()
+
+            // The flow's last page event should be the original Loading event before it
+            // was closed by the invalid result handler
+            assertThat(state.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(
+                    loadType = REFRESH,
+                    fromMediator = false,
+                    loadState = Loading
+                ),
+            )
+            // make sure no more new events are sent to UI
+            assertThat(state.newEvents()).isEmpty()
+            assertTrue(pagingSource.invalid)
+            assertTrue { !job.isActive }
+        }
+    }
+
+    @Test
+    fun append_cancelsCollectionFromLoadResultInvalid() = testScope.runBlockingTest {
+        val pagingSource = TestPagingSource()
+        val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+        collectSnapshotData(pager) { state, job ->
+
+            advanceUntilIdle()
+
+            assertThat(state.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(50..51)
+            )
+            // append a page
+            pager.accessHint(
+                ViewportHint.Access(
+                    pageOffset = 0,
+                    indexInPage = 1,
+                    presentedItemsBefore = 1,
+                    presentedItemsAfter = 0,
+                    originalPageOffsetFirst = 0,
+                    originalPageOffsetLast = 0
+                )
+            )
+            // now return LoadResult.Invalid
+            pagingSource.nextLoadResult = LoadResult.Invalid()
+
+            advanceUntilIdle()
+
+            // Only a LoadStateUpdate for Append with loading status should be sent and it should
+            // not complete
+            assertThat(state.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(APPEND, false, Loading),
+            )
+            assertTrue(pagingSource.invalid)
+            assertThat(state.newEvents()).isEmpty()
+            assertThat(!job.isActive)
+        }
+    }
+
+    @Test
+    fun prepend_cancelsCollectionFromLoadResultInvalid() = testScope.runBlockingTest {
+        val pagingSource = TestPagingSource()
+        val pager = PageFetcherSnapshot(50, pagingSource, config, retryFlow = retryBus.flow)
+
+        collectSnapshotData(pager) { state, job ->
+
+            advanceUntilIdle()
+
+            assertThat(state.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(50..51)
+            )
+            // now prepend
+            pager.accessHint(
+                ViewportHint.Access(
+                    pageOffset = 0,
+                    indexInPage = -1,
+                    presentedItemsBefore = 0,
+                    presentedItemsAfter = 1,
+                    originalPageOffsetFirst = 0,
+                    originalPageOffsetLast = 0
+                )
+            )
+            // now return LoadResult.Invalid.
+            pagingSource.nextLoadResult = LoadResult.Invalid()
+
+            advanceUntilIdle()
+
+            // Only a LoadStateUpdate for Prepend with loading status should be sent and it should
+            // not complete
+            assertThat(state.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(PREPEND, false, Loading),
+            )
+            assertTrue(pagingSource.invalid)
+            assertThat(state.newEvents()).isEmpty()
+            assertThat(!job.isActive)
+        }
+    }
+
     internal class CollectedPageEvents<T : Any>(val pageEvents: ArrayList<PageEvent<T>>) {
         var lastIndex = 0
         fun newEvents(): List<PageEvent<T>> = when {
diff --git a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
index b095750..97258f1 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PageFetcherTest.kt
@@ -19,7 +19,9 @@
 import androidx.paging.LoadState.Loading
 import androidx.paging.LoadType.APPEND
 import androidx.paging.LoadType.REFRESH
+import androidx.paging.LoadType.PREPEND
 import androidx.paging.PageEvent.LoadStateUpdate
+import androidx.paging.PagingSource.LoadResult
 import androidx.paging.RemoteMediator.InitializeAction.LAUNCH_INITIAL_REFRESH
 import androidx.paging.RemoteMediator.InitializeAction.SKIP_INITIAL_REFRESH
 import com.google.common.truth.Truth.assertThat
@@ -44,6 +46,7 @@
 import kotlin.test.assertFailsWith
 import kotlin.test.assertNotEquals
 import kotlin.test.assertTrue
+import java.util.ArrayList
 
 @OptIn(ExperimentalCoroutinesApi::class)
 @RunWith(JUnit4::class)
@@ -195,6 +198,153 @@
     }
 
     @Test
+    fun refresh_invalidatePropagatesThroughLoadResultInvalid() =
+        testScope.runBlockingTest {
+            val pagingSources = mutableListOf<TestPagingSource>()
+            val pageFetcher = PageFetcher(
+                pagingSourceFactory = {
+                    TestPagingSource().also {
+                        // make this initial load return LoadResult.Invalid to see if new paging
+                        // source is generated
+                        if (pagingSources.size == 0) it.nextLoadResult = LoadResult.Invalid()
+                        pagingSources.add(it)
+                    }
+                },
+                initialKey = 50,
+                config = config,
+            )
+
+            val fetcherState = collectFetcherState(pageFetcher)
+            advanceUntilIdle()
+
+            // should have two PagingData returned, one for each paging source
+            assertThat(fetcherState.pagingDataList.size).isEqualTo(2)
+
+            // First PagingData only returns a loading state because invalidation prevents load
+            // completion
+            assertTrue(pagingSources[0].invalid)
+            assertThat(fetcherState.pageEventLists[0]).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading)
+            )
+            // previous load() returning LoadResult.Invalid should trigger a new paging source
+            // retrying with the same load params, this should return a refresh starting
+            // from key = 50
+            assertTrue(!pagingSources[1].invalid)
+            assertThat(fetcherState.pageEventLists[1]).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(50..51)
+            )
+
+            assertThat(pagingSources[0]).isNotEqualTo(pagingSources[1])
+            fetcherState.job.cancel()
+        }
+
+    @Test
+    fun append_invalidatePropagatesThroughLoadResultInvalid() =
+        testScope.runBlockingTest {
+            val pagingSources = mutableListOf<TestPagingSource>()
+            val pageFetcher = PageFetcher(
+                pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
+                initialKey = 50,
+                config = config,
+            )
+            val fetcherState = collectFetcherState(pageFetcher)
+            advanceUntilIdle()
+
+            assertThat(fetcherState.pageEventLists.size).isEqualTo(1)
+            assertThat(fetcherState.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(50..51),
+            )
+
+            // append a page
+            fetcherState.pagingDataList[0].receiver.accessHint(
+                ViewportHint.Access(
+                    pageOffset = 0,
+                    indexInPage = 1,
+                    presentedItemsBefore = 1,
+                    presentedItemsAfter = 0,
+                    originalPageOffsetFirst = 0,
+                    originalPageOffsetLast = 0
+                )
+            )
+            // now return LoadResult.Invalid
+            pagingSources[0].nextLoadResult = LoadResult.Invalid()
+
+            advanceUntilIdle()
+
+            // make sure the append load never completes
+            assertThat(fetcherState.pageEventLists[0].last()).isEqualTo(
+                LoadStateUpdate<Int>(APPEND, false, Loading)
+            )
+
+            // the invalid result handler should exit the append load loop gracefully and allow
+            // fetcher to generate a new paging source
+            assertThat(pagingSources.size).isEqualTo(2)
+            assertTrue(pagingSources[0].invalid)
+
+            // second generation should load refresh with cached append load params
+            assertThat(fetcherState.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(51..52)
+            )
+
+            fetcherState.job.cancel()
+        }
+
+    @Test
+    fun prepend_invalidatePropagatesThroughLoadResultInvalid() =
+        testScope.runBlockingTest {
+            val pagingSources = mutableListOf<TestPagingSource>()
+            val pageFetcher = PageFetcher(
+                pagingSourceFactory = { TestPagingSource().also { pagingSources.add(it) } },
+                initialKey = 50,
+                config = config,
+            )
+            val fetcherState = collectFetcherState(pageFetcher)
+            advanceUntilIdle()
+
+            assertThat(fetcherState.pageEventLists.size).isEqualTo(1)
+            assertThat(fetcherState.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(50..51),
+            )
+            // prepend a page
+            fetcherState.pagingDataList[0].receiver.accessHint(
+                ViewportHint.Access(
+                    pageOffset = 0,
+                    indexInPage = -1,
+                    presentedItemsBefore = 0,
+                    presentedItemsAfter = 1,
+                    originalPageOffsetFirst = 0,
+                    originalPageOffsetLast = 0
+                )
+            )
+            // now return LoadResult.Invalid
+            pagingSources[0].nextLoadResult = LoadResult.Invalid()
+
+            advanceUntilIdle()
+
+            // make sure the prepend load never completes
+            assertThat(fetcherState.pageEventLists[0].last()).isEqualTo(
+                LoadStateUpdate<Int>(PREPEND, false, Loading)
+            )
+
+            // the invalid result should exit the prepend load loop gracefully and allow fetcher to
+            // generate a new paging source
+            assertThat(pagingSources.size).isEqualTo(2)
+            assertTrue(pagingSources[0].invalid)
+
+            // second generation should load refresh with cached prepend load params
+            assertThat(fetcherState.newEvents()).containsExactly(
+                LoadStateUpdate<Int>(REFRESH, false, Loading),
+                createRefresh(49..50)
+            )
+
+            fetcherState.job.cancel()
+        }
+
+    @Test
     fun refresh_closesCollection() = testScope.runBlockingTest {
         val pageFetcher = PageFetcher(pagingSourceFactory, 50, config)
 
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
index 7e81475..3d7479b 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagedListTest.kt
@@ -20,6 +20,7 @@
 import androidx.paging.LoadType.REFRESH
 import androidx.testutils.TestDispatcher
 import androidx.testutils.TestExecutor
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.Dispatchers
 import kotlinx.coroutines.runBlocking
@@ -32,6 +33,7 @@
 import kotlin.coroutines.EmptyCoroutineContext
 import kotlin.test.assertFailsWith
 import kotlin.test.assertTrue
+import kotlin.test.fail
 
 @RunWith(JUnit4::class)
 class PagedListTest {
@@ -132,6 +134,42 @@
     }
 
     @Test
+    fun createNoInitialPageInvalidResult() {
+        runBlocking {
+            val pagingSource = object : PagingSource<Int, String>() {
+                override suspend fun load(params: LoadParams<Int>): LoadResult<Int, String> {
+                    return LoadResult.Invalid()
+                }
+
+                override fun getRefreshKey(state: PagingState<Int, String>): Int? {
+                    fail("should not reach here")
+                }
+            }
+
+            val expectedException = assertFailsWith<IllegalStateException> {
+                @Suppress("DEPRECATION")
+                PagedList.create(
+                    pagingSource,
+                    initialPage = null,
+                    testCoroutineScope,
+                    Dispatchers.Default,
+                    Dispatchers.IO,
+                    boundaryCallback = null,
+                    Config(10),
+                    key = 0
+                )
+            }
+            assertThat(expectedException.message).isEqualTo(
+                "Failed to create PagedList. The provided PagingSource returned " +
+                    "LoadResult.Invalid, but a LoadResult.Page was expected. To use a " +
+                    "PagingSource which supports invalidation, use a PagedList builder that " +
+                    "accepts a factory method for PagingSource or DataSource.Factory, such as " +
+                    "LivePagedList."
+            )
+        }
+    }
+
+    @Test
     fun defaults() = runBlocking {
         val initialPage = pagingSource.load(
             PagingSource.LoadParams.Refresh(
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
index fa7152d..aaa1ee0 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingDataDifferTest.kt
@@ -26,11 +26,13 @@
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.CoroutineDispatcher
 import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.async
 import kotlinx.coroutines.channels.Channel
 import kotlinx.coroutines.delay
 import kotlinx.coroutines.flow.collect
 import kotlinx.coroutines.flow.collectLatest
 import kotlinx.coroutines.flow.consumeAsFlow
+import kotlinx.coroutines.flow.first
 import kotlinx.coroutines.flow.flow
 import kotlinx.coroutines.launch
 import kotlinx.coroutines.suspendCancellableCoroutine
@@ -42,6 +44,7 @@
 import org.junit.runners.JUnit4
 import kotlin.coroutines.ContinuationInterceptor
 import kotlin.test.assertEquals
+import kotlin.test.assertFailsWith
 import kotlin.test.assertFalse
 import kotlin.test.assertNull
 import kotlin.test.assertTrue
@@ -758,6 +761,57 @@
             job.cancel()
         }
     }
+
+    @Test
+    fun uncaughtException() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        val pager = Pager(
+            PagingConfig(1),
+        ) {
+            object : PagingSource<Int, Int>() {
+                override suspend fun load(params: LoadParams<Int>): LoadResult<Int, Int> {
+                    throw IllegalStateException()
+                }
+
+                override fun getRefreshKey(state: PagingState<Int, Int>): Int? = null
+            }
+        }
+
+        val pagingData = pager.flow.first()
+        val deferred = testScope.async {
+            differ.collectFrom(pagingData)
+        }
+
+        advanceUntilIdle()
+        assertFailsWith<IllegalStateException> { deferred.await() }
+    }
+
+    @Test
+    fun handledLoadResultInvalid() = testScope.runBlockingTest {
+        val differ = SimpleDiffer(dummyDifferCallback)
+        var generation = 0
+        val pager = Pager(
+            PagingConfig(1),
+        ) {
+            TestPagingSource().also {
+                if (generation == 0) {
+                    it.nextLoadResult = PagingSource.LoadResult.Invalid()
+                }
+                generation++
+            }
+        }
+
+        val pagingData = pager.flow.first()
+        val deferred = testScope.async {
+            // only returns if flow is closed, or work canclled, or exception thrown
+            // in this case it should cancel due LoadResult.Invalid causing collectFrom to return
+            differ.collectFrom(pagingData)
+        }
+
+        advanceUntilIdle()
+        // this will return only if differ.collectFrom returns
+        deferred.await()
+    }
 }
 
 private fun infinitelySuspendingPagingData(receiver: UiReceiver = dummyReceiver) = PagingData(
diff --git a/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt b/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
index 6f90f6b..c3fc14b 100644
--- a/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
+++ b/paging/common/src/test/kotlin/androidx/paging/PagingSourceTest.kt
@@ -58,12 +58,15 @@
             assertEquals(ITEMS_BY_NAME_ID.subList(45, 55), result.data)
             assertEquals(45, result.itemsAfter)
 
+            val errorParams = LoadParams.Refresh(key, 10, false)
             // Verify error is propagated correctly.
             pagingSource.enqueueError()
-            val errorParams = LoadParams.Refresh(key, 10, false)
             assertFailsWith<CustomException> {
                 pagingSource.load(errorParams)
             }
+            // Verify LoadResult.Invalid is returned
+            pagingSource.invalidateLoad()
+            assertTrue(pagingSource.load(errorParams) is LoadResult.Invalid)
         }
     }
 
@@ -197,12 +200,15 @@
 
             assertEquals(ITEMS_BY_NAME_ID.subList(0, 5), observed)
 
+            val errorParams = LoadParams.Prepend(key, 5, false)
             // Verify error is propagated correctly.
             dataSource.enqueueError()
             assertFailsWith<CustomException> {
-                val errorParams = LoadParams.Prepend(key, 5, false)
                 dataSource.load(errorParams)
             }
+            // Verify LoadResult.Invalid is returned
+            dataSource.invalidateLoad()
+            assertTrue(dataSource.load(errorParams) is LoadResult.Invalid)
         }
     }
 
@@ -217,12 +223,15 @@
 
             assertEquals(ITEMS_BY_NAME_ID.subList(6, 11), observed)
 
+            val errorParams = LoadParams.Append(key, 5, false)
             // Verify error is propagated correctly.
             dataSource.enqueueError()
             assertFailsWith<CustomException> {
-                val errorParams = LoadParams.Append(key, 5, false)
                 dataSource.load(errorParams)
             }
+            // Verify LoadResult.Invalid is returned
+            dataSource.invalidateLoad()
+            assertTrue(dataSource.load(errorParams) is LoadResult.Invalid)
         }
     }
 
@@ -256,6 +265,8 @@
 
         private var error = false
 
+        private var invalidLoadResult = false
+
         override fun getRefreshKey(state: PagingState<Key, Item>): Key? {
             return state.anchorPosition
                 ?.let { anchorPosition -> state.closestItemToPosition(anchorPosition) }
@@ -274,6 +285,9 @@
             if (error) {
                 error = false
                 throw EXCEPTION
+            } else if (invalidLoadResult) {
+                invalidLoadResult = false
+                return LoadResult.Invalid()
             }
 
             val key = params.key ?: Key("", Int.MAX_VALUE)
@@ -292,6 +306,9 @@
             if (error) {
                 error = false
                 throw EXCEPTION
+            } else if (invalidLoadResult) {
+                invalidLoadResult = false
+                return LoadResult.Invalid()
             }
 
             val start = findFirstIndexAfter(params.key!!)
@@ -304,6 +321,9 @@
             if (error) {
                 error = false
                 throw EXCEPTION
+            } else if (invalidLoadResult) {
+                invalidLoadResult = false
+                return LoadResult.Invalid()
             }
 
             val firstIndexBefore = findFirstIndexBefore(params.key!!)
@@ -327,6 +347,10 @@
         fun enqueueError() {
             error = true
         }
+
+        fun invalidateLoad() {
+            invalidLoadResult = true
+        }
     }
 
     class CustomException : Exception()
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
index 761a3d8..fa3204e 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RemoteMediator.kt
@@ -30,8 +30,17 @@
 @OptIn(ExperimentalPagingApi::class)
 internal class V3RemoteMediator(
     private val database: SampleDatabase,
-    private val networkSource: NetworkCustomerPagingSource
+    private val networkSourceFactory: () -> NetworkCustomerPagingSource
 ) : RemoteMediator<Int, Customer>() {
+
+    private var networkSource: NetworkCustomerPagingSource
+    private val callBack = { newNetworkSource() }
+
+    init {
+        networkSource = networkSourceFactory.invoke()
+        networkSource.registerInvalidatedCallback(callBack)
+    }
+
     override suspend fun load(
         loadType: LoadType,
         state: PagingState<Int, Customer>
@@ -73,6 +82,18 @@
             is PagingSource.LoadResult.Error -> {
                 MediatorResult.Error(result.throwable)
             }
+            is PagingSource.LoadResult.Invalid -> {
+                networkSource.invalidate()
+                load(loadType, state)
+            }
         }
     }
+
+    private fun newNetworkSource() {
+        val newNetworkSource = networkSourceFactory.invoke()
+        newNetworkSource.registerInvalidatedCallback { callBack }
+        networkSource.unregisterInvalidatedCallback { callBack }
+
+        networkSource = newNetworkSource
+    }
 }
\ No newline at end of file
diff --git a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
index 8f927e0..9a3718d 100644
--- a/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
+++ b/paging/integration-tests/testapp/src/main/java/androidx/paging/integration/testapp/v3room/V3RoomViewModel.kt
@@ -65,7 +65,7 @@
         PagingConfig(10),
         remoteMediator = V3RemoteMediator(
             database,
-            NetworkCustomerPagingSource.FACTORY()
+            NetworkCustomerPagingSource.FACTORY
         )
     ) {
         database.customerDao.loadPagedAgeOrderPagingSource()
diff --git a/paging/paging-compose/api/current.txt b/paging/paging-compose/api/current.txt
index 5859668..99d803e 100644
--- a/paging/paging-compose/api/current.txt
+++ b/paging/paging-compose/api/current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/api/public_plus_experimental_current.txt b/paging/paging-compose/api/public_plus_experimental_current.txt
index 5859668..99d803e 100644
--- a/paging/paging-compose/api/public_plus_experimental_current.txt
+++ b/paging/paging-compose/api/public_plus_experimental_current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/api/restricted_current.txt b/paging/paging-compose/api/restricted_current.txt
index 5859668..99d803e 100644
--- a/paging/paging-compose/api/restricted_current.txt
+++ b/paging/paging-compose/api/restricted_current.txt
@@ -16,8 +16,8 @@
 
   public final class LazyPagingItemsKt {
     method @androidx.compose.runtime.Composable public static <T> androidx.paging.compose.LazyPagingItems<T> collectAsLazyPagingItems(kotlinx.coroutines.flow.Flow<androidx.paging.PagingData<T>>);
-    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
-    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> lazyPagingItems, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
+    method public static <T> void items(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function1<? super T,?>? key, kotlin.jvm.functions.Function2<? super androidx.compose.foundation.lazy.LazyItemScope,? super T,kotlin.Unit> itemContent);
+    method public static <T> void itemsIndexed(androidx.compose.foundation.lazy.LazyListScope, androidx.paging.compose.LazyPagingItems<T> items, optional kotlin.jvm.functions.Function2<? super java.lang.Integer,? super T,?>? key, kotlin.jvm.functions.Function3<? super androidx.compose.foundation.lazy.LazyItemScope,? super java.lang.Integer,? super T,kotlin.Unit> itemContent);
   }
 
 }
diff --git a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
index 780a5ac..27ebfb1 100644
--- a/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
+++ b/paging/paging-compose/integration-tests/paging-demos/src/main/java/androidx/paging/compose/demos/room/PagingRoomSample.kt
@@ -16,13 +16,19 @@
 
 package androidx.paging.compose.demos.room
 
+import androidx.compose.foundation.clickable
 import androidx.compose.foundation.layout.Column
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.material.Button
 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.rememberCoroutineScope
+import androidx.compose.runtime.saveable.rememberSaveable
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.LocalContext
 import androidx.compose.ui.unit.sp
 import androidx.paging.Pager
@@ -92,9 +98,10 @@
                 scope.launch(Dispatchers.IO) {
                     val randomUser = dao.getRandomUser()
                     if (randomUser != null) {
+                        val newName = Names[Random.nextInt(Names.size)]
                         val updatedUser = User(
                             randomUser.id,
-                            randomUser.name + " updated"
+                            newName
                         )
                         dao.update(updatedUser)
                     }
@@ -106,8 +113,16 @@
 
         val lazyPagingItems = pager.flow.collectAsLazyPagingItems()
         LazyColumn {
-            itemsIndexed(lazyPagingItems) { index, user ->
-                Text("$index " + user?.name, fontSize = 50.sp)
+            itemsIndexed(
+                items = lazyPagingItems,
+                key = { _, user -> user.id }
+            ) { index, user ->
+                var counter by rememberSaveable { mutableStateOf(0) }
+                Text(
+                    text = "counter=$counter index=$index ${user?.name} ${user?.id}",
+                    fontSize = 50.sp,
+                    modifier = Modifier.clickable { counter++ }
+                )
             }
         }
     }
diff --git a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
index 17d2e46..18af614 100644
--- a/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
+++ b/paging/paging-compose/src/androidTest/java/androidx/paging/compose/LazyPagingItemsTest.kt
@@ -21,13 +21,16 @@
 import androidx.compose.foundation.layout.width
 import androidx.compose.foundation.lazy.LazyColumn
 import androidx.compose.foundation.lazy.LazyRow
+import androidx.compose.foundation.text.BasicText
 import androidx.compose.runtime.getValue
+import androidx.compose.runtime.remember
 import androidx.compose.ui.Modifier
 import androidx.compose.ui.platform.testTag
 import androidx.compose.ui.test.assertIsDisplayed
 import androidx.compose.ui.test.assertTopPositionInRootIsEqualTo
 import androidx.compose.ui.test.junit4.createComposeRule
 import androidx.compose.ui.test.onNodeWithTag
+import androidx.compose.ui.test.onNodeWithText
 import androidx.compose.ui.unit.dp
 import androidx.paging.Pager
 import androidx.paging.PagingConfig
@@ -497,4 +500,69 @@
         rule.onNodeWithTag("1")
             .assertDoesNotExist()
     }
+
+    @Test
+    fun stateIsMovedWithItemWithCustomKey_items() {
+        val items = mutableListOf(1)
+        val pager = createPager {
+            TestPagingSource(items = items, loadDelay = 0)
+        }
+
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        var counter = 0
+        rule.setContent {
+            lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+            LazyColumn {
+                items(lazyPagingItems, key = { it }) {
+                    BasicText(
+                        "Item=$it. counter=${remember { counter++ }}"
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            items.clear()
+            items.addAll(listOf(0, 1))
+            lazyPagingItems.refresh()
+        }
+
+        rule.onNodeWithText("Item=0. counter=1")
+            .assertExists()
+
+        rule.onNodeWithText("Item=1. counter=0")
+            .assertExists()
+    }
+
+    @Test
+    fun stateIsMovedWithItemWithCustomKey_itemsIndexed() {
+        val items = mutableListOf(1)
+        val pager = createPager {
+            TestPagingSource(items = items, loadDelay = 0)
+        }
+
+        lateinit var lazyPagingItems: LazyPagingItems<Int>
+        rule.setContent {
+            lazyPagingItems = pager.flow.collectAsLazyPagingItems()
+            LazyColumn {
+                itemsIndexed(lazyPagingItems, key = { _, item -> item }) { index, item ->
+                    BasicText(
+                        "Item=$item. index=$index. remembered index=${remember { index }}"
+                    )
+                }
+            }
+        }
+
+        rule.runOnIdle {
+            items.clear()
+            items.addAll(listOf(0, 1))
+            lazyPagingItems.refresh()
+        }
+
+        rule.onNodeWithText("Item=0. index=0. remembered index=0")
+            .assertExists()
+
+        rule.onNodeWithText("Item=1. index=1. remembered index=0")
+            .assertExists()
+    }
 }
diff --git a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
index b53790f..ae13eab 100644
--- a/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
+++ b/paging/paging-compose/src/main/java/androidx/paging/compose/LazyPagingItems.kt
@@ -17,6 +17,8 @@
 package androidx.paging.compose
 
 import android.annotation.SuppressLint
+import android.os.Parcel
+import android.os.Parcelable
 import androidx.compose.foundation.lazy.LazyItemScope
 import androidx.compose.foundation.lazy.LazyListScope
 import androidx.compose.runtime.Composable
@@ -253,17 +255,34 @@
  *
  * @sample androidx.paging.compose.samples.ItemsDemo
  *
- * @param lazyPagingItems the items received from a [Flow] of [PagingData].
+ * @param items the items received from a [Flow] of [PagingData].
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item. In case the item is `null`, the
  * [itemContent] method should handle the logic of displaying a placeholder instead of the main
  * content displayed by an item which is not `null`.
  */
 public fun <T : Any> LazyListScope.items(
-    lazyPagingItems: LazyPagingItems<T>,
+    items: LazyPagingItems<T>,
+    key: ((item: T) -> Any)? = null,
     itemContent: @Composable LazyItemScope.(value: T?) -> Unit
 ) {
-    items(lazyPagingItems.itemCount) { index ->
-        itemContent(lazyPagingItems[index])
+    items(
+        count = items.itemCount,
+        key = if (key == null) null else { index ->
+            val item = items.peek(index)
+            if (item == null) {
+                PagingPlaceholderKey(index)
+            } else {
+                key(item)
+            }
+        }
+    ) { index ->
+        itemContent(items[index])
     }
 }
 
@@ -275,16 +294,56 @@
  *
  * @sample androidx.paging.compose.samples.ItemsIndexedDemo
  *
- * @param lazyPagingItems the items received from a [Flow] of [PagingData].
+ * @param items the items received from a [Flow] of [PagingData].
+ * @param key a factory of stable and unique keys representing the item. Using the same key
+ * for multiple items in the list is not allowed. Type of the key should be saveable
+ * via Bundle on Android. If null is passed the position in the list will represent the key.
+ * When you specify the key the scroll position will be maintained based on the key, which
+ * means if you add/remove items before the current visible item the item with the given key
+ * will be kept as the first visible one.
  * @param itemContent the content displayed by a single item. In case the item is `null`, the
  * [itemContent] method should handle the logic of displaying a placeholder instead of the main
  * content displayed by an item which is not `null`.
  */
 public fun <T : Any> LazyListScope.itemsIndexed(
-    lazyPagingItems: LazyPagingItems<T>,
+    items: LazyPagingItems<T>,
+    key: ((index: Int, item: T) -> Any)? = null,
     itemContent: @Composable LazyItemScope.(index: Int, value: T?) -> Unit
 ) {
-    items(lazyPagingItems.itemCount) { index ->
-        itemContent(index, lazyPagingItems[index])
+    items(
+        count = items.itemCount,
+        key = if (key == null) null else { index ->
+            val item = items.peek(index)
+            if (item == null) {
+                PagingPlaceholderKey(index)
+            } else {
+                key(index, item)
+            }
+        }
+    ) { index ->
+        itemContent(index, items[index])
+    }
+}
+
+@SuppressLint("BanParcelableUsage")
+private data class PagingPlaceholderKey(private val index: Int) : Parcelable {
+    override fun writeToParcel(parcel: Parcel, flags: Int) {
+        parcel.writeInt(index)
+    }
+
+    override fun describeContents(): Int {
+        return 0
+    }
+
+    companion object {
+        @Suppress("unused")
+        @JvmField
+        val CREATOR: Parcelable.Creator<PagingPlaceholderKey> =
+            object : Parcelable.Creator<PagingPlaceholderKey> {
+                override fun createFromParcel(parcel: Parcel) =
+                    PagingPlaceholderKey(parcel.readInt())
+
+                override fun newArray(size: Int) = arrayOfNulls<PagingPlaceholderKey?>(size)
+            }
     }
 }
diff --git a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/Encoding.java b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/Encoding.java
index 84ff365..c2d5a35 100644
--- a/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/Encoding.java
+++ b/profileinstaller/profileinstaller/src/main/java/androidx/profileinstaller/Encoding.java
@@ -118,41 +118,49 @@
     ) throws IOException {
         // Read the expected compressed data size.
         Inflater inf = new Inflater();
-        byte[] result = new byte[uncompressedDataSize];
-        int totalBytesRead = 0;
-        int totalBytesInflated = 0;
-        byte[] input = new byte[2048]; // 2KB read window size;
-        while (!inf.finished() && !inf.needsDictionary() && totalBytesRead < compressedDataSize) {
-            int bytesRead = is.read(input);
-            if (bytesRead < 0) {
+        try {
+            byte[] result = new byte[uncompressedDataSize];
+            int totalBytesRead = 0;
+            int totalBytesInflated = 0;
+            byte[] input = new byte[2048]; // 2KB read window size;
+            while (
+                !inf.finished() &&
+                !inf.needsDictionary() &&
+                totalBytesRead < compressedDataSize
+            ) {
+                int bytesRead = is.read(input);
+                if (bytesRead < 0) {
+                    throw error(
+                            "Invalid zip data. Stream ended after $totalBytesRead bytes. " +
+                                    "Expected " + compressedDataSize + " bytes"
+                    );
+                }
+                inf.setInput(input, 0, bytesRead);
+                try {
+                    totalBytesInflated += inf.inflate(
+                            result,
+                            totalBytesInflated,
+                            uncompressedDataSize - totalBytesInflated
+                    );
+                } catch (DataFormatException e) {
+                    throw error(e.getMessage());
+                }
+                totalBytesRead += bytesRead;
+            }
+            if (totalBytesRead != compressedDataSize) {
                 throw error(
-                        "Invalid zip data. Stream ended after $totalBytesRead bytes. " +
-                                "Expected " + compressedDataSize + " bytes"
+                        "Didn't read enough bytes during decompression." +
+                                " expected=" + compressedDataSize +
+                                " actual=" + totalBytesRead
                 );
             }
-            inf.setInput(input, 0, bytesRead);
-            try {
-                totalBytesInflated += inf.inflate(
-                        result,
-                        totalBytesInflated,
-                        uncompressedDataSize - totalBytesInflated
-                );
-            } catch (DataFormatException e) {
-                throw error(e.getMessage());
+            if (!inf.finished()) {
+                throw error("Inflater did not finish");
             }
-            totalBytesRead += bytesRead;
+            return result;
+        } finally {
+            inf.end();
         }
-        if (totalBytesRead != compressedDataSize) {
-            throw error(
-                    "Didn't read enough bytes during decompression." +
-                            " expected=" + compressedDataSize +
-                            " actual=" + totalBytesRead
-            );
-        }
-        if (!inf.finished()) {
-            throw error("Inflater did not finish");
-        }
-        return result;
     }
 
     static void writeAll(@NonNull InputStream is, @NonNull OutputStream os) throws IOException {
diff --git a/room/room-compiler-processing-testing/build.gradle b/room/room-compiler-processing-testing/build.gradle
index 6494d0c..0e8ddae 100644
--- a/room/room-compiler-processing-testing/build.gradle
+++ b/room/room-compiler-processing-testing/build.gradle
@@ -24,7 +24,6 @@
 }
 
 dependencies {
-    implementation("androidx.annotation:annotation:1.1.0")
     api(project(":room:room-compiler-processing"))
     implementation(libs.kotlinStdlib)
     implementation(libs.kspApi)
diff --git a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
index 95ede95..8613a86 100644
--- a/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
+++ b/room/room-compiler-processing-testing/src/main/java/androidx/room/compiler/processing/SyntheticJavacProcessor.kt
@@ -17,24 +17,35 @@
 package androidx.room.compiler.processing
 
 import androidx.room.compiler.processing.util.XTestInvocation
+import javax.annotation.processing.AbstractProcessor
+import javax.annotation.processing.RoundEnvironment
 import javax.lang.model.SourceVersion
+import javax.lang.model.element.TypeElement
 
 @Suppress("VisibleForTests")
 @ExperimentalProcessingApi
 class SyntheticJavacProcessor private constructor(
     private val impl: SyntheticProcessorImpl
-) : JavacTestProcessor(), SyntheticProcessor by impl {
+) : AbstractProcessor(), SyntheticProcessor by impl {
     constructor(handlers: List<(XTestInvocation) -> Unit>) : this(
         SyntheticProcessorImpl(handlers)
     )
-    override fun doProcess(annotations: Set<XTypeElement>, roundEnv: XRoundEnv): Boolean {
+
+    override fun process(
+        annotations: MutableSet<out TypeElement>,
+        roundEnv: RoundEnvironment
+    ): Boolean {
+        if (roundEnv.processingOver()) {
+            return true
+        }
         if (!impl.canRunAnotherRound()) {
             return true
         }
         val xEnv = XProcessingEnv.create(processingEnv)
+        val xRoundEnv = XRoundEnv.create(xEnv, roundEnv)
         val testInvocation = XTestInvocation(
             processingEnv = xEnv,
-            roundEnv = roundEnv
+            roundEnv = xRoundEnv
         )
         impl.runNextRound(testInvocation)
         return impl.expectsAnotherRound()
diff --git a/room/room-compiler-processing/build.gradle b/room/room-compiler-processing/build.gradle
index f1c4286..d1ae9643 100644
--- a/room/room-compiler-processing/build.gradle
+++ b/room/room-compiler-processing/build.gradle
@@ -27,7 +27,6 @@
     api(libs.kotlinStdlib)
     api(libs.javapoet)
     api(libs.kotlinPoet)
-    implementation("androidx.annotation:annotation:1.1.0")
     implementation(libs.guava)
     implementation(libs.autoCommon)
     implementation(libs.autoValueAnnotations)
@@ -37,6 +36,7 @@
     implementation(libs.kspApi)
     implementation(libs.kotlinStdlibJdk8) // KSP defines older version as dependency, force update.
 
+    testImplementation("androidx.annotation:annotation:1.1.0")
     testImplementation(libs.googleCompileTesting)
     testImplementation(libs.junit)
     testImplementation(libs.jsr250)
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
index b850d16..64d89eb 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavaPoetExt.kt
@@ -15,7 +15,6 @@
  */
 package androidx.room.compiler.processing
 
-import androidx.room.compiler.processing.javac.JavacExecutableElement
 import androidx.room.compiler.processing.ksp.KspMethodElement
 import androidx.room.compiler.processing.ksp.KspMethodType
 import com.squareup.javapoet.ClassName
@@ -139,11 +138,8 @@
             }
             addAnnotation(Override::class.java)
             varargs(executableElement.isVarArgs())
-            if (executableElement is JavacExecutableElement) {
-                // copy throws for java
-                executableElement.element.thrownTypes.forEach {
-                    addException(TypeName.get(it))
-                }
+            executableElement.thrownTypes.forEach {
+                addException(it.typeName)
             }
             returns(resolvedType.returnType.typeName)
         }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavacTestProcessor.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavacTestProcessor.kt
deleted file mode 100644
index aa9b92a..0000000
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/JavacTestProcessor.kt
+++ /dev/null
@@ -1,58 +0,0 @@
-/*
- * Copyright (C) 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.room.compiler.processing
-
-import androidx.annotation.VisibleForTesting
-import androidx.room.compiler.processing.javac.JavacProcessingEnv
-import androidx.room.compiler.processing.javac.JavacRoundEnv
-import javax.annotation.processing.AbstractProcessor
-import javax.annotation.processing.RoundEnvironment
-import javax.lang.model.element.TypeElement
-
-/**
- * Javac processor implementation that provides access to the round environment.
- *
- * This is only used in tests, the main processor uses an API similar to the processing step
- * in Auto Common.
- */
-@VisibleForTesting
-@ExperimentalProcessingApi
-abstract class JavacTestProcessor : AbstractProcessor() {
-    val xProcessingEnv by lazy {
-        // lazily create this as it is not available on construction time
-        XProcessingEnv.create(super.processingEnv)
-    }
-
-    final override fun process(
-        annotations: MutableSet<out TypeElement>,
-        roundEnv: RoundEnvironment
-    ): Boolean {
-        if (roundEnv.processingOver()) {
-            return true
-        }
-        val env = xProcessingEnv as JavacProcessingEnv
-        val javacRoundEnv = JavacRoundEnv(env, roundEnv)
-        val xAnnotations = annotations.mapTo(mutableSetOf()) {
-            env.wrapTypeElement(it)
-        }
-        return doProcess(xAnnotations, javacRoundEnv)
-    }
-
-    abstract fun doProcess(
-        annotations: Set<XTypeElement>,
-        roundEnv: XRoundEnv
-    ): Boolean
-}
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
index 0bc4dd0..815f45e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/XExecutableElement.kt
@@ -40,6 +40,11 @@
      * @see [isVarArgs]
      */
     val parameters: List<XExecutableParameterElement>
+
+    /**
+     * The list of `Throwable`s that are declared in this executable's signature.
+     */
+    val thrownTypes: List<XType>
     /**
      * Returns true if this method receives a vararg parameter.
      */
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
index 14e4b06..aed0202 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/ElementExt.kt
@@ -21,19 +21,21 @@
 import javax.lang.model.element.Element
 
 private val NONNULL_ANNOTATIONS = arrayOf(
-    androidx.annotation.NonNull::class.java,
-    org.jetbrains.annotations.NotNull::class.java
+    "androidx.annotation.NonNull",
+    "org.jetbrains.annotations.NotNull"
 )
 
 private val NULLABLE_ANNOTATIONS = arrayOf(
-    androidx.annotation.Nullable::class.java,
-    org.jetbrains.annotations.Nullable::class.java
+    "androidx.annotation.Nullable",
+    "org.jetbrains.annotations.Nullable"
 )
 
 @Suppress("UnstableApiUsage")
-private fun Element.hasAnyOf(annotations: Array<Class<out Annotation>>) = annotations.any {
-    MoreElements.isAnnotationPresent(this, it)
-}
+private fun Element.hasAnyOf(annotations: Array<String>) =
+    annotationMirrors.any { annotationMirror ->
+        val annotationTypeElement = MoreElements.asType(annotationMirror.annotationType.asElement())
+        annotations.any { annotationTypeElement.qualifiedName.contentEquals(it) }
+    }
 
 internal val Element.nullability: XNullability
     get() = if (asType().kind.isPrimitive || hasAnyOf(NONNULL_ANNOTATIONS)) {
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacBasicAnnotationProcessor.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacBasicAnnotationProcessor.kt
index f8926ad..f41a368 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacBasicAnnotationProcessor.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacBasicAnnotationProcessor.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.XBasicAnnotationProcessor
 import androidx.room.compiler.processing.XElement
+import androidx.room.compiler.processing.XProcessingStep
 import androidx.room.compiler.processing.XRoundEnv
 import com.google.auto.common.BasicAnnotationProcessor
 import com.google.common.collect.ImmutableSetMultimap
@@ -31,43 +32,45 @@
 abstract class JavacBasicAnnotationProcessor :
     BasicAnnotationProcessor(), XBasicAnnotationProcessor {
 
-    final override fun steps(): Iterable<Step> {
-        // Execute all processing steps in a single auto-common Step. This is done to share the
-        // XProcessingEnv and its cached across steps in the same round.
-        val steps = processingSteps()
-        val parentStep = object : Step {
-            override fun annotations() = steps.flatMap { it.annotations() }.toSet()
+    // This state is cached here so that it can be shared by all steps in a given processing round.
+    // The state is initialized at beginning of each round using the InitializingStep, and
+    // the state is cleared at the end of each round in BasicAnnotationProcessor#postRound()
+    private var cachedXEnv: JavacProcessingEnv? = null
 
-            override fun process(
-                elementsByAnnotation: ImmutableSetMultimap<String, Element>
-            ): Set<Element> {
-                val xEnv = JavacProcessingEnv(processingEnv)
-                val convertedElementsByAnnotation = mutableMapOf<String, Set<XElement>>()
-                annotations().forEach { annotation ->
-                    convertedElementsByAnnotation[annotation] =
-                        elementsByAnnotation[annotation].mapNotNull { element ->
-                            xEnv.wrapAnnotatedElement(element, annotation)
-                        }.toSet()
-                }
-                val results = steps.flatMap { step ->
-                    step.process(
-                        env = xEnv,
-                        elementsByAnnotation = step.annotations().associateWith {
-                            convertedElementsByAnnotation[it] ?: emptySet()
-                        }
-                    )
-                }
-                return results.map { (it as JavacElement).element }.toSet()
+    final override fun steps(): Iterable<Step> {
+        return processingSteps().map { DelegatingStep(it) }
+    }
+
+    /** A [Step] that delegates to an [XProcessingStep]. */
+    private inner class DelegatingStep(val xStep: XProcessingStep) : Step {
+        override fun annotations() = xStep.annotations()
+
+        override fun process(
+            elementsByAnnotation: ImmutableSetMultimap<String, Element>
+        ): Set<Element> {
+            // The first step in a round initializes the cachedXEnv. Note: the "first" step can
+            // change each round depending on which annotations are present in the current round and
+            // which elements were deferred in the previous round.
+            val xEnv = cachedXEnv ?: JavacProcessingEnv(processingEnv).also { cachedXEnv = it }
+            val xElementsByAnnotation = mutableMapOf<String, Set<XElement>>()
+            xStep.annotations().forEach { annotation ->
+                xElementsByAnnotation[annotation] =
+                    elementsByAnnotation[annotation].mapNotNull { element ->
+                        xEnv.wrapAnnotatedElement(element, annotation)
+                    }.toSet()
             }
+            return xStep.process(xEnv, xElementsByAnnotation).map {
+                (it as JavacElement).element
+            }.toSet()
         }
-        return listOf(parentStep)
     }
 
     final override fun postRound(roundEnv: RoundEnvironment) {
-        // Due to BasicAnnotationProcessor taking over AbstractProcessor#process() we can't
-        // share the same XProcessingEnv from the steps, but that might be ok...
-        val xEnv = JavacProcessingEnv(processingEnv)
+        // The cachedXEnv can be null if none of the steps were processed in the round.
+        // In this case, we just create a new one since there is no cached one to share.
+        val xEnv = cachedXEnv ?: JavacProcessingEnv(processingEnv)
         val xRound = XRoundEnv.create(xEnv, roundEnv)
         postRound(xEnv, xRound)
+        cachedXEnv = null // Reset after every round to allow GC
     }
 }
\ No newline at end of file
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacExecutableElement.kt
index ea9c18a..ffca24b 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/javac/JavacExecutableElement.kt
@@ -18,6 +18,7 @@
 
 import androidx.room.compiler.processing.XExecutableElement
 import androidx.room.compiler.processing.XHasModifiers
+import androidx.room.compiler.processing.XNullability
 import androidx.room.compiler.processing.javac.kotlin.KmExecutable
 import androidx.room.compiler.processing.javac.kotlin.descriptor
 import javax.lang.model.element.ExecutableElement
@@ -59,6 +60,16 @@
         return element.isVarArgs
     }
 
+    override val thrownTypes by lazy {
+        element.thrownTypes.map {
+            env.wrap<JavacType>(
+                typeMirror = it,
+                kotlinType = null,
+                elementNullability = XNullability.UNKNOWN
+            )
+        }
+    }
+
     companion object {
         internal const val DEFAULT_IMPLS_CLASS_NAME = "DefaultImpls"
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
index 2a150d9..68af580 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspAnnotated.kt
@@ -24,6 +24,7 @@
 import com.google.devtools.ksp.symbol.AnnotationUseSiteTarget
 import com.google.devtools.ksp.symbol.KSAnnotated
 import com.google.devtools.ksp.symbol.KSAnnotation
+import com.google.devtools.ksp.symbol.KSTypeAlias
 import kotlin.reflect.KClass
 
 @OptIn(KspExperimental::class)
@@ -90,7 +91,10 @@
         ksAnnotation: KSAnnotation,
         annotationClass: KClass<out Annotation>
     ): Boolean {
-        val declaration = ksAnnotation.annotationType.resolve().declaration
+        var declaration = ksAnnotation.annotationType.resolve().declaration
+        while (declaration is KSTypeAlias) {
+            declaration = declaration.type.resolve().declaration
+        }
         val qualifiedName = declaration.qualifiedName?.asString() ?: return false
         return qualifiedName == annotationClass.qualifiedName
     }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
index 1fc7b4d..43ea091 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspBasicAnnotationProcessor.kt
@@ -46,14 +46,24 @@
         val round = XRoundEnv.create(processingEnv)
         val deferredElements = processingSteps().flatMap { step ->
             val invalidElements = mutableSetOf<XElement>()
-            val elementsByAnnotation = step.annotations().associateWith { annotation ->
+            val elementsByAnnotation = step.annotations().mapNotNull { annotation ->
                 val annotatedElements = round.getElementsAnnotatedWith(annotation)
-                annotatedElements
+                val validElements = annotatedElements
                     .filter { (it as KspElement).declaration.validateExceptLocals() }
-                    .also { invalidElements.addAll(annotatedElements - it) }
                     .toSet()
+                invalidElements.addAll(annotatedElements - validElements)
+                if (validElements.isNotEmpty()) {
+                    annotation to validElements
+                } else {
+                    null
+                }
+            }.toMap()
+            // Only process the step if there are annotated elements found for this step.
+            if (elementsByAnnotation.isNotEmpty()) {
+                invalidElements + step.process(processingEnv, elementsByAnnotation)
+            } else {
+                invalidElements
             }
-            invalidElements + step.process(processingEnv, elementsByAnnotation)
         }
         postRound(processingEnv, round)
         return deferredElements.map { (it as KspElement).declaration }
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
index f2a52bb..5e620f7 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspExecutableElement.kt
@@ -20,8 +20,10 @@
 import androidx.room.compiler.processing.XExecutableElement
 import androidx.room.compiler.processing.XExecutableParameterElement
 import androidx.room.compiler.processing.XHasModifiers
+import androidx.room.compiler.processing.XType
 import androidx.room.compiler.processing.ksp.KspAnnotated.UseSiteFilter.Companion.NO_USE_SITE
 import androidx.room.compiler.processing.util.ISSUE_TRACKER_LINK
+import com.google.devtools.ksp.KspExperimental
 import com.google.devtools.ksp.isConstructor
 import com.google.devtools.ksp.symbol.KSFunctionDeclaration
 import com.google.devtools.ksp.symbol.Modifier
@@ -60,6 +62,16 @@
         }
     }
 
+    @OptIn(KspExperimental::class)
+    override val thrownTypes: List<XType> by lazy {
+        env.resolver.getJvmCheckedException(declaration).map {
+            env.wrap(
+                ksType = it,
+                allowPrimitives = false
+            )
+        }.toList()
+    }
+
     override fun isVarArgs(): Boolean {
         // in java, only the last argument can be a vararg so for suspend functions, it is never
         // a vararg function. this would change if room generated kotlin code
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt
index 3bdd7ae..d07a36e 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspReflectiveAnnotationBox.kt
@@ -16,7 +16,6 @@
 
 package androidx.room.compiler.processing.ksp
 
-import androidx.annotation.VisibleForTesting
 import androidx.room.compiler.processing.XAnnotationBox
 import androidx.room.compiler.processing.XType
 
@@ -25,7 +24,7 @@
  * handles those cases.
  * see: https://github.com/google/ksp/issues/53
  */
-internal class KspReflectiveAnnotationBox<T : Annotation> @VisibleForTesting constructor(
+internal class KspReflectiveAnnotationBox<T : Annotation> constructor(
     private val env: KspProcessingEnv,
     private val annotationClass: Class<T>,
     private val annotation: T
diff --git a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
index 4c0226b..d01cec9 100644
--- a/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
+++ b/room/room-compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/synthetic/KspSyntheticPropertyMethodElement.kt
@@ -93,6 +93,16 @@
     override val docComment: String?
         get() = null
 
+    override val thrownTypes: List<XType>
+        get() {
+            // TODO replace with the Resolver method when it is available. This solution works only
+            //  in sources.
+            //  https://github.com/google/ksp/issues/505
+            return getAnnotation(Throws::class)
+                ?.getAsTypeList("exceptionClasses")
+                ?: emptyList()
+        }
+
     final override fun asMemberOf(other: XType): XMethodType {
         return KspSyntheticPropertyMethodType.create(
             element = this,
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavacTestProcessorTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavacTestProcessorTest.kt
deleted file mode 100644
index 7b42e74..0000000
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/JavacTestProcessorTest.kt
+++ /dev/null
@@ -1,100 +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.room.compiler.processing
-
-import androidx.room.compiler.processing.testcode.OtherAnnotation
-import androidx.room.compiler.processing.util.Source
-import androidx.room.compiler.processing.util.getField
-import androidx.room.compiler.processing.util.getMethod
-import com.google.common.truth.Truth.assertAbout
-import com.google.common.truth.Truth.assertThat
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import org.junit.Test
-import java.util.concurrent.atomic.AtomicBoolean
-import kotlin.reflect.KClass
-
-class JavacTestProcessorTest {
-
-    @Test
-    fun getElementsAnnotatedWith() {
-        val source = Source.java(
-            "foo.bar.Baz",
-            """
-            package foo.bar;
-            import androidx.room.compiler.processing.testcode.OtherAnnotation;
-            @OtherAnnotation(value="xx")
-            class Baz {
-              @OtherAnnotation(value="xx")
-              int myField = 0;
-              @OtherAnnotation(value="xx")
-              void myFunction() { }
-            }
-            """.trimIndent()
-        )
-        testProcessor(listOf(source), listOf(OtherAnnotation::class)) { roundEnv ->
-            val annotatedElementsByClass = roundEnv.getElementsAnnotatedWith(
-                OtherAnnotation::class
-            )
-
-            val annotatedElementsByName = roundEnv.getElementsAnnotatedWith(
-                OtherAnnotation::class.qualifiedName!!
-            )
-
-            val targetElement = xProcessingEnv.requireTypeElement("foo.bar.Baz")
-            assertThat(
-                annotatedElementsByClass
-            ).apply {
-                containsExactlyElementsIn(annotatedElementsByName)
-                hasSize(3)
-                contains(targetElement)
-                contains(targetElement.getMethod("myFunction"))
-                contains(targetElement.getField("myField"))
-            }
-        }
-    }
-
-    private fun testProcessor(
-        sources: List<Source>,
-        annotations: List<KClass<out Annotation>>,
-        doProcessTest: JavacTestProcessor.(XRoundEnv) -> Unit
-    ) {
-        val invoked = AtomicBoolean(false)
-
-        val testProcessor = object : JavacTestProcessor() {
-            override fun doProcess(annotations: Set<XTypeElement>, roundEnv: XRoundEnv): Boolean {
-                invoked.set(true)
-
-                doProcessTest(roundEnv)
-
-                return true
-            }
-
-            override fun getSupportedAnnotationTypes(): Set<String> {
-                return annotations.map { it.java.canonicalName }.toSet()
-            }
-        }
-        assertAbout(
-            JavaSourcesSubjectFactory.javaSources()
-        ).that(
-            sources.map { it.toJFO() }
-        ).processedWith(
-            testProcessor
-        ).compilesWithoutError()
-
-        assertThat(invoked.get()).isTrue()
-    }
-}
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
index 2032454..f76ad64 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/MethodSpecHelperTest.kt
@@ -442,8 +442,9 @@
         ) { invocation ->
             val (target, methods) = invocation.getOverrideTestTargets(ignoreInheritedMethods)
             methods.forEachIndexed { index, method ->
-                if (invocation.isKsp && method.name == "throwsException") {
+                if (invocation.isKsp && method.name == "throwsException" && preCompiledCode) {
                     // TODO b/171572318
+                    //  https://github.com/google/ksp/issues/507
                 } else {
                     val subject = MethodSpecHelper.overridingWithFinalParams(
                         method,
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
index 6f0b907..05526ea 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XAnnotationTest.kt
@@ -40,6 +40,8 @@
 import org.junit.runner.RunWith
 import org.junit.runners.Parameterized
 
+// used in typealias test
+typealias OtherAnnotationTypeAlias = OtherAnnotation
 @RunWith(Parameterized::class)
 class XAnnotationTest(
     private val preCompiled: Boolean
@@ -662,6 +664,33 @@
         }
     }
 
+    @Test
+    fun typealiasAnnotation() {
+        val source = Source.kotlin(
+            "Subject.kt",
+            """
+            typealias SourceTypeAlias = ${OtherAnnotation::class.qualifiedName}
+            @SourceTypeAlias("x")
+            class Subject {
+            }
+            """.trimIndent()
+        )
+        runTest(
+            sources = listOf(source)
+        ) { invocation ->
+            // TODO use getSymbolsWithAnnotation after
+            // https://github.com/google/ksp/issues/506 is fixed
+            val subject = invocation.processingEnv.requireTypeElement("Subject")
+            val annotation = subject.getAnnotation(OtherAnnotation::class)
+            assertThat(annotation).isNotNull()
+            assertThat(annotation?.value?.value).isEqualTo("x")
+
+            val annotation2 = subject.getAnnotation(OtherAnnotationTypeAlias::class)
+            assertThat(annotation2).isNotNull()
+            assertThat(annotation2?.value?.value).isEqualTo("x")
+        }
+    }
+
     // helper function to read what we need
     private fun XAnnotated.getSuppressValues(): List<String>? {
         return this.findAnnotation<TestSuppressWarnings>()
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
index 3f8a36a..78e2a11 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XExecutableElementTest.kt
@@ -20,6 +20,7 @@
 import androidx.room.compiler.processing.util.Source
 import androidx.room.compiler.processing.util.UNIT_CLASS_NAME
 import androidx.room.compiler.processing.util.className
+import androidx.room.compiler.processing.util.compileFiles
 import androidx.room.compiler.processing.util.getDeclaredMethod
 import androidx.room.compiler.processing.util.getMethod
 import androidx.room.compiler.processing.util.getParameter
@@ -27,12 +28,16 @@
 import androidx.room.compiler.processing.util.typeName
 import com.google.common.truth.Truth
 import com.google.common.truth.Truth.assertThat
+import com.google.common.truth.Truth.assertWithMessage
+import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.ParameterizedTypeName
 import com.squareup.javapoet.TypeName
 import com.squareup.javapoet.WildcardTypeName
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
+import java.io.IOException
+import java.lang.IllegalStateException
 
 @RunWith(JUnit4::class)
 class XExecutableElementTest {
@@ -604,6 +609,122 @@
         genericToPrimitiveOverrides(asMemberOf = true)
     }
 
+    @Test
+    fun thrownTypes() {
+        fun buildSources(pkg: String) = listOf(
+            Source.java(
+                "$pkg.JavaSubject",
+                """
+                package $pkg;
+                import java.io.*;
+                public class JavaSubject {
+                    public JavaSubject() throws IllegalArgumentException {}
+
+                    public void multipleThrows() throws IOException, IllegalStateException {
+                    }
+                }
+                """.trimIndent()
+            ),
+            Source.kotlin(
+                "KotlinSubject.kt",
+                """
+                package $pkg
+                import java.io.*
+                public class KotlinSubject {
+                    @Throws(IllegalArgumentException::class)
+                    constructor() {
+                    }
+
+                    @Throws(IOException::class, IllegalStateException::class)
+                    fun multipleThrows() {
+                    }
+                }
+                """.trimIndent()
+            ),
+            Source.kotlin(
+                "AccessorThrows.kt",
+                """
+                package $pkg
+                import java.io.*
+                public class KotlinAccessors {
+                    @get:Throws(IllegalArgumentException::class)
+                    val getterThrows: Int = 3
+                    @set:Throws(IllegalStateException::class)
+                    var setterThrows: Int = 3
+                    @get:Throws(IOException::class)
+                    @set:Throws(IllegalStateException::class, IllegalArgumentException::class)
+                    var bothThrows: Int = 3
+                }
+                """.trimIndent()
+            )
+        )
+        runProcessorTest(
+            sources = buildSources("app"),
+            classpath = compileFiles(sources = buildSources("lib"))
+        ) { invocation ->
+            fun collectExceptions(subject: XTypeElement): List<Pair<String, Set<TypeName>>> {
+                return (subject.getConstructors() + subject.getDeclaredMethods()).mapNotNull {
+                    val throwTypes = it.thrownTypes
+                    val name = if (it is XMethodElement) {
+                        it.name
+                    } else {
+                        "<init>"
+                    }
+                    if (throwTypes.isEmpty()) {
+                        null
+                    } else {
+                        name to throwTypes.map { it.typeName }.toSet()
+                    }
+                }
+            }
+            // TODO
+            // add lib here once https://github.com/google/ksp/issues/507 is fixed
+            listOf("app").forEach { pkg ->
+                invocation.processingEnv.requireTypeElement("$pkg.KotlinSubject").let { subject ->
+                    assertWithMessage(subject.qualifiedName).that(
+                        collectExceptions(subject)
+                    ).containsExactly(
+                        "<init>" to setOf(ClassName.get(IllegalArgumentException::class.java)),
+                        "multipleThrows" to setOf(
+                            ClassName.get(IOException::class.java),
+                            ClassName.get(IllegalStateException::class.java)
+                        )
+                    )
+                }
+                invocation.processingEnv.requireTypeElement("$pkg.JavaSubject").let { subject ->
+                    assertWithMessage(subject.qualifiedName).that(
+                        collectExceptions(subject)
+                    ).containsExactly(
+                        "<init>" to setOf(ClassName.get(IllegalArgumentException::class.java)),
+                        "multipleThrows" to setOf(
+                            ClassName.get(IOException::class.java),
+                            ClassName.get(IllegalStateException::class.java)
+                        )
+                    )
+                }
+                invocation.processingEnv.requireTypeElement("$pkg.KotlinAccessors").let { subject ->
+                    assertWithMessage(subject.qualifiedName).that(
+                        collectExceptions(subject)
+                    ).containsExactly(
+                        "getGetterThrows" to setOf(
+                            ClassName.get(IllegalArgumentException::class.java)
+                        ),
+                        "setSetterThrows" to setOf(
+                            ClassName.get(IllegalStateException::class.java)
+                        ),
+                        "getBothThrows" to setOf(
+                            ClassName.get(IOException::class.java)
+                        ),
+                        "setBothThrows" to setOf(
+                            ClassName.get(IllegalStateException::class.java),
+                            ClassName.get(IllegalArgumentException::class.java)
+                        ),
+                    )
+                }
+            }
+        }
+    }
+
     // see b/160258066
     private fun genericToPrimitiveOverrides(asMemberOf: Boolean) {
         val source = Source.kotlin(
diff --git a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingStepTest.kt b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingStepTest.kt
index b0af4d3..fbd36fc 100644
--- a/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingStepTest.kt
+++ b/room/room-compiler-processing/src/test/java/androidx/room/compiler/processing/XProcessingStepTest.kt
@@ -268,6 +268,68 @@
     }
 
     @Test
+    fun cachingBetweenSteps() {
+        val main = JavaFileObjects.forSourceString(
+            "foo.bar.Main",
+            """
+            package foo.bar;
+            import androidx.room.compiler.processing.testcode.*;
+            @MainAnnotation(
+                typeList = {},
+                singleType = Object.class,
+                intMethod = 3,
+                singleOtherAnnotation = @OtherAnnotation("y")
+            )
+            class Main {}
+            """.trimIndent()
+        )
+        val other = JavaFileObjects.forSourceString(
+            "foo.bar.Other",
+            """
+            package foo.bar;
+            import androidx.room.compiler.processing.testcode.*;
+            @OtherAnnotation("x")
+            class Other {
+            }
+            """.trimIndent()
+        )
+        val elementsByStep = mutableMapOf<XProcessingStep, XTypeElement>()
+        // create a scenario where we can test caching between steps
+        val mainStep = object : XProcessingStep {
+            override fun annotations(): Set<String> = setOf(MainAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XTypeElement> {
+                elementsByStep[this] = env.requireTypeElement("foo.bar.Main")
+                return emptySet()
+            }
+        }
+        val otherStep = object : XProcessingStep {
+            override fun annotations(): Set<String> = setOf(OtherAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XTypeElement> {
+                elementsByStep[this] = env.requireTypeElement("foo.bar.Main")
+                return emptySet()
+            }
+        }
+        assertAbout(
+            JavaSourcesSubjectFactory.javaSources()
+        ).that(
+            listOf(main, other)
+        ).processedWith(
+            object : JavacBasicAnnotationProcessor() {
+                override fun processingSteps() = listOf(mainStep, otherStep)
+            }
+        ).compilesWithoutError()
+        assertThat(elementsByStep.keys).containsExactly(mainStep, otherStep)
+        // make sure elements between steps are the same instances
+        assertThat(elementsByStep[mainStep]).isSameInstanceAs(elementsByStep[otherStep])
+    }
+
+    @Test
     fun kspReturnsUnprocessed() {
         CompilationTestCapabilities.assumeKspIsEnabled()
         var returned: Set<XElement>? = null
@@ -388,6 +450,112 @@
     }
 
     @Test
+    fun javacDeferredStep() {
+        // create a scenario where we defer the first round of processing
+        val main = JavaFileObjects.forSourceString(
+            "foo.bar.Main",
+            """
+            package foo.bar;
+            import androidx.room.compiler.processing.testcode.*;
+            @MainAnnotation(
+                typeList = {},
+                singleType = Object.class,
+                intMethod = 3,
+                singleOtherAnnotation = @OtherAnnotation("y")
+            )
+            class Main {}
+            """.trimIndent()
+        )
+        val stepsProcessed = mutableListOf<XProcessingStep>()
+        val mainStep = object : XProcessingStep {
+            var round = 0
+            override fun annotations() = setOf(MainAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                val deferredElements = if (round++ == 0) {
+                    // Generate a random class to trigger another processing round
+                    val className = ClassName.get("foo.bar", "Main_Impl")
+                    val spec = TypeSpec.classBuilder(className).build()
+                    JavaFile.builder(className.packageName(), spec)
+                        .build()
+                        .writeTo(env.filer)
+
+                    // Defer all processing to the next round
+                    elementsByAnnotation.values.flatten().toSet()
+                } else {
+                    emptySet()
+                }
+                return deferredElements
+            }
+        }
+        assertAbout(
+            JavaSourcesSubjectFactory.javaSources()
+        ).that(
+            listOf(main)
+        ).processedWith(
+            object : JavacBasicAnnotationProcessor() {
+                override fun processingSteps() = listOf(mainStep)
+            }
+        ).compilesWithoutError()
+
+        // Assert that mainStep was processed twice due to deferring
+        assertThat(stepsProcessed).containsExactly(mainStep, mainStep)
+    }
+
+    @Test
+    fun javacStepOnlyCalledIfElementsToProcess() {
+        val main = JavaFileObjects.forSourceString(
+            "foo.bar.Main",
+            """
+            package foo.bar;
+            import androidx.room.compiler.processing.testcode.*;
+            @MainAnnotation(
+                typeList = {},
+                singleType = Object.class,
+                intMethod = 3,
+                singleOtherAnnotation = @OtherAnnotation("y")
+            )
+            class Main {
+            }
+            """.trimIndent()
+        )
+        val stepsProcessed = mutableListOf<XProcessingStep>()
+        val mainStep = object : XProcessingStep {
+            override fun annotations() = setOf(MainAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                return emptySet()
+            }
+        }
+        val otherStep = object : XProcessingStep {
+            override fun annotations() = setOf(OtherAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                return emptySet()
+            }
+        }
+        assertAbout(
+            JavaSourcesSubjectFactory.javaSources()
+        ).that(
+            listOf(main)
+        ).processedWith(
+            object : JavacBasicAnnotationProcessor() {
+                override fun processingSteps() = listOf(mainStep, otherStep)
+            }
+        ).compilesWithoutError()
+        assertThat(stepsProcessed).containsExactly(mainStep)
+    }
+
+    @Test
     fun kspAnnotatedElementsByStep() {
         val main = SourceFile.kotlin(
             "Classes.kt",
@@ -449,4 +617,121 @@
         assertThat(elementsByStep[otherStep])
             .containsExactly("foo.bar.Other")
     }
+
+    @Test
+    fun kspDeferredStep() {
+        // create a scenario where we defer the first round of processing
+        val main = SourceFile.kotlin(
+            "Classes.kt",
+            """
+            package foo.bar
+            import androidx.room.compiler.processing.testcode.*
+            @MainAnnotation(
+                typeList = [],
+                singleType = Any::class,
+                intMethod = 3,
+                singleOtherAnnotation = OtherAnnotation("y")
+            )
+            class Main {}
+            """.trimIndent()
+        )
+        val stepsProcessed = mutableListOf<XProcessingStep>()
+        val mainStep = object : XProcessingStep {
+            var round = 0
+            override fun annotations() = setOf(MainAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                val deferredElements = if (round++ == 0) {
+                    // Generate a random class to trigger another processing round
+                    val className = ClassName.get("foo.bar", "Main_Impl")
+                    val spec = TypeSpec.classBuilder(className).build()
+                    JavaFile.builder(className.packageName(), spec)
+                        .build()
+                        .writeTo(env.filer)
+
+                    // Defer all processing to the next round
+                    elementsByAnnotation.values.flatten().toSet()
+                } else {
+                    emptySet()
+                }
+                return deferredElements
+            }
+        }
+
+        val processorProvider = object : SymbolProcessorProvider {
+            override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+                return object : KspBasicAnnotationProcessor(environment) {
+                    override fun processingSteps() = listOf(mainStep)
+                }
+            }
+        }
+        KotlinCompilation().apply {
+            workingDir = temporaryFolder.root
+            inheritClassPath = true
+            symbolProcessorProviders = listOf(processorProvider)
+            sources = listOf(main)
+            verbose = false
+        }.compile()
+
+        // Assert that mainStep was processed twice due to deferring
+        assertThat(stepsProcessed).containsExactly(mainStep, mainStep)
+    }
+
+    @Test
+    fun kspStepOnlyCalledIfElementsToProcess() {
+        val main = SourceFile.kotlin(
+            "Classes.kt",
+            """
+            package foo.bar
+            import androidx.room.compiler.processing.testcode.*
+            @MainAnnotation(
+                typeList = [],
+                singleType = Any::class,
+                intMethod = 3,
+                singleOtherAnnotation = OtherAnnotation("y")
+            )
+            class Main {
+            }
+            """.trimIndent()
+        )
+        val stepsProcessed = mutableListOf<XProcessingStep>()
+        val mainStep = object : XProcessingStep {
+            override fun annotations() = setOf(MainAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                return emptySet()
+            }
+        }
+        val otherStep = object : XProcessingStep {
+            override fun annotations() = setOf(OtherAnnotation::class.qualifiedName!!)
+            override fun process(
+                env: XProcessingEnv,
+                elementsByAnnotation: Map<String, Set<XElement>>
+            ): Set<XElement> {
+                stepsProcessed.add(this)
+                return emptySet()
+            }
+        }
+        val processorProvider = object : SymbolProcessorProvider {
+            override fun create(environment: SymbolProcessorEnvironment): SymbolProcessor {
+                return object : KspBasicAnnotationProcessor(environment) {
+                    override fun processingSteps() = listOf(mainStep, otherStep)
+                }
+            }
+        }
+        KotlinCompilation().apply {
+            workingDir = temporaryFolder.root
+            inheritClassPath = true
+            symbolProcessorProviders = listOf(processorProvider)
+            sources = listOf(main)
+            verbose = false
+        }.compile()
+        assertThat(stepsProcessed).containsExactly(mainStep)
+    }
 }
\ No newline at end of file
diff --git a/settings.gradle b/settings.gradle
index a78875b..9904a62 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -216,7 +216,6 @@
 includeProject(":benchmark:benchmark-junit4", "benchmark/junit4")
 includeProject(":benchmark:benchmark-macro", "benchmark/macro", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":benchmark:benchmark-macro-junit4", "benchmark/macro-junit4", [BuildType.MAIN, BuildType.COMPOSE])
-includeProject(":benchmark:integration-tests:crystalball-experiment", "benchmark/integration-tests/crystalball-experiment", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:dry-run-benchmark", "benchmark/integration-tests/dry-run-benchmark", [BuildType.MAIN])
 includeProject(":benchmark:integration-tests:macrobenchmark", "benchmark/integration-tests/macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":benchmark:integration-tests:macrobenchmark-target", "benchmark/integration-tests/macrobenchmark-target", [BuildType.MAIN, BuildType.COMPOSE])
@@ -623,6 +622,8 @@
 includeProject(":wear:wear-complications-data", "wear/wear-complications-data", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-complications-data-source", "wear/wear-complications-data-source", [BuildType.MAIN, BuildType.WEAR])
 includeProject(":wear:wear-complications-data-source-samples", "wear/wear-complications-data-source-samples", [BuildType.MAIN, BuildType.WEAR])
+includeProject(":wear:benchmark:integration-tests:macrobenchmark-target", "wear/benchmark/integration-tests/macrobenchmark-target", [BuildType.MAIN, BuildType.COMPOSE])
+includeProject(":wear:benchmark:integration-tests:macrobenchmark", "wear/benchmark/integration-tests/macrobenchmark", [BuildType.MAIN, BuildType.COMPOSE])
 includeProject(":wear:compose:compose-foundation", "wear/compose/foundation", [BuildType.COMPOSE])
 includeProject(":wear:compose:compose-foundation-samples", "wear/compose/foundation/samples", [BuildType.COMPOSE])
 includeProject(":wear:compose:compose-material", "wear/compose/material", [BuildType.COMPOSE])
diff --git a/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle b/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle
new file mode 100644
index 0000000..76ae77a
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark-target/build.gradle
@@ -0,0 +1,39 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id 'com.android.application'
+    id 'kotlin-android'
+}
+
+android {
+    buildTypes {
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile("proguard-android-optimize.txt")
+        }
+    }
+}
+
+dependencies {
+    implementation(libs.kotlinStdlib)
+    implementation(libs.constraintLayout)
+    implementation projectOrArtifact(":activity:activity-ktx")
+    implementation 'androidx.core:core-ktx'
+    implementation(libs.material)
+}
diff --git a/wear/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..0aaac14
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/AndroidManifest.xml
@@ -0,0 +1,53 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest
+    xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    package="androidx.wear.benchmark.integration.macrobenchmark.target">
+
+    <application
+        android:label="wear Macrobenchmark Target"
+        android:allowBackup="false"
+        android:supportsRtl="true"
+        android:theme="@android:style/Theme.DeviceDefault"
+        tools:ignore="MissingApplicationIcon">
+
+        <!-- Profileable to enable macrobenchmark profiling -->
+        <!--suppress AndroidElementNotAllowed -->
+        <profileable android:shell="true"/>
+
+        <!--
+        Activities need to be exported so the macrobenchmark can discover them
+        under the new package visibility changes for Android 11.
+         -->
+        <activity
+            android:name=".StartActivity"
+            android:exported="true">
+            <intent-filter>
+                <action android:name="android.intent.action.MAIN" />
+                <category android:name="android.intent.category.LAUNCHER" />
+            </intent-filter>
+            <intent-filter>
+                <action
+                  android:name=
+                    "androidx.wear.benchmark.integration.macrobenchmark.target.STARTUP_ACTIVITY" />
+                <category android:name="android.intent.category.DEFAULT" />
+            </intent-filter>
+        </activity>
+    </application>
+</manifest>
\ No newline at end of file
diff --git a/wear/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/target/StartActivity.kt b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/target/StartActivity.kt
new file mode 100644
index 0000000..d6e4c18
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/java/androidx/wear/benchmark/integration/macrobenchmark/target/StartActivity.kt
@@ -0,0 +1,31 @@
+/*
+ * 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.wear.benchmark.integration.macrobenchmark.target
+
+import android.app.Activity
+import android.os.Bundle
+import android.widget.TextView
+
+class StartActivity : Activity() {
+    override fun onCreate(savedInstanceState: Bundle?) {
+        super.onCreate(savedInstanceState)
+        setContentView(R.layout.activity_start)
+
+        val txt = findViewById<TextView>(R.id.text)
+        txt.setText("Wear Macrobenchmark Target")
+    }
+}
\ No newline at end of file
diff --git a/wear/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/activity_start.xml b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/activity_start.xml
new file mode 100644
index 0000000..8ca9e47
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark-target/src/main/res/layout/activity_start.xml
@@ -0,0 +1,33 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
+    xmlns:tools="http://schemas.android.com/tools"
+    android:layout_width="match_parent"
+    android:layout_height="match_parent"
+    android:padding="0dp"
+    tools:context=".StartActivity"
+    tools:deviceIds="wear">
+
+    <TextView
+        android:id="@+id/text"
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content"
+        android:layout_gravity="center"
+        android:text="Preview text" />
+
+</FrameLayout>
\ No newline at end of file
diff --git a/wear/benchmark/integration-tests/macrobenchmark/build.gradle b/wear/benchmark/integration-tests/macrobenchmark/build.gradle
new file mode 100644
index 0000000..67fa2c2
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark/build.gradle
@@ -0,0 +1,43 @@
+/*
+ * 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.
+ */
+
+plugins {
+    id("AndroidXPlugin")
+    id 'com.android.library'
+    id 'kotlin-android'
+}
+
+android {
+    defaultConfig {
+        minSdkVersion 29
+    }
+}
+
+dependencies {
+    androidTestImplementation(project(":benchmark:benchmark-junit4"))
+    androidTestImplementation(project(":benchmark:benchmark-macro-junit4"))
+    androidTestImplementation(project(":internal-testutils-macrobenchmark"))
+    androidTestImplementation(libs.testRules)
+    androidTestImplementation(libs.testExtJunit)
+    androidTestImplementation(libs.testCore)
+    androidTestImplementation(libs.testRunner)
+    androidTestImplementation(libs.testUiautomator)
+}
+
+// Define a task dependency so the app is installed before we run macro benchmarks.
+tasks.getByPath(":wear:benchmark:integration-tests:macrobenchmark:connectedCheck")
+    .dependsOn(tasks.getByPath(
+            ":wear:benchmark:integration-tests:macrobenchmark-target:installRelease"))
\ No newline at end of file
diff --git a/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml b/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..c9becec4
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest package="androidx.wear.benchmark.integration.macrobenchmark.test"/>
diff --git a/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/benchmark/integration/macrobenchmark/StartupBenchmark.kt b/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/benchmark/integration/macrobenchmark/StartupBenchmark.kt
new file mode 100644
index 0000000..87063f6
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark/src/androidTest/java/androidx/wear/benchmark/integration/macrobenchmark/StartupBenchmark.kt
@@ -0,0 +1,53 @@
+/*
+ * 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.wear.benchmark.integration.macrobenchmark
+
+import androidx.benchmark.macro.CompilationMode
+import androidx.benchmark.macro.StartupMode
+import androidx.benchmark.macro.junit4.MacrobenchmarkRule
+import androidx.test.filters.LargeTest
+import androidx.testutils.createStartupCompilationParams
+import androidx.testutils.measureStartup
+import org.junit.Rule
+import org.junit.Test
+import org.junit.runner.RunWith
+import org.junit.runners.Parameterized
+
+@LargeTest
+@RunWith(Parameterized::class)
+class StartupBenchmark(
+    private val startupMode: StartupMode,
+    private val compilationMode: CompilationMode
+) {
+    @get:Rule
+    val benchmarkRule = MacrobenchmarkRule()
+
+    @Test
+    fun startup() = benchmarkRule.measureStartup(
+        compilationMode = compilationMode,
+        startupMode = startupMode,
+        packageName = "androidx.wear.benchmark.integration.macrobenchmark.target"
+    ) {
+        action = "androidx.wear.benchmark.integration.macrobenchmark.target" + ".STARTUP_ACTIVITY"
+    }
+
+    companion object {
+        @Parameterized.Parameters(name = "startup={0},compilation={1}")
+        @JvmStatic
+        fun parameters() = createStartupCompilationParams()
+    }
+}
\ No newline at end of file
diff --git a/wear/benchmark/integration-tests/macrobenchmark/src/main/AndroidManifest.xml b/wear/benchmark/integration-tests/macrobenchmark/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..86fc508
--- /dev/null
+++ b/wear/benchmark/integration-tests/macrobenchmark/src/main/AndroidManifest.xml
@@ -0,0 +1,18 @@
+<?xml version="1.0" encoding="utf-8"?>
+<!--
+  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.
+  -->
+
+<manifest package="androidx.wear.benchmark.integration.macrobenchmark" />
\ No newline at end of file
diff --git a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
index d1d93f4..1c4df6df 100644
--- a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
+++ b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Card.kt
@@ -112,7 +112,7 @@
                 .matchParentSize()
                 .paint(
                     painter = backgroundPainter,
-                    contentScale = ContentScale.FillBounds
+                    contentScale = ContentScale.Crop
                 )
 
         val contentBoxModifier = Modifier
diff --git a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
index 5dff26b..c24ec40 100644
--- a/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
+++ b/wear/compose/material/src/commonMain/kotlin/androidx/wear/compose/material/Chip.kt
@@ -114,7 +114,7 @@
             Modifier
                 .paint(
                     painter = colors.background(enabled = enabled).value,
-                    contentScale = ContentScale.FillBounds
+                    contentScale = ContentScale.Crop
                 )
                 .fillMaxSize()
 
diff --git a/wear/wear-input-testing/api/1.1.0-beta01.txt b/wear/wear-input-testing/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..154d309
--- /dev/null
+++ b/wear/wear-input-testing/api/1.1.0-beta01.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/public_plus_experimental_1.1.0-beta01.txt b/wear/wear-input-testing/api/public_plus_experimental_1.1.0-beta01.txt
new file mode 100644
index 0000000..154d309
--- /dev/null
+++ b/wear/wear-input-testing/api/public_plus_experimental_1.1.0-beta01.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input-testing/api/res-1.1.0-beta01.txt b/wear/wear-input-testing/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input-testing/api/res-1.1.0-beta01.txt
diff --git a/wear/wear-input-testing/api/restricted_1.1.0-beta01.txt b/wear/wear-input-testing/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..154d309
--- /dev/null
+++ b/wear/wear-input-testing/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,18 @@
+// Signature format: 4.0
+package androidx.wear.input.testing {
+
+  public class TestWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public TestWearableButtonsProvider(java.util.Map<java.lang.Integer!,androidx.wear.input.testing.TestWearableButtonsProvider.TestWearableButtonLocation!>);
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  public static class TestWearableButtonsProvider.TestWearableButtonLocation {
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float);
+    ctor public TestWearableButtonsProvider.TestWearableButtonLocation(float, float, float, float);
+    method public android.graphics.PointF getLocation();
+    method public android.graphics.PointF? getRotatedLocation();
+  }
+
+}
+
diff --git a/wear/wear-input/api/1.1.0-beta01.txt b/wear/wear-input/api/1.1.0-beta01.txt
new file mode 100644
index 0000000..5909312
--- /dev/null
+++ b/wear/wear-input/api/1.1.0-beta01.txt
@@ -0,0 +1,67 @@
+// Signature format: 4.0
+package androidx.wear.input {
+
+  public final class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
+    method public static android.content.Intent createActionRemoteInputIntent();
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
+    method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public static boolean isActionRemoteInput(android.content.Intent intent);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+    field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
+  }
+
+  public static final class RemoteInputIntentHelper.Companion {
+    method public android.content.Intent createActionRemoteInputIntent();
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
+    method public boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public boolean isActionRemoteInput(android.content.Intent intent);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence? getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/public_plus_experimental_1.1.0-beta01.txt b/wear/wear-input/api/public_plus_experimental_1.1.0-beta01.txt
new file mode 100644
index 0000000..5909312
--- /dev/null
+++ b/wear/wear-input/api/public_plus_experimental_1.1.0-beta01.txt
@@ -0,0 +1,67 @@
+// Signature format: 4.0
+package androidx.wear.input {
+
+  public final class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
+    method public static android.content.Intent createActionRemoteInputIntent();
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
+    method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public static boolean isActionRemoteInput(android.content.Intent intent);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+    field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
+  }
+
+  public static final class RemoteInputIntentHelper.Companion {
+    method public android.content.Intent createActionRemoteInputIntent();
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
+    method public boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public boolean isActionRemoteInput(android.content.Intent intent);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence? getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-input/api/res-1.1.0-beta01.txt b/wear/wear-input/api/res-1.1.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-input/api/res-1.1.0-beta01.txt
diff --git a/wear/wear-input/api/restricted_1.1.0-beta01.txt b/wear/wear-input/api/restricted_1.1.0-beta01.txt
new file mode 100644
index 0000000..5909312
--- /dev/null
+++ b/wear/wear-input/api/restricted_1.1.0-beta01.txt
@@ -0,0 +1,67 @@
+// Signature format: 4.0
+package androidx.wear.input {
+
+  public final class DeviceWearableButtonsProvider implements androidx.wear.input.WearableButtonsProvider {
+    ctor public DeviceWearableButtonsProvider();
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+  @RequiresApi(android.os.Build.VERSION_CODES.N) public final class RemoteInputIntentHelper {
+    method public static android.content.Intent createActionRemoteInputIntent();
+    method public static CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public static CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public static java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public static java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public static CharSequence? getTitleExtra(android.content.Intent intent);
+    method public static boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public static boolean isActionRemoteInput(android.content.Intent intent);
+    method public static android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public static android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public static android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public static android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+    field public static final androidx.wear.input.RemoteInputIntentHelper.Companion Companion;
+  }
+
+  public static final class RemoteInputIntentHelper.Companion {
+    method public android.content.Intent createActionRemoteInputIntent();
+    method public CharSequence? getCancelLabelExtra(android.content.Intent intent);
+    method public CharSequence? getConfirmLabelExtra(android.content.Intent intent);
+    method public CharSequence? getInProgressLabelExtra(android.content.Intent intent);
+    method public java.util.List<android.app.RemoteInput>? getRemoteInputsExtra(android.content.Intent intent);
+    method public java.util.List<java.lang.CharSequence>? getSmartReplyContextExtra(android.content.Intent intent);
+    method public CharSequence? getTitleExtra(android.content.Intent intent);
+    method public boolean hasRemoteInputsExtra(android.content.Intent intent);
+    method public boolean isActionRemoteInput(android.content.Intent intent);
+    method public android.content.Intent putCancelLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putConfirmLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putInProgressLabelExtra(android.content.Intent intent, CharSequence label);
+    method public android.content.Intent putRemoteInputsExtra(android.content.Intent intent, java.util.List<android.app.RemoteInput> remoteInputs);
+    method public android.content.Intent putSmartReplyContextExtra(android.content.Intent intent, java.util.List<? extends java.lang.CharSequence> smartReplyContext);
+    method public android.content.Intent putTitleExtra(android.content.Intent intent, CharSequence title);
+  }
+
+  public final class WearableButtons {
+    method public static int getButtonCount(android.content.Context);
+    method public static android.graphics.drawable.Drawable? getButtonIcon(android.content.Context, int);
+    method public static androidx.wear.input.WearableButtons.ButtonInfo? getButtonInfo(android.content.Context, int);
+    method public static CharSequence? getButtonLabel(android.content.Context, int);
+  }
+
+  public static final class WearableButtons.ButtonInfo {
+    method public int getKeycode();
+    method public int getLocationZone();
+    method public float getX();
+    method public float getY();
+  }
+
+  public interface WearableButtonsProvider {
+    method public int[]? getAvailableButtonKeyCodes(android.content.Context);
+    method public android.os.Bundle getButtonInfo(android.content.Context, int);
+  }
+
+}
+
diff --git a/wear/wear-ongoing/api/1.0.0-beta01.txt b/wear/wear-ongoing/api/1.0.0-beta01.txt
new file mode 100644
index 0000000..9eb1123
--- /dev/null
+++ b/wear/wear-ongoing/api/1.0.0-beta01.txt
@@ -0,0 +1,100 @@
+// Signature format: 4.0
+package androidx.wear.ongoing {
+
+  @RequiresApi(24) public final class OngoingActivity {
+    method public void apply(android.content.Context);
+    method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
+    method public androidx.core.content.LocusIdCompat? getLocusId();
+    method public int getNotificationId();
+    method public int getOngoingActivityId();
+    method public android.graphics.drawable.Icon getStaticIcon();
+    method public androidx.wear.ongoing.Status? getStatus();
+    method public String? getTag();
+    method public long getTimestamp();
+    method public String? getTitle();
+    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
+  }
+
+  public static final class OngoingActivity.Builder {
+    ctor public OngoingActivity.Builder(android.content.Context, int, androidx.core.app.NotificationCompat.Builder);
+    ctor public OngoingActivity.Builder(android.content.Context, String, int, androidx.core.app.NotificationCompat.Builder);
+    method public androidx.wear.ongoing.OngoingActivity build();
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setCategory(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTitle(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
+  }
+
+  public class SerializationHelper {
+    method public static void copy(android.os.Bundle, android.os.Bundle);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
+    method public static boolean hasOngoingActivity(android.app.Notification);
+  }
+
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
+    method public long getNextChangeTimeMillis(long);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
+    method public java.util.Set<java.lang.String!> getPartNames();
+    method public java.util.List<java.lang.CharSequence!> getTemplates();
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
+  }
+
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
+  }
+
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
+    method public long getNextChangeTimeMillis(long);
+    method public long getPausedAtMillis();
+    method public CharSequence getText(android.content.Context, long);
+    method public long getTimeZeroMillis();
+    method public long getTotalDurationMillis();
+    method public boolean hasTotalDuration();
+    method public boolean isCountDown();
+    method public boolean isPaused();
+  }
+
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+}
+
diff --git a/wear/wear-ongoing/api/public_plus_experimental_1.0.0-beta01.txt b/wear/wear-ongoing/api/public_plus_experimental_1.0.0-beta01.txt
new file mode 100644
index 0000000..9eb1123
--- /dev/null
+++ b/wear/wear-ongoing/api/public_plus_experimental_1.0.0-beta01.txt
@@ -0,0 +1,100 @@
+// Signature format: 4.0
+package androidx.wear.ongoing {
+
+  @RequiresApi(24) public final class OngoingActivity {
+    method public void apply(android.content.Context);
+    method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
+    method public androidx.core.content.LocusIdCompat? getLocusId();
+    method public int getNotificationId();
+    method public int getOngoingActivityId();
+    method public android.graphics.drawable.Icon getStaticIcon();
+    method public androidx.wear.ongoing.Status? getStatus();
+    method public String? getTag();
+    method public long getTimestamp();
+    method public String? getTitle();
+    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
+  }
+
+  public static final class OngoingActivity.Builder {
+    ctor public OngoingActivity.Builder(android.content.Context, int, androidx.core.app.NotificationCompat.Builder);
+    ctor public OngoingActivity.Builder(android.content.Context, String, int, androidx.core.app.NotificationCompat.Builder);
+    method public androidx.wear.ongoing.OngoingActivity build();
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setCategory(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTitle(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
+  }
+
+  public class SerializationHelper {
+    method public static void copy(android.os.Bundle, android.os.Bundle);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
+    method public static boolean hasOngoingActivity(android.app.Notification);
+  }
+
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
+    method public long getNextChangeTimeMillis(long);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
+    method public java.util.Set<java.lang.String!> getPartNames();
+    method public java.util.List<java.lang.CharSequence!> getTemplates();
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
+  }
+
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
+  }
+
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
+    method public long getNextChangeTimeMillis(long);
+    method public long getPausedAtMillis();
+    method public CharSequence getText(android.content.Context, long);
+    method public long getTimeZeroMillis();
+    method public long getTotalDurationMillis();
+    method public boolean hasTotalDuration();
+    method public boolean isCountDown();
+    method public boolean isPaused();
+  }
+
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+}
+
diff --git a/wear/wear-ongoing/api/res-1.0.0-beta01.txt b/wear/wear-ongoing/api/res-1.0.0-beta01.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/wear/wear-ongoing/api/res-1.0.0-beta01.txt
diff --git a/wear/wear-ongoing/api/restricted_1.0.0-beta01.txt b/wear/wear-ongoing/api/restricted_1.0.0-beta01.txt
new file mode 100644
index 0000000..9eb1123
--- /dev/null
+++ b/wear/wear-ongoing/api/restricted_1.0.0-beta01.txt
@@ -0,0 +1,100 @@
+// Signature format: 4.0
+package androidx.wear.ongoing {
+
+  @RequiresApi(24) public final class OngoingActivity {
+    method public void apply(android.content.Context);
+    method public android.graphics.drawable.Icon? getAnimatedIcon();
+    method public String? getCategory();
+    method public androidx.core.content.LocusIdCompat? getLocusId();
+    method public int getNotificationId();
+    method public int getOngoingActivityId();
+    method public android.graphics.drawable.Icon getStaticIcon();
+    method public androidx.wear.ongoing.Status? getStatus();
+    method public String? getTag();
+    method public long getTimestamp();
+    method public String? getTitle();
+    method public android.app.PendingIntent getTouchIntent();
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, java.util.function.Predicate<androidx.wear.ongoing.OngoingActivity!>);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context);
+    method public static androidx.wear.ongoing.OngoingActivity? recoverOngoingActivity(android.content.Context, int);
+    method public void update(android.content.Context, androidx.wear.ongoing.Status);
+  }
+
+  public static final class OngoingActivity.Builder {
+    ctor public OngoingActivity.Builder(android.content.Context, int, androidx.core.app.NotificationCompat.Builder);
+    ctor public OngoingActivity.Builder(android.content.Context, String, int, androidx.core.app.NotificationCompat.Builder);
+    method public androidx.wear.ongoing.OngoingActivity build();
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setAnimatedIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setCategory(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setLocusId(androidx.core.content.LocusIdCompat);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setOngoingActivityId(int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(android.graphics.drawable.Icon);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStaticIcon(@DrawableRes int);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setStatus(androidx.wear.ongoing.Status);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTitle(String);
+    method public androidx.wear.ongoing.OngoingActivity.Builder setTouchIntent(android.app.PendingIntent);
+  }
+
+  public class SerializationHelper {
+    method public static void copy(android.os.Bundle, android.os.Bundle);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.app.Notification);
+    method public static androidx.wear.ongoing.OngoingActivity? create(android.os.Bundle);
+    method public static boolean hasOngoingActivity(android.app.Notification);
+  }
+
+  public final class Status implements androidx.wear.ongoing.TimeDependentText {
+    method public static androidx.wear.ongoing.Status forPart(androidx.wear.ongoing.Status.Part);
+    method public long getNextChangeTimeMillis(long);
+    method public androidx.wear.ongoing.Status.Part? getPart(String);
+    method public java.util.Set<java.lang.String!> getPartNames();
+    method public java.util.List<java.lang.CharSequence!> getTemplates();
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public static final class Status.Builder {
+    ctor public Status.Builder();
+    method public androidx.wear.ongoing.Status.Builder addPart(String, androidx.wear.ongoing.Status.Part);
+    method public androidx.wear.ongoing.Status.Builder addTemplate(CharSequence);
+    method public androidx.wear.ongoing.Status build();
+  }
+
+  public abstract static class Status.Part implements androidx.wear.ongoing.TimeDependentText {
+  }
+
+  public static final class Status.StopwatchPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.StopwatchPart(long, long, long);
+    ctor public Status.StopwatchPart(long, long);
+    ctor public Status.StopwatchPart(long);
+  }
+
+  public static final class Status.TextPart extends androidx.wear.ongoing.Status.Part {
+    ctor public Status.TextPart(String);
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+  public abstract static class Status.TimerOrStopwatchPart extends androidx.wear.ongoing.Status.Part {
+    method public long getNextChangeTimeMillis(long);
+    method public long getPausedAtMillis();
+    method public CharSequence getText(android.content.Context, long);
+    method public long getTimeZeroMillis();
+    method public long getTotalDurationMillis();
+    method public boolean hasTotalDuration();
+    method public boolean isCountDown();
+    method public boolean isPaused();
+  }
+
+  public static final class Status.TimerPart extends androidx.wear.ongoing.Status.TimerOrStopwatchPart {
+    ctor public Status.TimerPart(long, long, long);
+    ctor public Status.TimerPart(long, long);
+    ctor public Status.TimerPart(long);
+  }
+
+  public interface TimeDependentText {
+    method public long getNextChangeTimeMillis(long);
+    method public CharSequence getText(android.content.Context, long);
+  }
+
+}
+
diff --git a/wear/wear-phone-interactions/api/current.txt b/wear/wear-phone-interactions/api/current.txt
index 310f3a1..6db66ac 100644
--- a/wear/wear-phone-interactions/api/current.txt
+++ b/wear/wear-phone-interactions/api/current.txt
@@ -64,7 +64,7 @@
     method @UiThread public void close();
     method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
     method protected void finalize();
-    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, java.util.concurrent.Executor executor, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
     field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
     field public static final int ERROR_PHONE_UNAVAILABLE = 1; // 0x1
     field public static final int ERROR_UNSUPPORTED = 0; // 0x0
@@ -73,7 +73,7 @@
 
   public abstract static class RemoteAuthClient.Callback {
     ctor public RemoteAuthClient.Callback();
-    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationError(androidx.wear.phone.interactions.authentication.OAuthRequest request, int errorCode);
     method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
   }
 
@@ -130,9 +130,9 @@
     method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
   }
 
-  public final class BridgingManagerServiceBinder {
-    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
-    method public android.os.IBinder? getBinder();
+  public final class BridgingManagerService extends android.app.Service {
+    ctor public BridgingManagerService(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? onBind(android.content.Intent? intent);
   }
 
 }
diff --git a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
index 310f3a1..6db66ac 100644
--- a/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
+++ b/wear/wear-phone-interactions/api/public_plus_experimental_current.txt
@@ -64,7 +64,7 @@
     method @UiThread public void close();
     method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
     method protected void finalize();
-    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, java.util.concurrent.Executor executor, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
     field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
     field public static final int ERROR_PHONE_UNAVAILABLE = 1; // 0x1
     field public static final int ERROR_UNSUPPORTED = 0; // 0x0
@@ -73,7 +73,7 @@
 
   public abstract static class RemoteAuthClient.Callback {
     ctor public RemoteAuthClient.Callback();
-    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationError(androidx.wear.phone.interactions.authentication.OAuthRequest request, int errorCode);
     method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
   }
 
@@ -130,9 +130,9 @@
     method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
   }
 
-  public final class BridgingManagerServiceBinder {
-    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
-    method public android.os.IBinder? getBinder();
+  public final class BridgingManagerService extends android.app.Service {
+    ctor public BridgingManagerService(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? onBind(android.content.Intent? intent);
   }
 
 }
diff --git a/wear/wear-phone-interactions/api/restricted_current.txt b/wear/wear-phone-interactions/api/restricted_current.txt
index 310f3a1..6db66ac 100644
--- a/wear/wear-phone-interactions/api/restricted_current.txt
+++ b/wear/wear-phone-interactions/api/restricted_current.txt
@@ -64,7 +64,7 @@
     method @UiThread public void close();
     method public static androidx.wear.phone.interactions.authentication.RemoteAuthClient create(android.content.Context context);
     method protected void finalize();
-    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
+    method @UiThread public void sendAuthorizationRequest(androidx.wear.phone.interactions.authentication.OAuthRequest request, java.util.concurrent.Executor executor, androidx.wear.phone.interactions.authentication.RemoteAuthClient.Callback clientCallback);
     field public static final androidx.wear.phone.interactions.authentication.RemoteAuthClient.Companion Companion;
     field public static final int ERROR_PHONE_UNAVAILABLE = 1; // 0x1
     field public static final int ERROR_UNSUPPORTED = 0; // 0x0
@@ -73,7 +73,7 @@
 
   public abstract static class RemoteAuthClient.Callback {
     ctor public RemoteAuthClient.Callback();
-    method @UiThread public abstract void onAuthorizationError(int errorCode);
+    method @UiThread public abstract void onAuthorizationError(androidx.wear.phone.interactions.authentication.OAuthRequest request, int errorCode);
     method @UiThread public abstract void onAuthorizationResponse(androidx.wear.phone.interactions.authentication.OAuthRequest request, androidx.wear.phone.interactions.authentication.OAuthResponse response);
   }
 
@@ -130,9 +130,9 @@
     method public androidx.wear.phone.interactions.notifications.BridgingManager fromContext(android.content.Context context);
   }
 
-  public final class BridgingManagerServiceBinder {
-    ctor public BridgingManagerServiceBinder(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
-    method public android.os.IBinder? getBinder();
+  public final class BridgingManagerService extends android.app.Service {
+    ctor public BridgingManagerService(android.content.Context context, androidx.wear.phone.interactions.notifications.BridgingConfigurationHandler bridgingConfigurationHandler);
+    method public android.os.IBinder? onBind(android.content.Intent? intent);
   }
 
 }
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
index 6dd4162..842e9ac 100644
--- a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthClient.kt
@@ -65,6 +65,7 @@
  *          .setAuthProviderUrl(Uri.parse("https://...."))
  *          .setCodeChallenge(CodeChallenge(codeVerifier))
  *          .build(),
+ *      Executors.newSingleThreadExecutor()
  *      new MyAuthCallback()
  *   );
  * }
@@ -80,7 +81,7 @@
  *     ...
  *   }
  *
- *   override public fun onAuthorizationError(errorCode: int) {
+ *   override public fun onAuthorizationError(request: OAuthRequest, errorCode: int) {
  *     // Compare against codes available in RemoteAuthClient.ErrorCode
  *     // You'll also want to display an error UI.
  *     ...
@@ -205,7 +206,7 @@
          * see [sendAuthorizationRequest]
          */
         @UiThread
-        public abstract fun onAuthorizationError(@ErrorCode errorCode: Int)
+        public abstract fun onAuthorizationError(request: OAuthRequest, @ErrorCode errorCode: Int)
     }
 
     /**
@@ -215,13 +216,18 @@
      * completes.
      *
      * @param request Request that will be sent to the phone. The auth response should redirect
-     * to the Wear OS companion. See [WEAR_REDIRECT_URL_PREFIX]
+     * to the Wear OS companion. See [OAuthRequest.WEAR_REDIRECT_URL_PREFIX]
+     * @param executor The executor that callback will called on.
+     * @param clientCallback The callback that will be notified when request is completed.
      *
      * @Throws RuntimeException if the service has error to open the request
      */
     @UiThread
-    @SuppressLint("ExecutorRegistration")
-    public fun sendAuthorizationRequest(request: OAuthRequest, clientCallback: Callback) {
+    public fun sendAuthorizationRequest(
+        request: OAuthRequest,
+        executor: Executor,
+        clientCallback: Callback
+    ) {
         require(packageName == request.getPackageName()) {
             "The request's package name is different from the auth client's package name."
         }
@@ -229,18 +235,16 @@
         if (connectionState == STATE_DISCONNECTED) {
             connect()
         }
-        whenConnected(
-            Runnable {
-                val callback = RequestCallback(request, clientCallback)
-                outstandingRequests.add(callback)
-                try {
-                    service!!.openUrl(request.toBundle(), callback)
-                } catch (e: Exception) {
-                    removePendingCallback(callback)
-                    throw RuntimeException(e)
-                }
+        whenConnected {
+            val callback = RequestCallback(request, clientCallback, executor)
+            outstandingRequests.add(callback)
+            try {
+                service!!.openUrl(request.toBundle(), callback)
+            } catch (e: Exception) {
+                removePendingCallback(callback)
+                throw RuntimeException(e)
             }
-        )
+        }
     }
 
     /**
@@ -320,7 +324,8 @@
     /** Receives results of async requests to the remote auth service.  */
     internal inner class RequestCallback internal constructor(
         private val request: OAuthRequest,
-        private val clientCallback: Callback
+        private val clientCallback: Callback,
+        private val executor: Executor
     ) : IAuthenticationRequestCallback.Stub() {
 
         override fun getApiVersion(): Int = IAuthenticationRequestCallback.API_VERSION
@@ -345,9 +350,13 @@
                 Runnable {
                     removePendingCallback(this@RequestCallback)
                     if (error == NO_ERROR) {
-                        clientCallback.onAuthorizationResponse(request, response)
+                        executor.execute {
+                            clientCallback.onAuthorizationResponse(request, response)
+                        }
                     } else {
-                        clientCallback.onAuthorizationError(response.getErrorCode())
+                        executor.execute {
+                            clientCallback.onAuthorizationError(request, response.getErrorCode())
+                        }
                     }
                 }
             )
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
index dadd75b..cadc0c3 100644
--- a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/authentication/RemoteAuthService.kt
@@ -46,8 +46,8 @@
     /**
      * Handle the auth request by sending it to the phone.
      * Typically, if the paired phone is not connected, send a response with error code of
-     * ERROR_PHONE_UNAVAILABLE; otherwise listening for the response from the phone and send it
-     * back to the 3p app.
+     * [RemoteAuthClient.ERROR_PHONE_UNAVAILABLE]; otherwise listening for the response from the
+     * phone and send it back to the 3p app.
      *
      * [RemoteAuthService.sendResponseToCallback] is provided for sending response back to the
      * callback provided by the 3p app.
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt
index 7f8b241..6b07b6b 100644
--- a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManager.kt
@@ -121,7 +121,7 @@
     }
 
     public companion object {
-        private const val ACTION_BIND_BRIDGING_MANAGER =
+        internal const val ACTION_BIND_BRIDGING_MANAGER =
             "android.support.wearable.notifications.action.BIND_BRIDGING_MANAGER"
 
         private const val BRIDGING_CONFIG_SERVICE_PACKAGE = "com.google.android.wearable.app"
diff --git a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerService.kt
similarity index 79%
rename from wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt
rename to wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerService.kt
index 8961404..79e8336 100644
--- a/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerServiceBinder.kt
+++ b/wear/wear-phone-interactions/src/main/java/androidx/wear/phone/interactions/notifications/BridgingManagerService.kt
@@ -18,6 +18,7 @@
 
 import android.app.Service
 import android.content.Context
+import android.content.Intent
 import android.os.Binder
 import android.os.Bundle
 import android.os.IBinder
@@ -35,19 +36,21 @@
 }
 
 /**
- * Class for providing the binder that clients used to communicate with the service regarding
- * notification bridging configurations.
+ * Service class receiving notification bridging configurations.
+ *
+ * @param context  The [Context] of the application.
+ * @param bridgingConfigurationHandler The handler for applying the notification bridging
+ * configuration.
  */
-public class BridgingManagerServiceBinder(
+public class BridgingManagerService(
     private val context: Context,
     private val bridgingConfigurationHandler: BridgingConfigurationHandler
-) {
-    /**
-     * Call this method in [Service.onBind] to provide the interface that clients
-     * use to communicate with the service by returning an IBinder.
-     */
-    public fun getBinder(): IBinder? =
-        BridgingManagerServiceImpl(context, bridgingConfigurationHandler)
+) : Service() {
+    override fun onBind(intent: Intent?): IBinder? =
+        if (intent?.action == BridgingManager.ACTION_BIND_BRIDGING_MANAGER)
+            BridgingManagerServiceImpl(context, bridgingConfigurationHandler)
+        else
+            null
 }
 
 internal class BridgingManagerServiceImpl(
diff --git a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
index 8d8bf82..4b2ec78 100644
--- a/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
+++ b/wear/wear-phone-interactions/src/test/java/androidx/wear/phone/interactions/authentication/RemoteAuthTest.kt
@@ -69,6 +69,7 @@
     private var fakeService: FakeClockworkHomeAuthService = FakeClockworkHomeAuthService()
     private var clientUnderTest: RemoteAuthClient =
         RemoteAuthClient(fakeServiceBinder, DIRECT_EXECUTOR, appPackageName)
+    private val executor: Executor = SyncExecutor()
 
     @Test
     public fun doesntConnectUntilARequestIsMade() {
@@ -86,6 +87,7 @@
                 .setAuthProviderUrl(Uri.parse(requestUri))
                 .setCodeChallenge(CodeChallenge(CodeVerifier()))
                 .build(),
+            executor,
             mockCallback
         )
         // THEN a connection is made to Clockwork Home's Auth service
@@ -95,7 +97,7 @@
     @Test
     public fun sendAuthorizationRequestShouldCallBinderMethod() {
         // WHEN an authorization request is sent
-        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestA, executor, mockCallback)
         fakeServiceBinder.completeConnection()
         // THEN a request is made to Clockwork Home
         val request = fakeService.requests[0]
@@ -114,8 +116,8 @@
     @Test
     public fun twoQueuedAuthorizationRequestsBeforeConnectCompletes() {
         // GIVEN two authorization requests were made before connecting to Clockwork Home completes
-        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
-        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestA, executor, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestB, executor, mockCallback)
         // WHEN the connection does complete
         fakeServiceBinder.completeConnection()
         // THEN two requests are made to Clockwork Home
@@ -145,7 +147,7 @@
     @Throws(RemoteException::class)
     public fun requestCompletionShouldCallBackToClient() {
         // GIVEN an authorization request was sent
-        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestA, executor, mockCallback)
         fakeServiceBinder.completeConnection()
         val request = fakeService.requests[0]
         // WHEN the request completes
@@ -157,10 +159,10 @@
     @Throws(RemoteException::class)
     public fun doesntDisconnectWhenRequestStillInProgress() {
         // GIVEN 2 authorization requests were sent
-        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestA, executor, mockCallback)
         // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
         fakeServiceBinder.completeConnection()
-        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestB, executor, mockCallback)
         // WHEN the first one completes
         RemoteAuthService.sendResponseToCallback(
             response,
@@ -175,10 +177,10 @@
     @Throws(RemoteException::class)
     public fun disconnectsWhenAllRequestsComplete() {
         // GIVEN 2 authorization requests were sent
-        clientUnderTest.sendAuthorizationRequest(requestA, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestA, executor, mockCallback)
         // GIVEN the async binding to Clockwork Home completed after the 1st but before the 2nd
         fakeServiceBinder.completeConnection()
-        clientUnderTest.sendAuthorizationRequest(requestB, mockCallback)
+        clientUnderTest.sendAuthorizationRequest(requestB, executor, mockCallback)
         RemoteAuthService.sendResponseToCallback(
             response,
             fakeService.requests[0].second
@@ -271,3 +273,9 @@
         }
     }
 }
+
+private class SyncExecutor : Executor {
+    override fun execute(command: Runnable?) {
+        command?.run()
+    }
+}
diff --git a/wear/wear-watchface-complications-rendering/api/current.txt b/wear/wear-watchface-complications-rendering/api/current.txt
index 897ab96..f8923f6 100644
--- a/wear/wear-watchface-complications-rendering/api/current.txt
+++ b/wear/wear-watchface-complications-rendering/api/current.txt
@@ -6,13 +6,10 @@
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public boolean isHighlighted();
     method @CallSuper public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
-    property public boolean isHighlighted;
   }
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
@@ -145,11 +142,11 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.ComplicationSlot complicationSlot, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
-    method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public androidx.wear.watchface.ComplicationSlot getComplicationSlot();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final androidx.wear.watchface.ComplicationSlot complicationSlot;
   }
 
 }
diff --git a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
index 897ab96..f8923f6 100644
--- a/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/public_plus_experimental_current.txt
@@ -6,13 +6,10 @@
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public boolean isHighlighted();
     method @CallSuper public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
-    property public boolean isHighlighted;
   }
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
@@ -145,11 +142,11 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.ComplicationSlot complicationSlot, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
-    method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public androidx.wear.watchface.ComplicationSlot getComplicationSlot();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final androidx.wear.watchface.ComplicationSlot complicationSlot;
   }
 
 }
diff --git a/wear/wear-watchface-complications-rendering/api/restricted_current.txt b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
index a1772fa..8781006 100644
--- a/wear/wear-watchface-complications-rendering/api/restricted_current.txt
+++ b/wear/wear-watchface-complications-rendering/api/restricted_current.txt
@@ -6,13 +6,10 @@
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
     method public final androidx.wear.watchface.complications.rendering.ComplicationDrawable getDrawable();
-    method public boolean isHighlighted();
     method @CallSuper public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
-    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
+    method public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
     method public final void setDrawable(androidx.wear.watchface.complications.rendering.ComplicationDrawable value);
-    method public void setHighlighted(boolean value);
     property public final androidx.wear.watchface.complications.rendering.ComplicationDrawable drawable;
-    property public boolean isHighlighted;
   }
 
   public final class ComplicationDrawable extends android.graphics.drawable.Drawable {
@@ -148,11 +145,11 @@
   }
 
   public final class GlesTextureComplication {
-    ctor public GlesTextureComplication(androidx.wear.watchface.CanvasComplication canvasComplication, @Px int textureWidth, @Px int textureHeight, int textureType);
+    ctor public GlesTextureComplication(androidx.wear.watchface.ComplicationSlot complicationSlot, @Px int textureWidth, @Px int textureHeight, int textureType);
     method public void bind();
-    method public androidx.wear.watchface.CanvasComplication getCanvasComplication();
+    method public androidx.wear.watchface.ComplicationSlot getComplicationSlot();
     method public void renderToTexture(android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    property public final androidx.wear.watchface.CanvasComplication canvasComplication;
+    property public final androidx.wear.watchface.ComplicationSlot complicationSlot;
   }
 
 }
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
index fc971bc..dc02aaf 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/CanvasComplicationDrawable.kt
@@ -28,6 +28,7 @@
 import androidx.wear.complications.data.ComplicationData
 import androidx.wear.utility.TraceEvent
 import androidx.wear.watchface.CanvasComplication
+import androidx.wear.watchface.DrawMode
 import androidx.wear.watchface.RenderParameters
 import androidx.wear.watchface.WatchState
 import androidx.wear.watchface.data.ComplicationSlotBoundsType
@@ -93,31 +94,25 @@
             // update.
             value.setComplicationData(field.complicationData, false)
             field = value
-            value.isInAmbientMode = watchState.isAmbient.value
             value.isLowBitAmbient = watchState.hasLowBitAmbient
             value.isBurnInProtectionOn = watchState.hasBurnInProtection
         }
 
-    init {
-        // This observer needs to use the property drawable defined above, not the constructor
-        // argument with the same name.
-        watchState.isAmbient.addObserver {
-            this.drawable.isInAmbientMode = it
-        }
-    }
-
     override fun render(
         canvas: Canvas,
         bounds: Rect,
         calendar: Calendar,
-        renderParameters: RenderParameters
+        renderParameters: RenderParameters,
+        slotId: Int
     ) {
         if (!renderParameters.watchFaceLayers.contains(WatchFaceLayer.COMPLICATIONS)) {
             return
         }
 
+        drawable.isInAmbientMode = renderParameters.drawMode == DrawMode.AMBIENT
         drawable.bounds = bounds
         drawable.currentTimeMillis = calendar.timeInMillis
+        drawable.isHighlighted = renderParameters.pressedComplicationSlotIds.contains(slotId)
         drawable.draw(canvas)
     }
 
@@ -137,12 +132,6 @@
         }
     }
 
-    public override var isHighlighted: Boolean
-        get() = drawable.isHighlighted
-        set(value) {
-            drawable.isHighlighted = value
-        }
-
     private var _data: ComplicationData? = null
 
     /** Returns the [ComplicationData] to render with. */
@@ -170,4 +159,4 @@
             loadDrawablesAsynchronous
         )
     }
-}
\ No newline at end of file
+}
diff --git a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/GlesTextureComplication.kt b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/GlesTextureComplication.kt
index 3c5287b..c7b9a45 100644
--- a/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/GlesTextureComplication.kt
+++ b/wear/wear-watchface-complications-rendering/src/main/java/androidx/wear/watchface/complications/rendering/GlesTextureComplication.kt
@@ -24,20 +24,20 @@
 import android.opengl.GLES20
 import android.opengl.GLUtils
 import androidx.annotation.Px
-import androidx.wear.watchface.CanvasComplication
+import androidx.wear.watchface.ComplicationSlot
 import androidx.wear.watchface.RenderParameters
 
 /**
- * Helper for rendering a [CanvasComplication] to a GLES20 texture. To use call [renderToTexture]
+ * Helper for rendering a [ComplicationSlot] to a GLES20 texture. To use call [renderToTexture]
  * and then [bind] before drawing.
  *
- * @param canvasComplication The [CanvasComplication] to render to texture.
+ * @param complicationSlot The [ComplicationSlot] to render to texture.
  * @param textureWidth The width of the texture in pixels to create.
  * @param textureHeight The height of the texture in pixels to create.
  * @param textureType The texture type, e.g. [GLES20.GL_TEXTURE_2D].
  */
 public class GlesTextureComplication(
-    public val canvasComplication: CanvasComplication,
+    public val complicationSlot: ComplicationSlot,
     @Px textureWidth: Int,
     @Px textureHeight: Int,
     private val textureType: Int
@@ -51,10 +51,16 @@
     private val canvas = Canvas(bitmap)
     private val bounds = Rect(0, 0, textureWidth, textureHeight)
 
-    /** Renders [canvasComplication] to an OpenGL texture. */
+    /** Renders [complicationSlot] to an OpenGL texture. */
     public fun renderToTexture(calendar: Calendar, renderParameters: RenderParameters) {
         canvas.drawColor(Color.BLACK)
-        canvasComplication.render(canvas, bounds, calendar, renderParameters)
+        complicationSlot.renderer.render(
+            canvas,
+            bounds,
+            calendar,
+            renderParameters,
+            complicationSlot.id
+        )
         bind()
         GLUtils.texImage2D(textureType, 0, bitmap, 0)
     }
diff --git a/wear/wear-watchface-data/api/restricted_current.txt b/wear/wear-watchface-data/api/restricted_current.txt
index f0b0b20..0989e7d 100644
--- a/wear/wear-watchface-data/api/restricted_current.txt
+++ b/wear/wear-watchface-data/api/restricted_current.txt
@@ -301,7 +301,7 @@
   }
 
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @androidx.versionedparcelable.VersionedParcelize public class RenderParametersWireFormat implements android.os.Parcelable androidx.versionedparcelable.VersionedParcelable {
-    ctor public RenderParametersWireFormat(int, int, int, int, String?, @ColorInt int, @ColorInt int);
+    ctor public RenderParametersWireFormat(int, int, int, int, String?, @ColorInt int, @ColorInt int, int[]);
     method public int describeContents();
     method @ColorInt public int getBackgroundTint();
     method public int getDrawMode();
@@ -309,6 +309,7 @@
     method public int getElementType();
     method public String? getElementUserStyleSettingId();
     method @ColorInt public int getHighlightTint();
+    method public int[] getPressedComplicationSlotIds();
     method public int getWatchFaceLayerSetBitfield();
     method public void writeToParcel(android.os.Parcel, int);
     field public static final android.os.Parcelable.Creator<androidx.wear.watchface.data.RenderParametersWireFormat!>! CREATOR;
diff --git a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/RenderParametersWireFormat.java b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/RenderParametersWireFormat.java
index 03f3529..59359aa 100644
--- a/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/RenderParametersWireFormat.java
+++ b/wear/wear-watchface-data/src/main/java/androidx/wear/watchface/data/RenderParametersWireFormat.java
@@ -108,6 +108,11 @@
     @ColorInt
     int mBackgroundTint;
 
+    /** Optional set of ComplicationSlots to render as pressed. */
+    @ParcelField(8)
+    @NonNull
+    int[] mPressedComplicationSlotIds = new int[0];
+
     RenderParametersWireFormat() {
     }
 
@@ -118,7 +123,8 @@
             int complicationSlotId,
             @Nullable String elementUserStyleSettingId,
             @ColorInt int highlightTint,
-            @ColorInt int backgroundTint) {
+            @ColorInt int backgroundTint,
+            @NonNull int[] pressedComplicationSlotIds) {
         mDrawMode = drawMode;
         mWatchFaceLayerSetBitfield = watchFaceLayerSetBitfield;
         mElementType = elementType;
@@ -126,6 +132,7 @@
         mElementUserStyleSettingId = elementUserStyleSettingId;
         mHighlightTint = highlightTint;
         mBackgroundTint = backgroundTint;
+        mPressedComplicationSlotIds = pressedComplicationSlotIds;
         if (elementType == ELEMENT_TYPE_USER_STYLE) {
             if (elementUserStyleSettingId == null) {
                 throw new IllegalArgumentException(
@@ -172,6 +179,11 @@
         return mBackgroundTint;
     }
 
+    @NonNull
+    public int[] getPressedComplicationSlotIds() {
+        return mPressedComplicationSlotIds;
+    }
+
     /** Serializes this IndicatorState to the specified {@link Parcel}. */
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 2e16536..20299ac 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -9,12 +9,9 @@
   public interface CanvasComplication {
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
-    method public boolean isHighlighted();
     method public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method @WorkerThread public default void onRendererCreated(androidx.wear.watchface.Renderer renderer);
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIsHighlighted(boolean isHighlighted);
-    property public abstract boolean isHighlighted;
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
   }
 
   public static interface CanvasComplication.InvalidateCallback {
@@ -89,8 +86,10 @@
     method public androidx.wear.watchface.ComplicationSlot? getBackgroundComplicationSlot();
     method public androidx.wear.watchface.ComplicationSlot? getComplicationSlotAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> getComplicationSlots();
+    method public java.util.Set<java.lang.Integer> getPressedSlotIds();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationSlotsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> complicationSlots;
+    property public final java.util.Set<java.lang.Integer> pressedSlotIds;
   }
 
   public static interface ComplicationSlotsManager.TapCallback {
@@ -147,13 +146,16 @@
   }
 
   public final class RenderParameters {
+    ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer, optional java.util.Set<java.lang.Integer> pressedComplicationSlotIds);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public androidx.wear.watchface.RenderParameters.HighlightLayer? getHighlightLayer();
+    method public java.util.Set<java.lang.Integer> getPressedComplicationSlotIds();
     method public java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> getWatchFaceLayers();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer;
+    property public final java.util.Set<java.lang.Integer> pressedComplicationSlotIds;
     property public final java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 2e16536..20299ac 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -9,12 +9,9 @@
   public interface CanvasComplication {
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
-    method public boolean isHighlighted();
     method public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method @WorkerThread public default void onRendererCreated(androidx.wear.watchface.Renderer renderer);
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIsHighlighted(boolean isHighlighted);
-    property public abstract boolean isHighlighted;
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
   }
 
   public static interface CanvasComplication.InvalidateCallback {
@@ -89,8 +86,10 @@
     method public androidx.wear.watchface.ComplicationSlot? getBackgroundComplicationSlot();
     method public androidx.wear.watchface.ComplicationSlot? getComplicationSlotAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> getComplicationSlots();
+    method public java.util.Set<java.lang.Integer> getPressedSlotIds();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationSlotsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> complicationSlots;
+    property public final java.util.Set<java.lang.Integer> pressedSlotIds;
   }
 
   public static interface ComplicationSlotsManager.TapCallback {
@@ -147,13 +146,16 @@
   }
 
   public final class RenderParameters {
+    ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer, optional java.util.Set<java.lang.Integer> pressedComplicationSlotIds);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public androidx.wear.watchface.RenderParameters.HighlightLayer? getHighlightLayer();
+    method public java.util.Set<java.lang.Integer> getPressedComplicationSlotIds();
     method public java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> getWatchFaceLayers();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer;
+    property public final java.util.Set<java.lang.Integer> pressedComplicationSlotIds;
     property public final java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 2263f8d..ca81607 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -35,12 +35,9 @@
   public interface CanvasComplication {
     method public void drawHighlight(android.graphics.Canvas canvas, android.graphics.Rect bounds, @androidx.wear.watchface.data.ComplicationSlotBoundsType int boundsType, android.icu.util.Calendar calendar, @ColorInt int color);
     method public androidx.wear.complications.data.ComplicationData? getData();
-    method public boolean isHighlighted();
     method public void loadData(androidx.wear.complications.data.ComplicationData? complicationData, boolean loadDrawablesAsynchronous);
     method @WorkerThread public default void onRendererCreated(androidx.wear.watchface.Renderer renderer);
-    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters);
-    method public void setIsHighlighted(boolean isHighlighted);
-    property public abstract boolean isHighlighted;
+    method @UiThread public void render(android.graphics.Canvas canvas, android.graphics.Rect bounds, android.icu.util.Calendar calendar, androidx.wear.watchface.RenderParameters renderParameters, int slotId);
   }
 
   public static interface CanvasComplication.InvalidateCallback {
@@ -138,8 +135,10 @@
     method public androidx.wear.watchface.ComplicationSlot? getBackgroundComplicationSlot();
     method public androidx.wear.watchface.ComplicationSlot? getComplicationSlotAt(@Px int x, @Px int y);
     method public java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> getComplicationSlots();
+    method public java.util.Set<java.lang.Integer> getPressedSlotIds();
     method @UiThread public void removeTapListener(androidx.wear.watchface.ComplicationSlotsManager.TapCallback tapCallback);
     property public final java.util.Map<java.lang.Integer,androidx.wear.watchface.ComplicationSlot> complicationSlots;
+    property public final java.util.Set<java.lang.Integer> pressedSlotIds;
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) @VisibleForTesting public androidx.wear.watchface.WatchState watchState;
   }
 
@@ -228,15 +227,18 @@
   }
 
   public final class RenderParameters {
+    ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer, optional java.util.Set<java.lang.Integer> pressedComplicationSlotIds);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers, optional androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer);
     ctor public RenderParameters(androidx.wear.watchface.DrawMode drawMode, java.util.Set<? extends androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers);
     ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public RenderParameters(androidx.wear.watchface.data.RenderParametersWireFormat wireFormat);
     method public androidx.wear.watchface.DrawMode getDrawMode();
     method public androidx.wear.watchface.RenderParameters.HighlightLayer? getHighlightLayer();
+    method public java.util.Set<java.lang.Integer> getPressedComplicationSlotIds();
     method public java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> getWatchFaceLayers();
     method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public androidx.wear.watchface.data.RenderParametersWireFormat toWireFormat();
     property public final androidx.wear.watchface.DrawMode drawMode;
     property public final androidx.wear.watchface.RenderParameters.HighlightLayer? highlightLayer;
+    property public final java.util.Set<java.lang.Integer> pressedComplicationSlotIds;
     property public final java.util.Set<androidx.wear.watchface.style.WatchFaceLayer> watchFaceLayers;
     field public static final androidx.wear.watchface.RenderParameters.Companion Companion;
     field public static final androidx.wear.watchface.RenderParameters DEFAULT_INTERACTIVE;
diff --git a/wear/wear-watchface/samples/app/build.gradle b/wear/wear-watchface/samples/app/build.gradle
index 6ac4a42..056b9a9 100644
--- a/wear/wear-watchface/samples/app/build.gradle
+++ b/wear/wear-watchface/samples/app/build.gradle
@@ -42,11 +42,19 @@
     }
 
     buildTypes {
-	release {
-	    minifyEnabled true
-	    shrinkResources true
-	    proguardFiles getDefaultProguardFile('proguard-android.txt')
-	}
+        release {
+            minifyEnabled true
+            shrinkResources true
+            proguardFiles getDefaultProguardFile('proguard-android.txt')
+        }
+
+        /*
+         Release and debug targets will have different package names. Package name for debug version
+         will have suffix .debug, so this should be appended to package name when i.e. uninstalling.
+         */
+        debug {
+            applicationIdSuffix ".debug"
+        }
     }
 
     compileOptions {
diff --git a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
index 64d50b6..76a4aa2 100644
--- a/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
+++ b/wear/wear-watchface/samples/src/main/java/androidx/wear/watchface/samples/ExampleOpenGLWatchFaceService.kt
@@ -337,7 +337,7 @@
         ) // up vector
 
         complicationTexture = GlesTextureComplication(
-            complicationSlot.renderer,
+            complicationSlot,
             128,
             128,
             GLES20.GL_TEXTURE_2D
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
index c718ba4..bcc5c10 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceControlServiceTest.kt
@@ -244,7 +244,7 @@
                 ComplicationRenderParams(
                     EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID,
                     RenderParameters(
-                        DrawMode.AMBIENT,
+                        DrawMode.INTERACTIVE,
                         WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
                         null,
                     ).toWireFormat(),
diff --git a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
index 87d91b8..314edbe 100644
--- a/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
+++ b/wear/wear-watchface/src/androidTest/java/androidx/wear/watchface/test/WatchFaceServiceImageTest.kt
@@ -16,6 +16,7 @@
 
 package androidx.wear.watchface.test
 
+import android.annotation.SuppressLint
 import android.app.Activity
 import android.app.PendingIntent
 import android.content.Context
@@ -533,6 +534,7 @@
         bitmap.assertAgainstGolden(screenshotRule, "ambient_screenshot2")
     }
 
+    @SuppressLint("NewApi")
     @Test
     public fun testCommandTakeScreenShot() {
         val latch = CountDownLatch(1)
@@ -562,10 +564,11 @@
         assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
         bitmap!!.assertAgainstGolden(
             screenshotRule,
-            "ambient_screenshot"
+            "testCommandTakeScreenShot"
         )
     }
 
+    @SuppressLint("NewApi")
     @Test
     public fun testCommandTakeOpenGLScreenShot() {
         val latch = CountDownLatch(1)
@@ -619,6 +622,7 @@
         bitmap.assertAgainstGolden(screenshotRule, "green_screenshot")
     }
 
+    @SuppressLint("NewApi")
     @Test
     public fun testHighlightAllComplicationsInScreenshot() {
         val latch = CountDownLatch(1)
@@ -657,6 +661,43 @@
         )
     }
 
+    @SuppressLint("NewApi")
+    @Test
+    public fun testRenderLeftComplicationPressed() {
+        val latch = CountDownLatch(1)
+
+        handler.post(this::initCanvasWatchFace)
+        assertThat(initLatch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
+        sendComplications()
+
+        var bitmap: Bitmap? = null
+        handler.post {
+            bitmap = SharedMemoryImage.ashmemReadImageBundle(
+                interactiveWatchFaceInstance.renderWatchFaceToBitmap(
+                    WatchFaceRenderParams(
+                        RenderParameters(
+                            DrawMode.INTERACTIVE,
+                            WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+                            null,
+                            setOf(EXAMPLE_CANVAS_WATCHFACE_LEFT_COMPLICATION_ID)
+                        ).toWireFormat(),
+                        123456789,
+                        null,
+                        null
+                    )
+                )
+            )
+            latch.countDown()
+        }
+
+        assertThat(latch.await(TIMEOUT_MS, TimeUnit.MILLISECONDS)).isTrue()
+        bitmap!!.assertAgainstGolden(
+            screenshotRule,
+            "left_complication_pressed"
+        )
+    }
+
+    @SuppressLint("NewApi")
     @Test
     public fun testHighlightRightComplicationInScreenshot() {
         val latch = CountDownLatch(1)
@@ -697,6 +738,7 @@
         )
     }
 
+    @SuppressLint("NewApi")
     @Test
     public fun testScreenshotWithPreviewComplicationData() {
         val latch = CountDownLatch(1)
@@ -812,6 +854,7 @@
         }
     }
 
+    @SuppressLint("NewApi")
     @Test
     public fun complicationTapLaunchesActivity() {
         handler.post(this::initCanvasWatchFace)
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
index f72ed54..da6551a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlot.kt
@@ -69,13 +69,15 @@
      * @param bounds A [Rect] describing the bounds of the complication
      * @param calendar The current [Calendar]
      * @param renderParameters The current [RenderParameters]
+     * @param slotId The Id of the [ComplicationSlot] being rendered
      */
     @UiThread
     public fun render(
         canvas: Canvas,
         bounds: Rect,
         calendar: Calendar,
-        renderParameters: RenderParameters
+        renderParameters: RenderParameters,
+        slotId: Int
     )
 
     /**
@@ -97,15 +99,6 @@
         @ColorInt color: Int
     )
 
-    /**
-     * Whether the complication should be drawn highlighted. This is to provide visual feedback when
-     * the user taps on a complication.
-     */
-    @Suppress("INAPPLICABLE_JVM_NAME") // https://stackoverflow.com/questions/47504279
-    @get:JvmName("isHighlighted")
-    @set:JvmName("setIsHighlighted")
-    public var isHighlighted: Boolean
-
     /** Returns the [ComplicationData] to render with. */
     public fun getData(): ComplicationData?
 
@@ -612,7 +605,7 @@
         renderParameters: RenderParameters
     ) {
         val bounds = computeBounds(Rect(0, 0, canvas.width, canvas.height))
-        renderer.render(canvas, bounds, calendar, renderParameters)
+        renderer.render(canvas, bounds, calendar, renderParameters, id)
     }
 
     /**
@@ -661,16 +654,6 @@
         }
     }
 
-    /**
-     * Sets whether the complication should be drawn highlighted or not. This is to provide visual
-     * feedback when the user taps on a complication.
-     *
-     * @param highlight Whether or not the complication should be drawn highlighted.
-     */
-    internal fun setIsHighlighted(highlight: Boolean) {
-        renderer.isHighlighted = highlight
-    }
-
     internal fun init(invalidateListener: InvalidateListener) {
         this.invalidateListener = invalidateListener
     }
@@ -699,7 +682,6 @@
         writer.increaseIndent()
         writer.println("fixedComplicationDataSource=$fixedComplicationDataSource")
         writer.println("enabled=$enabled")
-        writer.println("renderer.isHighlighted=${renderer.isHighlighted}")
         writer.println("boundsType=$boundsType")
         writer.println("configExtras=$configExtras")
         writer.println("supportedTypes=${supportedTypes.joinToString { it.toString() }}")
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
index e056e16..9246c09 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationSlotsManager.kt
@@ -91,6 +91,9 @@
     public val complicationSlots: Map<Int, ComplicationSlot> =
         complicationSlotCollection.associateBy(ComplicationSlot::id)
 
+    /** The set of slot ids that are rendered as pressed. */
+    public val pressedSlotIds: Set<Int> = HashSet()
+
     private class InitialComplicationConfig(
         val complicationSlotBounds: ComplicationSlotBounds,
         val enabled: Boolean,
@@ -283,17 +286,17 @@
      */
     @UiThread
     public fun displayPressedAnimation(complicationSlotId: Int) {
-        val complication = requireNotNull(complicationSlots[complicationSlotId]) {
+        requireNotNull(complicationSlots[complicationSlotId]) {
             "No complication found with ID $complicationSlotId"
         }
-        complication.setIsHighlighted(true)
+        (pressedSlotIds as HashSet<Int>).add(complicationSlotId)
 
         val weakRef = WeakReference(this)
         watchFaceHostApi.getUiThreadHandler().postDelayed(
             {
                 // The watch face might go away before this can run.
                 if (weakRef.get() != null) {
-                    complication.setIsHighlighted(false)
+                    pressedSlotIds.remove(complicationSlotId)
                 }
             },
             WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS
@@ -379,6 +382,7 @@
     @UiThread
     internal fun dump(writer: IndentingPrintWriter) {
         writer.println("ComplicationSlotsManager:")
+        writer.println("renderer.pressedSlotIds=${pressedSlotIds.joinToString()}")
         writer.increaseIndent()
         for ((_, complication) in complicationSlots) {
             complication.dump(writer)
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
index dd06ae0..8b66e7f 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/RenderParameters.kt
@@ -81,11 +81,13 @@
  * @param highlightLayer Optional [HighlightLayer] used by editors to visually highlight an
  * aspect of the watch face. Rendered last on top of [watchFaceLayers]. If highlighting isn't needed
  * this will be `null`.
+ * @param pressedComplicationSlotIds A set of [ComplicationSlot.id]s to render as pressed.
  */
 public class RenderParameters @JvmOverloads constructor(
     public val drawMode: DrawMode,
     public val watchFaceLayers: Set<WatchFaceLayer>,
-    public val highlightLayer: HighlightLayer? = null
+    public val highlightLayer: HighlightLayer? = null,
+    public val pressedComplicationSlotIds: Set<Int> = emptySet()
 ) {
     init {
         require(watchFaceLayers.isNotEmpty() || highlightLayer != null) {
@@ -240,7 +242,8 @@
             }
 
             else -> null
-        }
+        },
+        wireFormat.pressedComplicationSlotIds.toSet()
     )
 
     /** @hide */
@@ -254,7 +257,8 @@
                 0,
                 null,
                 highlightLayer!!.highlightTint,
-                highlightLayer.backgroundTint
+                highlightLayer.backgroundTint,
+                pressedComplicationSlotIds.toIntArray()
             )
 
             is HighlightedElement.ComplicationSlot -> RenderParametersWireFormat(
@@ -264,7 +268,8 @@
                 thingHighlighted.id,
                 null,
                 highlightLayer!!.highlightTint,
-                highlightLayer.backgroundTint
+                highlightLayer.backgroundTint,
+                pressedComplicationSlotIds.toIntArray()
             )
 
             is HighlightedElement.UserStyle -> RenderParametersWireFormat(
@@ -274,7 +279,8 @@
                 0,
                 thingHighlighted.id.value,
                 highlightLayer!!.highlightTint,
-                highlightLayer.backgroundTint
+                highlightLayer.backgroundTint,
+                pressedComplicationSlotIds.toIntArray()
             )
 
             else -> RenderParametersWireFormat(
@@ -284,7 +290,8 @@
                 0,
                 null,
                 Color.BLACK,
-                Color.BLACK
+                Color.BLACK,
+                pressedComplicationSlotIds.toIntArray()
             )
         }
 
@@ -342,6 +349,7 @@
         if (drawMode != other.drawMode) return false
         if (watchFaceLayers != other.watchFaceLayers) return false
         if (highlightLayer != other.highlightLayer) return false
+        if (pressedComplicationSlotIds != other.pressedComplicationSlotIds) return false
 
         return true
     }
@@ -350,6 +358,7 @@
         var result = drawMode.hashCode()
         result = 31 * result + watchFaceLayers.hashCode()
         result = 31 * result + (highlightLayer?.hashCode() ?: 0)
+        result = 31 * result + pressedComplicationSlotIds.hashCode()
         return result
     }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
index 4af16d0..c19817e 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFace.kt
@@ -435,29 +435,40 @@
         // available programmatically. The value below is the default but it could be overridden
         // by OEMs.
         internal const val INITIAL_LOW_BATTERY_THRESHOLD = 15.0f
-
-        internal val defaultRenderParametersForDrawMode: HashMap<DrawMode, RenderParameters> =
-            hashMapOf(
-                DrawMode.AMBIENT to
-                    RenderParameters(
-                        DrawMode.AMBIENT, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null
-                    ),
-                DrawMode.INTERACTIVE to
-                    RenderParameters(
-                        DrawMode.INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null
-                    ),
-                DrawMode.LOW_BATTERY_INTERACTIVE to
-                    RenderParameters(
-                        DrawMode.LOW_BATTERY_INTERACTIVE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
-                        null
-                    ),
-                DrawMode.MUTE to
-                    RenderParameters(
-                        DrawMode.MUTE, WatchFaceLayer.ALL_WATCH_FACE_LAYERS, null
-                    ),
-            )
     }
 
+    private val defaultRenderParametersForDrawMode: HashMap<DrawMode, RenderParameters> =
+        hashMapOf(
+            DrawMode.AMBIENT to
+                RenderParameters(
+                    DrawMode.AMBIENT,
+                    WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+                    null,
+                    complicationSlotsManager.pressedSlotIds
+                ),
+            DrawMode.INTERACTIVE to
+                RenderParameters(
+                    DrawMode.INTERACTIVE,
+                    WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+                    null,
+                    complicationSlotsManager.pressedSlotIds
+                ),
+            DrawMode.LOW_BATTERY_INTERACTIVE to
+                RenderParameters(
+                    DrawMode.LOW_BATTERY_INTERACTIVE,
+                    WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+                    null,
+                    complicationSlotsManager.pressedSlotIds
+                ),
+            DrawMode.MUTE to
+                RenderParameters(
+                    DrawMode.MUTE,
+                    WatchFaceLayer.ALL_WATCH_FACE_LAYERS,
+                    null,
+                    complicationSlotsManager.pressedSlotIds
+                ),
+        )
+
     private val systemTimeProvider = watchface.systemTimeProvider
     private val legacyWatchFaceStyle = watchface.legacyWatchFaceStyle
     internal val renderer = watchface.renderer
@@ -1019,7 +1030,8 @@
                 Canvas(complicationBitmap),
                 Rect(0, 0, bounds.width(), bounds.height()),
                 calendar,
-                RenderParameters(params.renderParametersWireFormat)
+                RenderParameters(params.renderParametersWireFormat),
+                params.complicationSlotId
             )
 
             // Restore previous ComplicationData & style if required.
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/RenderParametersTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/RenderParametersTest.kt
index 18a2cec..bc881a2 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/RenderParametersTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/RenderParametersTest.kt
@@ -252,4 +252,4 @@
         assertThat(renderParameters3a).isNotEqualTo(renderParameters3c)
         assertThat(renderParameters3a).isNotEqualTo(renderParameters4a)
     }
-}
\ No newline at end of file
+}
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
index ba939fa..1f72f17 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/WatchFaceServiceTest.kt
@@ -720,27 +720,27 @@
             UserStyleSchema(emptyList())
         )
 
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-        assertThat(complicationDrawableRight.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(RIGHT_COMPLICATION_ID)
 
         // Tap left complication.
         tapAt(30, 50)
-        assertThat(complicationDrawableLeft.isHighlighted).isTrue()
+        assertThat(complicationSlotsManager.pressedSlotIds).contains(LEFT_COMPLICATION_ID)
         assertThat(testWatchFaceService.tappedComplicationSlotIds)
             .isEqualTo(listOf(LEFT_COMPLICATION_ID))
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
 
         // Tap right complication.
         testWatchFaceService.reset()
         tapAt(70, 50)
-        assertThat(complicationDrawableRight.isHighlighted).isTrue()
+        assertThat(complicationSlotsManager.pressedSlotIds).contains(RIGHT_COMPLICATION_ID)
         assertThat(testWatchFaceService.tappedComplicationSlotIds)
             .isEqualTo(listOf(RIGHT_COMPLICATION_ID))
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
 
         // Tap on blank space.
         testWatchFaceService.reset()
@@ -748,7 +748,7 @@
         assertThat(testWatchFaceService.tappedComplicationSlotIds).isEmpty()
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
         assertThat(testWatchFaceService.tappedComplicationSlotIds).isEmpty()
     }
 
@@ -760,21 +760,21 @@
             UserStyleSchema(emptyList())
         )
 
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-        assertThat(complicationDrawableRight.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(RIGHT_COMPLICATION_ID)
 
         // Rapidly tap left then right complication.
         tapAt(30, 50)
         tapAt(70, 50)
 
         // Both complicationSlots get temporarily highlighted.
-        assertThat(complicationDrawableLeft.isHighlighted).isTrue()
-        assertThat(complicationDrawableRight.isHighlighted).isTrue()
+        assertThat(complicationSlotsManager.pressedSlotIds).contains(LEFT_COMPLICATION_ID)
+        assertThat(complicationSlotsManager.pressedSlotIds).contains(RIGHT_COMPLICATION_ID)
 
         // And the highlight goes away after a delay.
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-        assertThat(complicationDrawableRight.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(LEFT_COMPLICATION_ID)
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(RIGHT_COMPLICATION_ID)
 
         // Taps are registered on both complicationSlots.
         assertThat(testWatchFaceService.tappedComplicationSlotIds)
@@ -804,7 +804,7 @@
             tapListener = tapListener
         )
 
-        assertThat(complicationDrawableEdge.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(EDGE_COMPLICATION_ID)
 
         `when`(
             edgeComplicationHitTester.hitTest(
@@ -817,12 +817,12 @@
 
         // Tap the edge complication.
         tapAt(0, 50)
-        assertThat(complicationDrawableEdge.isHighlighted).isTrue()
+        assertThat(complicationSlotsManager.pressedSlotIds).contains(EDGE_COMPLICATION_ID)
         assertThat(testWatchFaceService.tappedComplicationSlotIds)
             .isEqualTo(listOf(EDGE_COMPLICATION_ID))
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableEdge.isHighlighted).isFalse()
+        assertThat(complicationSlotsManager.pressedSlotIds).doesNotContain(EDGE_COMPLICATION_ID)
     }
 
     @Test
diff --git a/wear/wear/api/current.txt b/wear/wear/api/current.txt
index da88d15..3b47016 100644
--- a/wear/wear/api/current.txt
+++ b/wear/wear/api/current.txt
@@ -114,6 +114,10 @@
     field public static final String WATCH_FACE_PREVIEW_METADATA_NAME = "com.google.android.wearable.watchface.preview";
   }
 
+  public final class WearTypeHelper {
+    method public static boolean isChinaDevice(android.content.Context);
+  }
+
 }
 
 package androidx.wear.widget {
diff --git a/wear/wear/api/public_plus_experimental_current.txt b/wear/wear/api/public_plus_experimental_current.txt
index da88d15..3b47016 100644
--- a/wear/wear/api/public_plus_experimental_current.txt
+++ b/wear/wear/api/public_plus_experimental_current.txt
@@ -114,6 +114,10 @@
     field public static final String WATCH_FACE_PREVIEW_METADATA_NAME = "com.google.android.wearable.watchface.preview";
   }
 
+  public final class WearTypeHelper {
+    method public static boolean isChinaDevice(android.content.Context);
+  }
+
 }
 
 package androidx.wear.widget {
diff --git a/wear/wear/api/restricted_current.txt b/wear/wear/api/restricted_current.txt
index 89aab1d..c16eeeb 100644
--- a/wear/wear/api/restricted_current.txt
+++ b/wear/wear/api/restricted_current.txt
@@ -114,6 +114,10 @@
     field public static final String WATCH_FACE_PREVIEW_METADATA_NAME = "com.google.android.wearable.watchface.preview";
   }
 
+  public final class WearTypeHelper {
+    method public static boolean isChinaDevice(android.content.Context);
+  }
+
 }
 
 package androidx.wear.widget {
diff --git a/wear/wear/build.gradle b/wear/wear/build.gradle
index 654eb8a..a379c0b 100644
--- a/wear/wear/build.gradle
+++ b/wear/wear/build.gradle
@@ -30,6 +30,10 @@
     testImplementation(libs.testCore)
     testImplementation(libs.testRunner)
     testImplementation(libs.robolectric)
+    testImplementation(libs.testExtJunit)
+    testImplementation(libs.testRules)
+    testImplementation(libs.mockitoCore)
+    testImplementation(libs.mockitoKotlin)
 
     implementation("androidx.core:core-ktx:1.5.0-alpha04")
 
diff --git a/wear/wear/src/main/java/androidx/wear/utils/WearTypeHelper.java b/wear/wear/src/main/java/androidx/wear/utils/WearTypeHelper.java
new file mode 100644
index 0000000..bac34b3
--- /dev/null
+++ b/wear/wear/src/main/java/androidx/wear/utils/WearTypeHelper.java
@@ -0,0 +1,45 @@
+/*
+ * 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.wear.utils;
+
+import android.content.Context;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+/**
+ * Helper class for determining whether the given Wear OS device is for China or rest of the world.
+ */
+public final class WearTypeHelper {
+    @VisibleForTesting(otherwise = VisibleForTesting.PRIVATE)
+    static final String CHINA_SYSTEM_FEATURE = "cn.google";
+
+    /**
+     * Returns whether the given device is China device.
+     *
+     * This can be used together with
+     * {@code androidx.wear.phone.interactions.PhoneTypeHelper} to
+     * decide what Uri should be used when opening Play Store on connected phone.
+     *
+     * @return True if it is China device and false if it is rest of the world device.
+     */
+    public static boolean isChinaDevice(@NonNull Context context) {
+        return context.getPackageManager().hasSystemFeature(CHINA_SYSTEM_FEATURE);
+    }
+
+    private WearTypeHelper() {}
+}
diff --git a/wear/wear/src/test/java/androidx/wear/utils/WearTypeHelperTest.java b/wear/wear/src/test/java/androidx/wear/utils/WearTypeHelperTest.java
new file mode 100644
index 0000000..555a428
--- /dev/null
+++ b/wear/wear/src/test/java/androidx/wear/utils/WearTypeHelperTest.java
@@ -0,0 +1,67 @@
+/*
+ * 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.wear.utils;
+
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+
+import androidx.test.core.app.ApplicationProvider;
+
+import org.junit.Before;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.mockito.MockitoAnnotations;
+import org.robolectric.Shadows;
+import org.robolectric.annotation.Config;
+import org.robolectric.annotation.internal.DoNotInstrument;
+import org.robolectric.shadows.ShadowPackageManager;
+
+@RunWith(WearUtilsTestRunner.class)
+@DoNotInstrument // Stop Robolectric instrumenting this class due to it being in package "android".
+public class WearTypeHelperTest {
+    private ShadowPackageManager mShadowPackageManager = null;
+    private Context mContext;
+
+    @Before
+    public void setUp() {
+        MockitoAnnotations.initMocks(this);
+        mContext = ApplicationProvider.getApplicationContext();
+        mShadowPackageManager = Shadows.shadowOf(mContext.getPackageManager());
+    }
+
+    private void setSystemFeatureChina(boolean value) {
+        mShadowPackageManager.setSystemFeature(WearTypeHelper.CHINA_SYSTEM_FEATURE, value);
+    }
+
+    @Test
+    @Config(sdk = 28)
+    public void test_isChinaDevice() {
+        setSystemFeatureChina(true);
+
+        assertTrue(WearTypeHelper.isChinaDevice(mContext));
+    }
+
+    @Test
+    @Config(sdk = 28)
+    public void test_isROWDevice() {
+        setSystemFeatureChina(false);
+
+        assertFalse(WearTypeHelper.isChinaDevice(mContext));
+    }
+}
diff --git a/wear/wear/src/test/java/androidx/wear/utils/WearUtilsTestRunner.kt b/wear/wear/src/test/java/androidx/wear/utils/WearUtilsTestRunner.kt
new file mode 100644
index 0000000..6e8c2c2
--- /dev/null
+++ b/wear/wear/src/test/java/androidx/wear/utils/WearUtilsTestRunner.kt
@@ -0,0 +1,39 @@
+/*
+ * 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.wear.utils
+
+import org.junit.runners.model.FrameworkMethod
+import org.robolectric.RobolectricTestRunner
+import org.robolectric.internal.bytecode.InstrumentationConfiguration
+
+/**
+ * A [RobolectricTestRunner] for [androidx.wear.utils] unit tests.
+ *
+ * It has instrumentation turned off for the [androidx.wear.utils] package.
+ *
+ * Robolectric tries to instrument Kotlin classes, and it throws errors when it encounters
+ * companion objects, constructors with default values for parameters, and data classes with
+ * inline classes. We don't need shadowing of our classes because we want to use the actual
+ * objects in our tests.
+ */
+class WearUtilsTestRunner(testClass: Class<*>) : RobolectricTestRunner(testClass) {
+    override fun createClassLoaderConfig(method: FrameworkMethod): InstrumentationConfiguration =
+        InstrumentationConfiguration.Builder(
+            super.createClassLoaderConfig(method)
+        )
+            .doNotInstrumentPackage("androidx.wear.utils")
+            .build()
+}
\ No newline at end of file
diff --git a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
index 68a57ab..01a3a1a 100644
--- a/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
+++ b/webkit/webkit/src/main/java/androidx/webkit/WebViewCompat.java
@@ -507,7 +507,6 @@
      * HOSTNAME_PATTERN [ ":" PORT ] ]}, each part is explained in the below table:
      *
      * <table>
-     * <col width="25%">
      * <tr><th>Rule</th><th>Description</th><th>Example</th></tr>
      *
      * <tr>
diff --git a/window/window/proguard-rules.pro b/window/window/proguard-rules.pro
index 957f9c6..63c9b44 100644
--- a/window/window/proguard-rules.pro
+++ b/window/window/proguard-rules.pro
@@ -21,7 +21,7 @@
 
 # Keep the whole library for now since there is a crash with a missing method.
 # TODO(b/165268619) Make a narrow rule
--keep class androidx.window.window.** { *; }
+-keep class androidx.window.** { *; }
 
 # We also neep to keep sidecar.** for the same reason.
 -keep class androidx.window.sidecar.** { *; }
diff --git a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
index 37207a0..16a795f 100644
--- a/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
+++ b/work/workmanager/src/androidTest/java/androidx/work/impl/utils/ForceStopRunnableTest.java
@@ -24,6 +24,7 @@
 import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.anyLong;
 import static org.mockito.Mockito.doNothing;
+import static org.mockito.Mockito.doReturn;
 import static org.mockito.Mockito.mock;
 import static org.mockito.Mockito.spy;
 import static org.mockito.Mockito.times;
@@ -194,4 +195,12 @@
         verify(runnable, times(MAX_ATTEMPTS)).forceStopRunnable();
         verify(handler, times(1)).handleException(any(Throwable.class));
     }
+
+    @Test
+    public void test_completeOnMultiProcessChecks() {
+        ForceStopRunnable runnable = spy(mRunnable);
+        doReturn(false).when(runnable).multiProcessChecks();
+        runnable.run();
+        verify(mWorkManager).onForceStopRunnableCompleted();
+    }
 }
diff --git a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
index c1cc106..4a31178 100644
--- a/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
+++ b/work/workmanager/src/main/java/androidx/work/impl/utils/ForceStopRunnable.java
@@ -89,54 +89,59 @@
 
     @Override
     public void run() {
-        if (!multiProcessChecks()) {
-            return;
-        }
-
-        while (true) {
-            // Migrate the database to the no-backup directory if necessary.
-            WorkDatabasePathHelper.migrateDatabase(mContext);
-            // Clean invalid jobs attributed to WorkManager, and Workers that might have been
-            // interrupted because the application crashed (RUNNING state).
-            Logger.get().debug(TAG, "Performing cleanup operations.");
-            try {
-                forceStopRunnable();
-                break;
-            } catch (SQLiteCantOpenDatabaseException
-                    | SQLiteDatabaseCorruptException
-                    | SQLiteDatabaseLockedException
-                    | SQLiteTableLockedException
-                    | SQLiteConstraintException
-                    | SQLiteAccessPermException exception) {
-                mRetryCount++;
-                if (mRetryCount >= MAX_ATTEMPTS) {
-                    // ForceStopRunnable is usually the first thing that accesses a database
-                    // (or an app's internal data directory). This means that weird
-                    // PackageManager bugs are attributed to ForceStopRunnable, which is
-                    // unfortunate. This gives the developer a better error
-                    // message.
-                    String message = "The file system on the device is in a bad state. "
-                            + "WorkManager cannot access the app's internal data store.";
-                    Logger.get().error(TAG, message, exception);
-                    IllegalStateException throwable = new IllegalStateException(message, exception);
-                    InitializationExceptionHandler exceptionHandler =
-                            mWorkManager.getConfiguration().getExceptionHandler();
-                    if (exceptionHandler != null) {
-                        Logger.get().debug(TAG,
-                                "Routing exception to the specified exception handler",
-                                throwable);
-                        exceptionHandler.handleException(throwable);
-                        break;
+        try {
+            if (!multiProcessChecks()) {
+                return;
+            }
+            while (true) {
+                // Migrate the database to the no-backup directory if necessary.
+                WorkDatabasePathHelper.migrateDatabase(mContext);
+                // Clean invalid jobs attributed to WorkManager, and Workers that might have been
+                // interrupted because the application crashed (RUNNING state).
+                Logger.get().debug(TAG, "Performing cleanup operations.");
+                try {
+                    forceStopRunnable();
+                    break;
+                } catch (SQLiteCantOpenDatabaseException
+                        | SQLiteDatabaseCorruptException
+                        | SQLiteDatabaseLockedException
+                        | SQLiteTableLockedException
+                        | SQLiteConstraintException
+                        | SQLiteAccessPermException exception) {
+                    mRetryCount++;
+                    if (mRetryCount >= MAX_ATTEMPTS) {
+                        // ForceStopRunnable is usually the first thing that accesses a database
+                        // (or an app's internal data directory). This means that weird
+                        // PackageManager bugs are attributed to ForceStopRunnable, which is
+                        // unfortunate. This gives the developer a better error
+                        // message.
+                        String message = "The file system on the device is in a bad state. "
+                                + "WorkManager cannot access the app's internal data store.";
+                        Logger.get().error(TAG, message, exception);
+                        IllegalStateException throwable = new IllegalStateException(message,
+                                exception);
+                        InitializationExceptionHandler exceptionHandler =
+                                mWorkManager.getConfiguration().getExceptionHandler();
+                        if (exceptionHandler != null) {
+                            Logger.get().debug(TAG,
+                                    "Routing exception to the specified exception handler",
+                                    throwable);
+                            exceptionHandler.handleException(throwable);
+                            break;
+                        } else {
+                            throw throwable;
+                        }
                     } else {
-                        throw throwable;
+                        long duration = mRetryCount * BACKOFF_DURATION_MS;
+                        Logger.get()
+                                .debug(TAG, String.format("Retrying after %s", duration),
+                                        exception);
+                        sleep(mRetryCount * BACKOFF_DURATION_MS);
                     }
-                } else {
-                    long duration = mRetryCount * BACKOFF_DURATION_MS;
-                    Logger.get()
-                            .debug(TAG, String.format("Retrying after %s", duration), exception);
-                    sleep(mRetryCount * BACKOFF_DURATION_MS);
                 }
             }
+        } finally {
+            mWorkManager.onForceStopRunnableCompleted();
         }
     }
 
@@ -187,7 +192,6 @@
                     mWorkManager.getWorkDatabase(),
                     mWorkManager.getSchedulers());
         }
-        mWorkManager.onForceStopRunnableCompleted();
     }
 
     /**