Merge "Standardize to more kt-compatible syntax" into androidx-main
diff --git a/buildSrc-tests/project-subsets/build.gradle b/buildSrc-tests/project-subsets/build.gradle
new file mode 100644
index 0000000..cfeacda
--- /dev/null
+++ b/buildSrc-tests/project-subsets/build.gradle
@@ -0,0 +1,35 @@
+/*
+ * 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.
+ */
+
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.BuildServerConfigurationKt
+
+plugins {
+    id("AndroidXPlugin")
+    id("kotlin")
+}
+
+dependencies {
+    implementation gradleTestKit()
+    testImplementation JUNIT
+}
+tasks["test"].configure { t ->
+    // The output of this task can potentially depend on the contents of settings.gradle
+    // and any files referenced by it, including every build.gradle and any scripts or plugins they
+    // apply too. We don't have a convenient way to identify the true inputs here, so we always
+    // run this task and leave details around incrementality up to the Gradle builds that we spawn
+    t.outputs.upToDateWhen { false }
+}
diff --git a/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt b/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt
new file mode 100644
index 0000000..02cb205
--- /dev/null
+++ b/buildSrc-tests/project-subsets/src/test/kotlin/androidx/build/ProjectSubsetsTest.kt
@@ -0,0 +1,64 @@
+/*
+ * 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.
+ */
+
+import org.gradle.testkit.runner.GradleRunner
+import org.junit.Test
+import java.io.File
+
+/**
+ * This class tests that each of the project subsets defined in settings.gradle can be built
+ * successfully and does not involve a project that attempts to reference
+ * another project defined only in a different subset, b/172277767 .
+ *
+ * This is implemented using the Gradle TestKit so it can be
+ * run in parallel with other tasks, b/180012150 .
+ */
+public class ProjectSubsetsTest {
+    @Test
+    fun testSubsetMain() {
+        validateSubset("main")
+    }
+
+    @Test
+    fun testSubsetCompose() {
+        validateSubset("compose")
+    }
+
+    @Test
+    fun testSubsetFlan() {
+        validateSubset("flan")
+    }
+
+    @Test
+    fun testSubsetMedia() {
+        validateSubset("media")
+    }
+
+    @Test
+    fun testSubsetWear() {
+        validateSubset("wear")
+    }
+
+    /**
+     * Validates a specific project subset
+     */
+    fun validateSubset(name: String) {
+        GradleRunner.create()
+            .withProjectDir(File("../..").normalize())
+            .withArguments("-Pandroidx.projects=$name", "tasks")
+            .build(); // fails the test if the build fails
+    }
+}
diff --git a/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
index ee7aa5d..07ca269 100644
--- a/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
+++ b/buildSrc-tests/src/test/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilderTest.kt
@@ -100,8 +100,8 @@
     }
 
     @Test
-    fun testValidTestConfigXml_runFullTests() {
-        builder.runFullTests(false)
+    fun testValidTestConfigXml_runAllTests() {
+        builder.runAllTests(false)
         validate(builder.build())
     }
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
index 37d6fe4..f5146b1885 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXGradleProperties.kt
@@ -64,6 +64,12 @@
 const val ENABLE_DOCUMENTATION = "androidx.enableDocumentation"
 
 /**
+ * Adjusts the set of projects participating in this build.
+ * See settings.gradle for more information
+ */
+const val PROJECT_SUBSET = "androidx.projects"
+
+/**
  * Setting this property puts a summary of the relevant failure messages into standard error
  */
 const val SUMMARIZE_STANDARD_ERROR = "androidx.summarizeStderr"
@@ -115,6 +121,7 @@
     COVERAGE_ENABLED,
     DISPLAY_TEST_OUTPUT,
     ENABLE_DOCUMENTATION,
+    PROJECT_SUBSET,
     STUDIO_TYPE,
     SUMMARIZE_STANDARD_ERROR,
     USE_MAX_DEP_VERSIONS,
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
index b123344..fe36f68 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXPlugin.kt
@@ -35,6 +35,7 @@
 import androidx.build.resources.configurePublicResourcesStub
 import androidx.build.studio.StudioTask
 import androidx.build.testConfiguration.addAppApkToTestConfigGeneration
+import androidx.build.testConfiguration.addToTestZips
 import androidx.build.testConfiguration.configureTestConfigGeneration
 import com.android.build.api.extension.LibraryAndroidComponentsExtension
 import com.android.build.gradle.AppExtension
@@ -491,18 +492,7 @@
                 return
             }
 
-            project.rootProject.tasks.named(ZIP_TEST_CONFIGS_WITH_APKS_TASK)
-                .configure { task ->
-                    task as Zip
-                    task.from(packageTask.outputDirectory) {
-                        it.include("*.apk")
-                        it.duplicatesStrategy = DuplicatesStrategy.FAIL
-                        it.rename { fileName ->
-                            fileName.renameApkForTesting(project.path, project.hasBenchmarkPlugin())
-                        }
-                    }
-                    task.dependsOn(packageTask)
-                }
+            addToTestZips(project, packageTask)
 
             packageTask.doLast {
                 project.copy {
@@ -669,6 +659,7 @@
         const val GENERATE_TEST_CONFIGURATION_TASK = "GenerateTestConfiguration"
         const val REPORT_LIBRARY_METRICS_TASK = "reportLibraryMetrics"
         const val ZIP_TEST_CONFIGS_WITH_APKS_TASK = "zipTestConfigsWithApks"
+        const val ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK = "zipConstrainedTestConfigsWithApks"
 
         const val TASK_GROUP_API = "API"
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
index 4aeaf7d..6420a50 100644
--- a/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/AndroidXRootPlugin.kt
@@ -16,6 +16,7 @@
 
 package androidx.build
 
+import androidx.build.AndroidXPlugin.Companion.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.AndroidXPlugin.Companion.ZIP_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.dependencyTracker.AffectedModuleDetector
 import androidx.build.gradle.isRoot
@@ -142,16 +143,24 @@
 
         val zipTestConfigsWithApks = project.tasks.register(
             ZIP_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
-        )
-        // can't chain this, or a kotlin.Unit gets added as dependency below instead of the task
-        zipTestConfigsWithApks.configure {
+        ) {
             it.destinationDirectory.set(project.getDistributionDirectory())
             it.archiveFileName.set("androidTest.zip")
             it.from(project.getTestConfigDirectory())
             // We're mostly zipping a bunch of .apk files that are already compressed
             it.entryCompression = ZipEntryCompression.STORED
         }
+        val zipConstrainedTestConfigsWithApks = project.tasks.register(
+            ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK, Zip::class.java
+        ) {
+            it.destinationDirectory.set(project.getDistributionDirectory())
+            it.archiveFileName.set("constrainedAndroidTest.zip")
+            it.from(project.getConstrainedTestConfigDirectory())
+            // We're mostly zipping a bunch of .apk files that are already compressed
+            it.entryCompression = ZipEntryCompression.STORED
+        }
         buildOnServerTask.dependsOn(zipTestConfigsWithApks)
+        buildOnServerTask.dependsOn(zipConstrainedTestConfigsWithApks)
 
         AffectedModuleDetector.configure(gradle, this)
 
diff --git a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
index 7feac90..5cc80d4 100644
--- a/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/BuildServerConfiguration.kt
@@ -61,12 +61,20 @@
     File(getDistributionDirectory(), "build-info")
 
 /**
- * Directory for android test configuration files that get consumed by Tradefed in CI.
+ * Directory for android test configuration files that get consumed by Tradefed in CI. These
+ * configs cause all the tests to be run, except in cases where buildSrc changes.
  */
 fun Project.getTestConfigDirectory(): File =
     File(getDistributionDirectory(), "test-xml-configs")
 
 /**
+ * Directory for android test configuration files that get consumed by Tradefed in CI. These
+ * "constrained" configs cause only small and medium tests to be run for dependent projects.
+ */
+fun Project.getConstrainedTestConfigDirectory(): File =
+    File(getDistributionDirectory(), "constrained-test-xml-configs")
+
+/**
  * Directory to put release note files for generate release note tasks.
  */
 fun Project.getReleaseNotesDirectory(): File =
diff --git a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
index 54c4120..9b51d33 100644
--- a/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/LibraryVersions.kt
@@ -52,6 +52,7 @@
     val CORE_ANIMATION = Version("1.0.0-alpha03")
     val CORE_ANIMATION_TESTING = Version("1.0.0-alpha03")
     val CORE_APPDIGEST = Version("1.0.0-alpha01")
+    val CORE_GOOGLE_SHORTCUTS = Version("1.0.0-alpha01")
     val CORE_ROLE = Version("1.1.0-alpha02")
     val CURSORADAPTER = Version("1.1.0-alpha01")
     val CUSTOMVIEW = Version("1.2.0-alpha01")
@@ -142,7 +143,7 @@
     val WEAR_WATCHFACE_EDITOR = Version("1.0.0-alpha08")
     val WEAR_WATCHFACE_STYLE = Version("1.0.0-alpha08")
     val WEBKIT = Version("1.5.0-alpha01")
-    val WINDOW = Version("1.0.0-alpha03")
+    val WINDOW = Version("1.0.0-alpha04")
     val WINDOW_EXTENSIONS = Version("1.0.0-alpha01")
     val WINDOW_SIDECAR = Version("0.1.0-alpha01")
     val WORK = Version("2.6.0-alpha01")
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
index 7cf2af5a..a3aa541 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/AndroidTestXmlBuilder.kt
@@ -22,7 +22,7 @@
     var isBenchmark: Boolean = false
     var isPostsubmit: Boolean = true
     lateinit var minSdk: String
-    var runFullTests: Boolean = true
+    var runAllTests: Boolean = true
     val tags: MutableList<String> = mutableListOf()
     lateinit var testApkName: String
     lateinit var testRunner: String
@@ -32,7 +32,7 @@
     fun isBenchmark(isBenchmark: Boolean) = apply { this.isBenchmark = isBenchmark }
     fun isPostsubmit(isPostsubmit: Boolean) = apply { this.isPostsubmit = isPostsubmit }
     fun minSdk(minSdk: String) = apply { this.minSdk = minSdk }
-    fun runFullTests(runFullTests: Boolean) = apply { this.runFullTests = runFullTests }
+    fun runAllTests(runAllTests: Boolean) = apply { this.runAllTests = runAllTests }
     fun tag(tag: String) = apply { this.tags.add(tag) }
     fun testApkName(testApkName: String) = apply { this.testApkName = testApkName }
     fun testRunner(testRunner: String) = apply { this.testRunner = testRunner }
@@ -63,7 +63,7 @@
             .append(TEST_BLOCK_OPEN)
             .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
             .append(PACKAGE_OPTION.replace("APPLICATION_ID", applicationId))
-        if (runFullTests) {
+        if (runAllTests) {
             if (!isPostsubmit) {
                 sb.append(FLAKY_TEST_OPTION)
             }
@@ -95,7 +95,7 @@
     var isPostsubmit: Boolean = true
     var isServicePrevious: Boolean = true
     lateinit var minSdk: String
-    var runFullTests: Boolean = true
+    var runAllTests: Boolean = true
     lateinit var serviceApkName: String
     lateinit var serviceApplicationId: String
     var tags: MutableList<String> = mutableListOf()
@@ -112,7 +112,7 @@
         this.isServicePrevious = isServicePrevious
     }
     fun minSdk(minSdk: String) = apply { this.minSdk = minSdk }
-    fun runFullTests(runFullTests: Boolean) = apply { this.runFullTests = runFullTests }
+    fun runAllTests(runAllTests: Boolean) = apply { this.runAllTests = runAllTests }
     fun serviceApkName(serviceApkName: String) = apply { this.serviceApkName = serviceApkName }
     fun serviceApplicationId(serviceApplicationId: String) =
         apply { this.serviceApplicationId = serviceApplicationId }
@@ -158,7 +158,7 @@
             .append(RUNNER_OPTION.replace("TEST_RUNNER", testRunner))
             .append(PACKAGE_OPTION.replace("APPLICATION_ID", clientApplicationId))
             .append(mediaInstrumentationArgs())
-        if (runFullTests) {
+        if (runAllTests) {
             if (!isPostsubmit) {
                 sb.append(FLAKY_TEST_OPTION)
             }
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
index 4bee86c..becea7a 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateMediaTestConfigurationTask.kt
@@ -96,6 +96,15 @@
     @get:OutputFile
     abstract val clientToTServiceToT: RegularFileProperty
 
+    @get:OutputFile
+    abstract val constrainedClientPreviousServiceToT: RegularFileProperty
+
+    @get:OutputFile
+    abstract val constrainedClientToTServicePrevious: RegularFileProperty
+
+    @get:OutputFile
+    abstract val constrainedClientToTServiceToT: RegularFileProperty
+
     @TaskAction
     fun generateAndroidTestZip() {
         val clientToTApk = resolveApk(clientToTFolder, clientToTLoader)
@@ -116,6 +125,19 @@
             clientPreviousApk, serviceToTApk, clientPreviousPath.get(),
             serviceToTPath.get(), clientPreviousServiceToT, true, false
         )
+        // write constrained configs as well
+        writeConfigFileContent(
+            clientToTApk, serviceToTApk, clientToTPath.get(),
+            serviceToTPath.get(), constrainedClientToTServiceToT, false, false, true
+        )
+        writeConfigFileContent(
+            clientToTApk, servicePreviousApk, clientToTPath.get(),
+            servicePreviousPath.get(), constrainedClientToTServicePrevious, false, true, true
+        )
+        writeConfigFileContent(
+            clientPreviousApk, serviceToTApk, clientPreviousPath.get(),
+            serviceToTPath.get(), constrainedClientPreviousServiceToT, true, false, true
+        )
     }
 
     private fun resolveApk(
@@ -138,7 +160,8 @@
         servicePath: String,
         outputFile: RegularFileProperty,
         isClientPrevious: Boolean,
-        isServicePrevious: Boolean
+        isServicePrevious: Boolean,
+        isConstrained: Boolean = false
     ) {
         val configBuilder = MediaConfigBuilder()
         configBuilder.clientApkName(resolveName(clientApk, clientPath))
@@ -154,15 +177,19 @@
         when (affectedModuleDetectorSubset.get()) {
             ProjectSubset.CHANGED_PROJECTS -> {
                 configBuilder.isPostsubmit(false)
-                configBuilder.runFullTests(true)
+                configBuilder.runAllTests(true)
             }
             ProjectSubset.ALL_AFFECTED_PROJECTS -> {
                 configBuilder.isPostsubmit(true)
-                configBuilder.runFullTests(true)
+                configBuilder.runAllTests(true)
             }
             ProjectSubset.DEPENDENT_PROJECTS -> {
                 configBuilder.isPostsubmit(false)
-                configBuilder.runFullTests(false)
+                if (isConstrained) {
+                    configBuilder.runAllTests(false)
+                } else {
+                    configBuilder.runAllTests(false)
+                }
             }
             else -> {
                 throw IllegalStateException(
@@ -176,7 +203,7 @@
         if (!resolvedOutputFile.exists()) {
             if (!resolvedOutputFile.createNewFile()) {
                 throw RuntimeException(
-                    "Failed to create test configuration file: $outputFile"
+                    "Failed to create test configuration file: $resolvedOutputFile"
                 )
             }
         }
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
index ac32787..e4dd273 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/GenerateTestConfigurationTask.kt
@@ -74,12 +74,19 @@
     @get:OutputFile
     abstract val outputXml: RegularFileProperty
 
+    @get:OutputFile
+    abstract val constrainedOutputXml: RegularFileProperty
+
     @TaskAction
     fun generateAndroidTestZip() {
-        writeConfigFileContent()
+        writeConfigFileContent(constrainedOutputXml, true)
+        writeConfigFileContent(outputXml)
     }
 
-    private fun writeConfigFileContent() {
+    private fun writeConfigFileContent(
+        outputFile: RegularFileProperty,
+        isConstrained: Boolean = true
+    ) {
         /*
         Testing an Android Application project involves 2 APKS: an application to be instrumented,
         and a test APK. Testing an Android Library project involves only 1 APK, since the library
@@ -93,7 +100,7 @@
             // We don't need to check hasBenchmarkPlugin because benchmarks shouldn't have test apps
             val appName = appApk.elements.single().outputFile.substringAfterLast("/")
                 .renameApkForTesting(appProjectPath.get(), hasBenchmarkPlugin = false)
-            // TODO(b/178776319)
+            // TODO(b/178776319): Clean up this hardcoded hack
             if (appProjectPath.get().contains("macrobenchmark-target")) {
                 configBuilder.appApkName(appName.replace("debug-androidTest", "release"))
             } else {
@@ -103,15 +110,20 @@
         when (affectedModuleDetectorSubset.get()) {
             ProjectSubset.CHANGED_PROJECTS -> {
                 configBuilder.isPostsubmit(false)
-                configBuilder.runFullTests(true)
+                configBuilder.runAllTests(true)
             }
             ProjectSubset.ALL_AFFECTED_PROJECTS -> {
                 configBuilder.isPostsubmit(true)
-                configBuilder.runFullTests(true)
+                configBuilder.runAllTests(true)
             }
             ProjectSubset.DEPENDENT_PROJECTS -> {
                 configBuilder.isPostsubmit(false)
-                configBuilder.runFullTests(false)
+                // Don't ever run full tests of RV if it is dependent, since they take > 45 minutes
+                if (isConstrained || testProjectPath.get().contains("recyclerview")) {
+                    configBuilder.runAllTests(false)
+                } else {
+                    configBuilder.runAllTests(true)
+                }
             }
             else -> {
                 throw IllegalStateException(
@@ -145,11 +157,11 @@
             .minSdk(minSdk.get().toString())
             .testRunner(testRunner.get())
 
-        val resolvedOutputFile: File = outputXml.asFile.get()
+        val resolvedOutputFile: File = outputFile.asFile.get()
         if (!resolvedOutputFile.exists()) {
             if (!resolvedOutputFile.createNewFile()) {
                 throw RuntimeException(
-                    "Failed to create test configuration file: $outputXml"
+                    "Failed to create test configuration file: $resolvedOutputFile"
                 )
             }
         }
diff --git a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
index 9312d82..807b3f7 100644
--- a/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/testConfiguration/TestSuiteConfiguration.kt
@@ -19,18 +19,25 @@
 package androidx.build.testConfiguration
 
 import androidx.build.AndroidXPlugin
+import androidx.build.AndroidXPlugin.Companion.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
+import androidx.build.AndroidXPlugin.Companion.ZIP_TEST_CONFIGS_WITH_APKS_TASK
 import androidx.build.asFilenamePrefix
 import androidx.build.dependencyTracker.AffectedModuleDetector
+import androidx.build.getConstrainedTestConfigDirectory
 import androidx.build.getTestConfigDirectory
 import androidx.build.gradle.getByType
 import androidx.build.hasAndroidTestSourceCode
 import androidx.build.hasBenchmarkPlugin
+import androidx.build.renameApkForTesting
 import com.android.build.api.artifact.ArtifactType
 import com.android.build.api.artifact.Artifacts
 import com.android.build.api.extension.AndroidComponentsExtension
 import com.android.build.api.extension.ApplicationAndroidComponentsExtension
 import com.android.build.gradle.TestedExtension
+import com.android.build.gradle.tasks.PackageAndroidArtifact
 import org.gradle.api.Project
+import org.gradle.api.file.DuplicatesStrategy
+import org.gradle.api.tasks.bundling.Zip
 import org.gradle.api.tasks.TaskProvider
 import java.io.File
 
@@ -61,6 +68,12 @@
                 "${this.path.asFilenamePrefix()}$variantName.xml"
             )
         )
+        task.constrainedOutputXml.fileValue(
+            File(
+                this.getConstrainedTestConfigDirectory(),
+                "${this.path.asFilenamePrefix()}$variantName.xml"
+            )
+        )
         // Disable work tests on < API 18: b/178127496
         if (this.path.startsWith(":work:")) {
             task.minSdk.set(maxOf(18, minSdk))
@@ -85,6 +98,8 @@
     }
     this.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
         .dependsOn(generateTestConfigurationTask)
+    this.rootProject.tasks.findByName(AndroidXPlugin.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK)!!
+        .dependsOn(generateTestConfigurationTask)
 }
 
 /**
@@ -108,6 +123,34 @@
     }
 }
 
+/**
+ * Configures the test zip task to include the project's apk
+ */
+fun addToTestZips(project: Project, packageTask: PackageAndroidArtifact) {
+    project.rootProject.tasks.named(ZIP_TEST_CONFIGS_WITH_APKS_TASK) { task ->
+        task as Zip
+        task.from(packageTask.outputDirectory) {
+            it.include("*.apk")
+            it.duplicatesStrategy = DuplicatesStrategy.FAIL
+            it.rename { fileName ->
+                fileName.renameApkForTesting(project.path, project.hasBenchmarkPlugin())
+            }
+        }
+        task.dependsOn(packageTask)
+    }
+    project.rootProject.tasks.named(ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK) { task ->
+        task as Zip
+        task.from(packageTask.outputDirectory) {
+            it.include("*.apk")
+            it.duplicatesStrategy = DuplicatesStrategy.FAIL
+            it.rename { fileName ->
+                fileName.renameApkForTesting(project.path, project.hasBenchmarkPlugin())
+            }
+        }
+        task.dependsOn(packageTask)
+    }
+}
+
 private fun getOrCreateMediaTestConfigTask(project: Project, isMedia2: Boolean):
     TaskProvider<GenerateMediaTestConfigurationTask> {
         val mediaPrefix = getMediaConfigTaskPrefix(isMedia2)
@@ -132,6 +175,9 @@
             }
             project.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
                 .dependsOn(task)
+            project.rootProject.tasks.findByName(
+                AndroidXPlugin.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
+            )!!.dependsOn(task)
             return task
         } else {
             return parentProject.tasks.withType(GenerateMediaTestConfigurationTask::class.java)
@@ -197,6 +243,24 @@
                 "${mediaPrefix}ClientToTServiceToT$variantName.xml"
             )
         )
+        it.constrainedClientPreviousServiceToT.fileValue(
+            File(
+                this.getConstrainedTestConfigDirectory(),
+                "${mediaPrefix}ClientPreviousServiceToT$variantName.xml"
+            )
+        )
+        it.constrainedClientToTServicePrevious.fileValue(
+            File(
+                this.getConstrainedTestConfigDirectory(),
+                "${mediaPrefix}ClientToTServicePrevious$variantName.xml"
+            )
+        )
+        it.constrainedClientToTServiceToT.fileValue(
+            File(
+                this.getConstrainedTestConfigDirectory(),
+                "${mediaPrefix}ClientToTServiceToT$variantName.xml"
+            )
+        )
         it.minSdk.set(minSdk)
         it.testRunner.set(testRunner)
         AffectedModuleDetector.configureTaskGuard(it)
@@ -236,6 +300,12 @@
                     "${this.path.asFilenamePrefix()}$variantName.xml"
                 )
             )
+            task.constrainedOutputXml.fileValue(
+                File(
+                    this.getTestConfigDirectory(),
+                    "${this.path.asFilenamePrefix()}$variantName.xml"
+                )
+            )
             task.minSdk.set(minSdk)
             task.hasBenchmarkPlugin.set(this.hasBenchmarkPlugin())
             task.testRunner.set(testRunner)
@@ -255,6 +325,9 @@
         }
         this.rootProject.tasks.findByName(AndroidXPlugin.ZIP_TEST_CONFIGS_WITH_APKS_TASK)!!
             .dependsOn(configTask)
+        this.rootProject.tasks.findByName(
+            AndroidXPlugin.ZIP_CONSTRAINED_TEST_CONFIGS_WITH_APKS_TASK
+        )!!.dependsOn(configTask)
     } else if (path.endsWith("macrobenchmark-target")) {
         configTask.configure { task ->
             task.appFolder.set(artifacts.get(ArtifactType.APK))
diff --git a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
index f150ee3..d1b7f3c 100644
--- a/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
+++ b/buildSrc/src/main/kotlin/androidx/build/uptodatedness/TaskUpToDateValidator.kt
@@ -108,6 +108,7 @@
     "testDebugUnitTest",
     "stripArchiveForPartialDejetification",
     "verifyDependencyVersions",
+    "zipConstrainedTestConfigsWithApks",
     "zipTestConfigsWithApks",
     "zipHtmlResultsOfTestDebugUnitTest",
     "zipXmlResultsOfTestDebugUnitTest",
@@ -129,11 +130,11 @@
 // Additional tasks that are expected to be temporarily out-of-date after running once
 // Tasks in this set we don't even try to rerun, because they're known to be unnecessary
 val DONT_TRY_RERUNNING_TASKS = setOf(
+    ":buildSrc-tests:project-subsets:test",
     "listTaskOutputs",
     "validateProperties",
     "tasks",
     "zipEcFiles",
-
     // More information about the fact that these dokka tasks rerun can be found at b/167569304
     "dokkaKotlinDocs",
     "zipDokkaDocs",
diff --git a/busytown/androidx-studio-integration.sh b/busytown/androidx-studio-integration.sh
index c189fe7..a35614b 100755
--- a/busytown/androidx-studio-integration.sh
+++ b/busytown/androidx-studio-integration.sh
@@ -48,7 +48,7 @@
   LOG_PROCESSOR="$SCRIPT_DIR/../development/build_log_processor.sh"
   properties="-Pandroidx.summarizeStderr --no-daemon -Pandroidx.allWarningsAsErrors"
   "$LOG_PROCESSOR"                   $gw $properties -p frameworks/support    listTaskOutputs && \
-  "$LOG_PROCESSOR"                   $gw $properties -p frameworks/support    bOS -x lintDebug -x lint -x validateLint --stacktrace -PverifyUpToDate
+  "$LOG_PROCESSOR"                   $gw $properties -p frameworks/support    bOS -x lintDebug -x lint -x validateLint -x verifyDependencyVersions --stacktrace -PverifyUpToDate
 }
 
 function exportTransformsDir() {
diff --git a/busytown/androidx.sh b/busytown/androidx.sh
index 986efbd..c091b0e 100755
--- a/busytown/androidx.sh
+++ b/busytown/androidx.sh
@@ -7,10 +7,6 @@
 
 # Run Gradle
 impl/build.sh listTaskOutputs "$@"
-subsets="MAIN COMPOSE FLAN MEDIA WEAR"
-for subset in $subsets; do
-  ANDROIDX_PROJECTS=$subset impl/build.sh tasks -Pandroidx.validateNoUnrecognizedMessages=false >/dev/null
-done
 impl/build.sh buildOnServer checkExternalLicenses validateAllProperties \
     --profile "$@"
 
diff --git a/busytown/androidx_device_tests.sh b/busytown/androidx_device_tests.sh
index 5f72d6f..a3db9d2 100755
--- a/busytown/androidx_device_tests.sh
+++ b/busytown/androidx_device_tests.sh
@@ -5,7 +5,7 @@
 
 cd "$(dirname $0)"
 
-impl/build.sh zipTestConfigsWithApks \
+impl/build.sh zipTestConfigsWithApks zipConstrainedTestConfigsWithApks \
     "$@"
 
 echo "Completing $0 at $(date)"
diff --git a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
index 32674ba..51dfe49 100644
--- a/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
+++ b/camera/camera-camera2-pipe/src/test/java/androidx/camera/camera2/pipe/CameraPipeTest.kt
@@ -26,6 +26,7 @@
 import androidx.test.core.app.ApplicationProvider
 import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.runBlocking
+import org.junit.Ignore
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.robolectric.annotation.Config
@@ -72,6 +73,7 @@
     }
 
     @Test
+    @Ignore("b/180539013: Test is currently flaky")
     fun createExternalCameraGraph() {
         val fakeRequestProcessor = FakeRequestProcessor()
         val fakeCameraMetadata = FakeCameraMetadata()
diff --git a/car/app/app-aaos/api/current.txt b/car/app/app-aaos/api/current.txt
index 015dfc5..56a478a 100644
--- a/car/app/app-aaos/api/current.txt
+++ b/car/app/app-aaos/api/current.txt
@@ -22,13 +22,13 @@
     method public void setSurfaceWrapper(androidx.car.app.aaos.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageCompat implements android.os.Parcelable {
-    ctor public SurfacePackageCompat(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageCompat(android.view.SurfaceControlViewHost.SurfacePackage);
+  public final class SurfacePackageWrapper implements android.os.Parcelable {
+    ctor public SurfacePackageWrapper(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
     method public int describeContents();
     method public android.os.Parcelable? getSurfacePackage();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageCompat!> CREATOR;
+    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper!> CREATOR;
   }
 
   public final class SurfaceWrapper implements android.os.Parcelable {
diff --git a/car/app/app-aaos/api/public_plus_experimental_current.txt b/car/app/app-aaos/api/public_plus_experimental_current.txt
index 015dfc5..56a478a 100644
--- a/car/app/app-aaos/api/public_plus_experimental_current.txt
+++ b/car/app/app-aaos/api/public_plus_experimental_current.txt
@@ -22,13 +22,13 @@
     method public void setSurfaceWrapper(androidx.car.app.aaos.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageCompat implements android.os.Parcelable {
-    ctor public SurfacePackageCompat(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageCompat(android.view.SurfaceControlViewHost.SurfacePackage);
+  public final class SurfacePackageWrapper implements android.os.Parcelable {
+    ctor public SurfacePackageWrapper(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
     method public int describeContents();
     method public android.os.Parcelable? getSurfacePackage();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageCompat!> CREATOR;
+    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper!> CREATOR;
   }
 
   public final class SurfaceWrapper implements android.os.Parcelable {
diff --git a/car/app/app-aaos/api/restricted_current.txt b/car/app/app-aaos/api/restricted_current.txt
index 015dfc5..56a478a 100644
--- a/car/app/app-aaos/api/restricted_current.txt
+++ b/car/app/app-aaos/api/restricted_current.txt
@@ -22,13 +22,13 @@
     method public void setSurfaceWrapper(androidx.car.app.aaos.renderer.surface.SurfaceWrapper);
   }
 
-  public final class SurfacePackageCompat implements android.os.Parcelable {
-    ctor public SurfacePackageCompat(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
-    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageCompat(android.view.SurfaceControlViewHost.SurfacePackage);
+  public final class SurfacePackageWrapper implements android.os.Parcelable {
+    ctor public SurfacePackageWrapper(androidx.car.app.aaos.renderer.surface.LegacySurfacePackage);
+    ctor @RequiresApi(android.os.Build.VERSION_CODES.R) public SurfacePackageWrapper(android.view.SurfaceControlViewHost.SurfacePackage);
     method public int describeContents();
     method public android.os.Parcelable? getSurfacePackage();
     method public void writeToParcel(android.os.Parcel, int);
-    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageCompat!> CREATOR;
+    field public static final android.os.Parcelable.Creator<androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper!> CREATOR;
   }
 
   public final class SurfaceWrapper implements android.os.Parcelable {
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ICarAppActivity.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ICarAppActivity.aidl
index d06bdf2..86367cf 100644
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ICarAppActivity.aidl
+++ b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ICarAppActivity.aidl
@@ -16,12 +16,9 @@
 
 package androidx.car.app.aaos.renderer;
 
-import androidx.car.app.aaos.renderer.IBackButtonListener;
-import androidx.car.app.aaos.renderer.IInputConnectionListener;
-import androidx.car.app.aaos.renderer.ILifecycleListener;
-import androidx.car.app.aaos.renderer.IRotaryEventListener;
+import androidx.car.app.aaos.renderer.IRendererCallback;
 import androidx.car.app.aaos.renderer.surface.ISurfaceListener;
-import androidx.car.app.aaos.renderer.surface.SurfacePackageCompat;
+import androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper;
 
 /**
  * An interface to let renderer service communicate with the car activity.
@@ -30,36 +27,23 @@
  */
 oneway interface ICarAppActivity {
     /** Sets the surface package. */
-    void setSurfacePackage(in SurfacePackageCompat surfacePackageCompat) = 1;
+    void setSurfacePackage(in SurfacePackageWrapper surfacePackageWrapper) = 1;
 
     /** Registers the listener to get callbacks for surface events. */
     void setSurfaceListener(ISurfaceListener listener) = 2;
 
-    /** Registers the listener to get callbacks for lifecyle events. */
-    void setLifecycleListener(ILifecycleListener listener) = 3;
-
-    /** Registers the listener to get callbacks for back button events. */
-    void setBackButtonListener(IBackButtonListener listener) = 4;
+    /** Registers the callback to get callbacks for renderer events. */
+    void registerRendererCallback(IRendererCallback callback) = 3;
 
     /** Notifies to start the input, i.e. to show the keyboard. */
-    void onStartInput() = 5;
+    void onStartInput() = 4;
 
     /** Notifies to stop the input, i.e. to hide the keyboard. */
-    void onStopInput() = 6;
-
-    /**
-     * Registers the listener for input connection.
-     *
-     * This listener can be used to establish an input connection with the host.
-     */
-    void setInputConnectionListener(IInputConnectionListener listener) = 7;
-
-    /** Registers the listener to get rotary events. */
-    void setRotaryEventListener(IRotaryEventListener listener) = 8;
+    void onStopInput() = 5;
 
     /** Sends the Intent to be used to start a car app. */
-    void startCarApp(in Intent intent) = 9;
+    void startCarApp(in Intent intent) = 6;
 
     /** Requests the activity to finish itself. */
-    void finishCarApp() = 10;
+    void finishCarApp() = 7;
 }
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IInputConnectionListener.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IInputConnectionListener.aidl
deleted file mode 100644
index 2fe9f33e..0000000
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IInputConnectionListener.aidl
+++ /dev/null
@@ -1,37 +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.car.app.aaos.renderer;
-
-import  androidx.car.app.aaos.renderer.IProxyInputConnection;
-
-/**
- * Proxies input connection events from {@link CarAppActivity} to the host renderer.
- *
- * @hide
- */
-interface IInputConnectionListener {
-
-  /**
-   * Creates a proxy to a remote {@link InputConnection}.
-   *
-   * @params editorInfo the {@link EditorInfo} for which the input connection should be created
-   *
-   * @return an {@link IProxyInputConnection} through which communication to the
-   *   remote {@code InputConnection} should occur
-   */
-  IProxyInputConnection onCreateInputConnection(in EditorInfo editorInfo) = 1;
-}
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ILifecycleListener.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ILifecycleListener.aidl
deleted file mode 100644
index 5c5d85fd..0000000
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/ILifecycleListener.aidl
+++ /dev/null
@@ -1,55 +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.car.app.aaos.renderer;
-
-/**
- * A lifecycle event listener interface.
- *
- * @hide
- */
-interface ILifecycleListener {
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onCreate()}.
-   */
-  void onCreate() = 1;
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onStart()}.
-   */
-  void onStart() = 2;
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onResume()}.
-   */
-  void onResume() = 3;
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onPause()}.
-   */
-  void onPause() = 4;
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onStop()}.
-   */
-  void onStop() = 5;
-
-  /**
-   * Notifies that {@link CarAppActivity} called {Activity#onDestroyed()}.
-   */
-  void onDestroyed() = 6;
-}
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRendererCallback.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRendererCallback.aidl
new file mode 100644
index 0000000..38d6f7e
--- /dev/null
+++ b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRendererCallback.aidl
@@ -0,0 +1,90 @@
+/*
+ * 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.car.app.aaos.renderer;
+
+import  androidx.car.app.aaos.renderer.IProxyInputConnection;
+
+/**
+ * Interface to events relevant to remote rendering.
+ *
+ * @hide
+ */
+interface IRendererCallback {
+  /** Notifies that the button was pressed. */
+  void onBackPressed() = 1;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onCreate()}.
+   */
+  void onCreate() = 2;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onStart()}.
+   */
+  void onStart() = 3;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onResume()}.
+   */
+  void onResume() = 4;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onPause()}.
+   */
+  void onPause() = 5;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onStop()}.
+   */
+  void onStop() = 6;
+
+  /**
+   * Notifies that {@link CarAppActivity} called {Activity#onDestroyed()}.
+   */
+  void onDestroyed() = 7;
+
+  /**
+   * Creates a proxy to a remote {@link InputConnection}.
+   *
+   * @params editorInfo the {@link EditorInfo} for which the input connection should be created
+   *
+   * @return an {@link IProxyInputConnection} through which communication to the
+   *   remote {@code InputConnection} should occur
+   */
+  IProxyInputConnection onCreateInputConnection(in EditorInfo editorInfo) = 8;
+
+  /**
+   * Notifies of a rotary rotation.
+   *
+   * @param steps the number of rotation steps detected. Should be a positive number
+   * @param isClockwise true if the rotation direction is clockwise
+   */
+  void onRotate(int steps, boolean isClockwise) = 9;
+
+  /**
+   * Notifies of a nudge event.
+   *
+   * @param key the nudge key code. It can be {@code KEYCODE_DPAD_RIGHT}, {@code KEYCODE_DPAD_LEFT},
+   * {@code KEYCODE_DPAD_UP}, {@code KEYCODE_DPAD_DOWN}.
+   *
+   * @return true if handled successfully
+   */
+  boolean onNudge(int keyCode) = 10;
+
+  /** Will be called when rotary select is triggered. */
+  void onSelect() = 11;
+}
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRotaryEventListener.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRotaryEventListener.aidl
deleted file mode 100644
index 755c641..0000000
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IRotaryEventListener.aidl
+++ /dev/null
@@ -1,46 +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.car.app.aaos.renderer;
-
-/**
- * Interface to receive rotary events.
- *
- * @hide
- */
-interface IRotaryEventListener {
-
-  /**
-   * Notifies of a rotary rotation.
-   *
-   * @param steps the number of rotation steps detected. Should be a positive number
-   * @param isClockwise true if the rotation direction is clockwise
-   */
-  void onRotate(int steps, boolean isClockwise) = 1;
-
-  /**
-   * Notifies of a nudge event.
-   *
-   * @param key the nudge key code. It can be {@code KEYCODE_DPAD_RIGHT}, {@code KEYCODE_DPAD_LEFT},
-   * {@code KEYCODE_DPAD_UP}, {@code KEYCODE_DPAD_DOWN}.
-   *
-   * @return true if handled successfully
-   */
-  boolean onNudge(int keyCode) = 2;
-
-  /** Will be called when rotary select is triggered. */
-  void onSelect() = 3;
-}
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.aidl
deleted file mode 100644
index 41def00..0000000
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.aidl
+++ /dev/null
@@ -1,3 +0,0 @@
-package androidx.car.app.aaos.renderer.surface;
-
-parcelable SurfacePackageCompat;
\ No newline at end of file
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.aidl b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.aidl
new file mode 100644
index 0000000..e4de051
--- /dev/null
+++ b/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.aidl
@@ -0,0 +1,3 @@
+package androidx.car.app.aaos.renderer.surface;
+
+parcelable SurfacePackageWrapper;
\ No newline at end of file
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/ActivityLifecycleDelegate.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/ActivityLifecycleDelegate.java
index 0153bfa..a19dd46 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/ActivityLifecycleDelegate.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/ActivityLifecycleDelegate.java
@@ -26,26 +26,25 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.car.app.aaos.renderer.ILifecycleListener;
+import androidx.car.app.aaos.renderer.IRendererCallback;
 import androidx.lifecycle.Lifecycle.Event;
 
 /**
- * An activity lifecycle listener which dispatches the lifecycle events to the {@link
- * ILifecycleListener}.
+ * An activity lifecycle listener which dispatches the lifecycle events to a {@link
+ * IRendererCallback}.
  */
 final class ActivityLifecycleDelegate implements ActivityLifecycleCallbacks {
     public static final String TAG = "ActivityLifecycleListener";
     @Nullable
-    private ILifecycleListener mLifecycleListener;
+    private IRendererCallback mRendererCallback;
     @NonNull
     private Event mLastObservedEvent = Event.ON_ANY;
 
     /**
-     * Registers the lifecycle listener. This listener gets notified of lifecycle method
-     * invocations.
+     * Registers a {@link IRendererCallback} that is notified of lifecycle method invocations.
      */
-    void setLifecycleListener(@Nullable ILifecycleListener lifecycleListener) {
-        mLifecycleListener = lifecycleListener;
+    void registerRendererCallback(@Nullable IRendererCallback rendererCallback) {
+        mRendererCallback = rendererCallback;
         onActive();
     }
 
@@ -97,29 +96,29 @@
     private void notifyEvent(Event event) {
         mLastObservedEvent = event;
 
-        if (mLifecycleListener == null) {
+        if (mRendererCallback == null) {
             return;
         }
 
         try {
             switch (event) {
                 case ON_CREATE:
-                    mLifecycleListener.onCreate();
+                    mRendererCallback.onCreate();
                     break;
                 case ON_START:
-                    mLifecycleListener.onStart();
+                    mRendererCallback.onStart();
                     break;
                 case ON_RESUME:
-                    mLifecycleListener.onResume();
+                    mRendererCallback.onResume();
                     break;
                 case ON_PAUSE:
-                    mLifecycleListener.onPause();
+                    mRendererCallback.onPause();
                     break;
                 case ON_STOP:
-                    mLifecycleListener.onStop();
+                    mRendererCallback.onStop();
                     break;
                 case ON_DESTROY:
-                    mLifecycleListener.onDestroyed();
+                    mRendererCallback.onDestroyed();
                     break;
                 case ON_ANY:
                     break;
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/CarAppActivity.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/CarAppActivity.java
index 1d47918..76d9a38 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/CarAppActivity.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/CarAppActivity.java
@@ -18,6 +18,8 @@
 
 import static android.content.pm.PackageManager.NameNotFoundException;
 
+import static androidx.car.app.aaos.LogTags.TAG_AAOS_HOST;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.SuppressLint;
@@ -38,15 +40,14 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
-import androidx.car.app.aaos.renderer.IBackButtonListener;
 import androidx.car.app.aaos.renderer.ICarAppActivity;
-import androidx.car.app.aaos.renderer.IInputConnectionListener;
-import androidx.car.app.aaos.renderer.ILifecycleListener;
+import androidx.car.app.aaos.renderer.IRendererCallback;
 import androidx.car.app.aaos.renderer.IRendererService;
-import androidx.car.app.aaos.renderer.IRotaryEventListener;
 import androidx.car.app.aaos.renderer.surface.ISurfaceListener;
+import androidx.car.app.aaos.renderer.surface.OnBackPressedListener;
+import androidx.car.app.aaos.renderer.surface.RotaryEventCallback;
 import androidx.car.app.aaos.renderer.surface.SurfaceHolderListener;
-import androidx.car.app.aaos.renderer.surface.SurfacePackageCompat;
+import androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper;
 import androidx.car.app.aaos.renderer.surface.SurfaceWrapperProvider;
 import androidx.car.app.aaos.renderer.surface.TemplateSurfaceView;
 import androidx.car.app.utils.ThreadUtils;
@@ -78,7 +79,7 @@
     SurfaceHolderListener mSurfaceHolderListener;
     ActivityLifecycleDelegate mActivityLifecycleDelegate;
     @Nullable
-    IBackButtonListener mBackButtonListener;
+    OnBackPressedListener mOnBackPressedListener;
     @Nullable
     IRendererService mRendererService;
     private int mDisplayId;
@@ -89,30 +90,82 @@
      */
     private final ICarAppActivity.Stub mCarActivity = new ICarAppActivity.Stub() {
         @Override
-        public void setSurfacePackage(@NonNull SurfacePackageCompat surfacePackage) {
+        public void setSurfacePackage(@NonNull SurfacePackageWrapper surfacePackage) {
             requireNonNull(surfacePackage);
             ThreadUtils.runOnMain(() -> mSurfaceView.setSurfacePackage(surfacePackage));
         }
 
         @Override
+        public void registerRendererCallback(@NonNull IRendererCallback callback) {
+            requireNonNull(callback);
+            ThreadUtils.runOnMain(() -> {
+                mSurfaceView.registerRotaryEventCallback(new RotaryEventCallback() {
+                    @Override
+                    public void onRotate(int steps, boolean isClockwise) {
+                        try {
+                            callback.onRotate(steps, isClockwise);
+                        } catch (RemoteException e) {
+                            onServiceConnectionError("Failed to send rotary onRotate event to "
+                                    + "renderer: " + e.getMessage());
+                        }
+                    }
+
+                    @Override
+                    public boolean onNudge(int keyCode) {
+                        try {
+                            return callback.onNudge(keyCode);
+                        } catch (RemoteException e) {
+                            onServiceConnectionError("Failed to send rotary onNudge event to "
+                                    + "renderer: " + e.getMessage());
+                        }
+
+                        return false;
+                    }
+
+                    @Override
+                    public void onSelect() {
+                        try {
+                            callback.onSelect();
+                        } catch (RemoteException e) {
+                            onServiceConnectionError(
+                                    "Failed to send rotary onSelect event to renderer: "
+                                            + e.getMessage());
+                        }
+                    }
+                });
+
+                mSurfaceView.setOnCreateInputConnectionListener(editorInfo -> {
+                    try {
+                        return callback.onCreateInputConnection(editorInfo);
+                    } catch (RemoteException e) {
+                        onServiceConnectionError("Failed to send onCreateInputConnection event to "
+                                + "renderer: " + e.getMessage());
+                    }
+
+                    return null;
+                });
+
+                mOnBackPressedListener = () -> {
+                    try {
+                        callback.onBackPressed();
+                    } catch (RemoteException e) {
+                        onServiceConnectionError(
+                                "Failed to send onBackPressed event to renderer: "
+                                        + e.getMessage());
+                    }
+                };
+                mSurfaceView.setOnBackPressedListener(mOnBackPressedListener);
+                mActivityLifecycleDelegate.registerRendererCallback(callback);
+            });
+        }
+
+        @Override
         public void setSurfaceListener(@NonNull ISurfaceListener listener) {
             requireNonNull(listener);
             ThreadUtils.runOnMain(() -> mSurfaceHolderListener.setSurfaceListener(listener));
         }
 
         @Override
-        public void setLifecycleListener(@NonNull ILifecycleListener listener) {
-            requireNonNull(listener);
-            ThreadUtils.runOnMain(() -> mActivityLifecycleDelegate.setLifecycleListener(listener));
-        }
-
-        @Override
-        public void setBackButtonListener(@NonNull IBackButtonListener listener) {
-            mBackButtonListener = requireNonNull(listener);
-            mSurfaceView.setBackButtonListener(listener);
-        }
-
-        @Override
         public void onStartInput() {
             ThreadUtils.runOnMain(() -> mSurfaceView.onStartInput());
         }
@@ -123,16 +176,6 @@
         }
 
         @Override
-        public void setInputConnectionListener(@NonNull IInputConnectionListener listener) {
-            mSurfaceView.setInputConnectionListener(requireNonNull(listener));
-        }
-
-        @Override
-        public void setRotaryEventListener(@NonNull IRotaryEventListener listener) {
-            mSurfaceView.setRotaryEventListener(requireNonNull(listener));
-        }
-
-        @Override
         public void startCarApp(@NonNull Intent intent) {
             startActivity(intent);
         }
@@ -224,13 +267,8 @@
 
     @Override
     public void onBackPressed() {
-        if (mBackButtonListener != null) {
-            try {
-                mBackButtonListener.onBackPressed();
-            } catch (RemoteException e) {
-                onServiceConnectionError(
-                        "Failed to send onBackPressed event to renderer: " + e.getMessage());
-            }
+        if (mOnBackPressedListener != null) {
+            mOnBackPressedListener.onBackPressed();
         }
     }
 
@@ -266,7 +304,7 @@
             activityInfo = getPackageManager().getActivityInfo(getComponentName(),
                     PackageManager.GET_META_DATA);
         } catch (NameNotFoundException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Unable to find component: " + getComponentName(), e);
+            Log.e(TAG_AAOS_HOST, "Unable to find component: " + getComponentName(), e);
         }
 
         if (activityInfo == null) {
@@ -275,11 +313,10 @@
 
         String serviceName = activityInfo.metaData.getString(SERVICE_METADATA_KEY);
         if (serviceName == null) {
-            Log.e(LogTags.TAG_AAOS_HOST, String.format("Unable to find required metadata tag with "
-                            + "name %s. App manifest must include metadata tag with name %s and "
-                            + "the name of the car app service as the value",
-                    SERVICE_METADATA_KEY,
-                    SERVICE_METADATA_KEY));
+            Log.e(TAG_AAOS_HOST, "Unable to find required metadata tag with "
+                    + "name " + SERVICE_METADATA_KEY + ". App manifest must include "
+                    + "metadata tag with name " + SERVICE_METADATA_KEY + " and "
+                    + "the name of the car app service as the value");
             return null;
         }
 
@@ -325,9 +362,9 @@
         if (errorMessage != null) {
             Log.e(TAG, errorMessage);
         }
-        // Remove the lifecycle listener since there is no need to communicate the state with
+        // Remove the renderer callback since there is no need to communicate the state with
         // the host.
-        mActivityLifecycleDelegate.setLifecycleListener(null);
+        mActivityLifecycleDelegate.registerRendererCallback(null);
         finish();
     }
 
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/LogTags.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/LogTags.java
index 458c702..f1aec9d 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/LogTags.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/LogTags.java
@@ -21,13 +21,13 @@
 import androidx.annotation.RestrictTo;
 
 /**
- * Assorted tags for logging.
+ * Declares the log tags to use in the library.
  *
  * @hide
  */
 @RestrictTo(LIBRARY)
 public final class LogTags {
-    public static final String TAG_AAOS_HOST = "CarApp.H";
+    public static final String TAG_AAOS_HOST = androidx.car.app.utils.LogTags.TAG + ".AAOS";
 
     private LogTags() {
     }
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnBackPressedListener.java
similarity index 70%
rename from car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl
rename to car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnBackPressedListener.java
index d48ca1b..576b2e7 100644
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnBackPressedListener.java
@@ -14,14 +14,19 @@
  * limitations under the License.
  */
 
-package androidx.car.app.aaos.renderer;
+package androidx.car.app.aaos.renderer.surface;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.RestrictTo;
 
 /**
  * Classes that wish to listen for back button events should implement this.
  *
  * @hide
  */
-interface IBackButtonListener {
-  /** Notifies that the button was pressed. */
-  void onBackPressed() = 1;
+@RestrictTo(LIBRARY)
+public interface OnBackPressedListener {
+    /** Notifies that the button was pressed. */
+    void onBackPressed();
 }
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnCreateInputConnectionListener.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnCreateInputConnectionListener.java
new file mode 100644
index 0000000..e262db7
--- /dev/null
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/OnCreateInputConnectionListener.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.car.app.aaos.renderer.surface;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import android.view.inputmethod.EditorInfo;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.RestrictTo;
+import androidx.car.app.aaos.renderer.IProxyInputConnection;
+
+/**
+ * Proxies the {@link android.view.View#onCreateInputConnection} method invocation events from
+ * {@link TemplateSurfaceView} to the host renderer.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface OnCreateInputConnectionListener {
+    /**
+     * Creates a proxy to a remote {@link android.view.inputmethod.InputConnection}.
+     *
+     * @param editorInfo the {@link EditorInfo} for which the input connection should be created
+     * @return an {@link IProxyInputConnection} through which communication to the
+     * remote {@code InputConnection} should occur
+     */
+    @Nullable
+    IProxyInputConnection onCreateInputConnection(@NonNull EditorInfo editorInfo);
+}
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RemoteProxyInputConnection.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RemoteProxyInputConnection.java
index 209a054..a99e4c1 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RemoteProxyInputConnection.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RemoteProxyInputConnection.java
@@ -16,6 +16,8 @@
 
 package androidx.car.app.aaos.renderer.surface;
 
+import static androidx.car.app.aaos.LogTags.TAG_AAOS_HOST;
+
 import static java.util.Objects.requireNonNull;
 
 import android.os.Bundle;
@@ -31,7 +33,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
-import androidx.car.app.aaos.LogTags;
 import androidx.car.app.aaos.renderer.IProxyInputConnection;
 
 /** Proxies input connection calls to the provided {@link IProxyInputConnection}. */
@@ -50,7 +51,7 @@
         try {
             text = mProxyInputConnection.getTextBeforeCursor(n, flags);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             text = null;
         }
         return text;
@@ -63,7 +64,7 @@
         try {
             text = mProxyInputConnection.getTextAfterCursor(n, flags);
         } catch (RemoteException e) {
-            Log.w(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.w(TAG_AAOS_HOST, "Remote connection lost", e);
             text = null;
         }
 
@@ -77,7 +78,7 @@
         try {
             text = mProxyInputConnection.getSelectedText(flags);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             text = null;
         }
 
@@ -90,7 +91,7 @@
         try {
             text = mProxyInputConnection.getCursorCapsMode(reqModes);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             text = 0;
         }
 
@@ -105,7 +106,7 @@
         try {
             text = mProxyInputConnection.getExtractedText(request, flags);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             text = null;
         }
 
@@ -118,7 +119,7 @@
         try {
             success = mProxyInputConnection.deleteSurroundingText(beforeLength, afterLength);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -132,7 +133,7 @@
         try {
             success = mProxyInputConnection.setComposingText(text, newCursorPosition);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -145,7 +146,7 @@
         try {
             success = mProxyInputConnection.setComposingRegion(start, end);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -158,7 +159,7 @@
         try {
             success = mProxyInputConnection.finishComposingText();
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -172,7 +173,7 @@
         try {
             success = mProxyInputConnection.commitText(text, newCursorPosition);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -186,7 +187,7 @@
         try {
             success = mProxyInputConnection.commitCompletion(text);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -200,7 +201,7 @@
         try {
             success = mProxyInputConnection.commitCorrection(correctionInfo);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -213,7 +214,7 @@
         try {
             success = mProxyInputConnection.setSelection(start, end);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -226,7 +227,7 @@
         try {
             success = mProxyInputConnection.performEditorAction(editorAction);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -239,7 +240,7 @@
         try {
             success = mProxyInputConnection.performContextMenuAction(id);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -252,7 +253,7 @@
         try {
             success = mProxyInputConnection.beginBatchEdit();
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -265,7 +266,7 @@
         try {
             success = mProxyInputConnection.endBatchEdit();
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -279,7 +280,7 @@
         try {
             success = mProxyInputConnection.sendKeyEvent(event);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -292,7 +293,7 @@
         try {
             success = mProxyInputConnection.clearMetaKeyStates(states);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -305,7 +306,7 @@
         try {
             success = mProxyInputConnection.reportFullscreenMode(enabled);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -320,7 +321,7 @@
         try {
             success = mProxyInputConnection.performPrivateCommand(action, data);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -333,7 +334,7 @@
         try {
             success = mProxyInputConnection.requestCursorUpdates(cursorUpdateMode);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             success = false;
         }
 
@@ -345,7 +346,7 @@
         try {
             mProxyInputConnection.closeConnection();
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
     }
 
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RotaryEventCallback.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RotaryEventCallback.java
new file mode 100644
index 0000000..7eea59b
--- /dev/null
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/RotaryEventCallback.java
@@ -0,0 +1,50 @@
+/*
+ * 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.car.app.aaos.renderer.surface;
+
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+
+import androidx.annotation.RestrictTo;
+
+/**
+ * Interface to receive rotary events.
+ *
+ * @hide
+ */
+@RestrictTo(LIBRARY)
+public interface RotaryEventCallback {
+    /**
+     * Notifies of a rotary rotation.
+     *
+     * @param steps the number of rotation steps detected. Should be a positive number
+     * @param isClockwise true if the rotation direction is clockwise
+     */
+    void onRotate(int steps, boolean isClockwise);
+
+    /**
+     * Notifies of a nudge event.
+     *
+     * @param keyCode the nudge key code. It can be {@code KEYCODE_DPAD_RIGHT}, {@code
+     * KEYCODE_DPAD_LEFT}, {@code KEYCODE_DPAD_UP}, {@code KEYCODE_DPAD_DOWN}.
+     *
+     * @return true if handled successfully
+     */
+    boolean onNudge(int keyCode);
+
+    /** Will be called when rotary select is triggered. */
+    void onSelect();
+}
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfaceHolderListener.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfaceHolderListener.java
index 67cb801..6da4f0e 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfaceHolderListener.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfaceHolderListener.java
@@ -17,6 +17,7 @@
 package androidx.car.app.aaos.renderer.surface;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.car.app.aaos.LogTags.TAG_AAOS_HOST;
 
 import static java.util.Objects.requireNonNull;
 
@@ -27,7 +28,6 @@
 import androidx.annotation.NonNull;
 import androidx.annotation.Nullable;
 import androidx.annotation.RestrictTo;
-import androidx.car.app.aaos.LogTags;
 
 /**
  * A listener of {@link SurfaceHolder}.
@@ -83,7 +83,7 @@
                 mSurfaceListener.onSurfaceAvailable(mSurfaceWrapperProvider.createSurfaceWrapper());
             }
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
 
     }
@@ -94,7 +94,7 @@
                 mSurfaceListener.onSurfaceChanged(mSurfaceWrapperProvider.createSurfaceWrapper());
             }
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
     }
 }
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.java
similarity index 73%
rename from car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.java
rename to car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.java
index 25b21d4..210b789 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageCompat.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/SurfacePackageWrapper.java
@@ -35,7 +35,7 @@
  */
 //TODO(179714355): Investigate using Bundleable instead of Parcelable
 @SuppressLint({"BanParcelableUsage"})
-public final class SurfacePackageCompat implements Parcelable {
+public final class SurfacePackageWrapper implements Parcelable {
     enum SurfacePackageType {
         LEGACY,
         SURFACE_CONTROL;
@@ -45,31 +45,31 @@
     private final Parcelable mSurfacePackage;
 
     /**
-     * Creates a {@link SurfacePackageCompat} that stores a
+     * Creates a {@link SurfacePackageWrapper} that stores a
      * {@link android.view.SurfaceControlViewHost.SurfacePackage}.
      *
      * @param legacySurfacePackage the {@link android.view.SurfaceControlViewHost.SurfacePackage}
      *                             to be stored in this wrapper.
      */
-    public SurfacePackageCompat(@NonNull LegacySurfacePackage legacySurfacePackage) {
+    public SurfacePackageWrapper(@NonNull LegacySurfacePackage legacySurfacePackage) {
         mSurfacePackage = legacySurfacePackage;
     }
 
     /**
-     * Creates a {@link SurfacePackageCompat} that stores a {@link LegacySurfacePackage}.
+     * Creates a {@link SurfacePackageWrapper} that stores a {@link LegacySurfacePackage}.
      *
      * @param surfacePackage the {@link LegacySurfacePackage} to be stored in this wrapper.
      */
     @RequiresApi(Build.VERSION_CODES.R)
-    public SurfacePackageCompat(@NonNull SurfacePackage surfacePackage) {
+    public SurfacePackageWrapper(@NonNull SurfacePackage surfacePackage) {
         mSurfacePackage = surfacePackage;
     }
 
-    SurfacePackageCompat(Parcel parcel) {
+    SurfacePackageWrapper(Parcel parcel) {
         SurfacePackageType type = SurfacePackageType.values()[parcel.readInt()];
         if (type == SurfacePackageType.LEGACY) {
             mSurfacePackage = parcel.readParcelable(LegacySurfacePackage.class.getClassLoader());
-        } else if (type == SurfacePackageCompat.SurfacePackageType.SURFACE_CONTROL
+        } else if (type == SurfacePackageWrapper.SurfacePackageType.SURFACE_CONTROL
                 && VERSION.SDK_INT >= Build.VERSION_CODES.R) {
             mSurfacePackage = parcel.readParcelable(SurfacePackage.class.getClassLoader());
         } else {
@@ -79,10 +79,10 @@
 
     @Override
     public void writeToParcel(@NonNull Parcel parcel, int flags) {
-        SurfacePackageCompat.SurfacePackageType type =
+        SurfacePackageWrapper.SurfacePackageType type =
                 mSurfacePackage instanceof LegacySurfacePackage
-                        ? SurfacePackageCompat.SurfacePackageType.LEGACY
-                        : SurfacePackageCompat.SurfacePackageType.SURFACE_CONTROL;
+                        ? SurfacePackageWrapper.SurfacePackageType.LEGACY
+                        : SurfacePackageWrapper.SurfacePackageType.SURFACE_CONTROL;
         parcel.writeInt(type.ordinal());
         parcel.writeParcelable(mSurfacePackage, flags);
     }
@@ -98,18 +98,18 @@
     }
 
     @NonNull
-    public static final Creator<SurfacePackageCompat> CREATOR =
-            new Creator<SurfacePackageCompat>() {
+    public static final Creator<SurfacePackageWrapper> CREATOR =
+            new Creator<SurfacePackageWrapper>() {
                 @NonNull
                 @Override
-                public SurfacePackageCompat createFromParcel(@NonNull Parcel parcel) {
-                    return new SurfacePackageCompat(parcel);
+                public SurfacePackageWrapper createFromParcel(@NonNull Parcel parcel) {
+                    return new SurfacePackageWrapper(parcel);
                 }
 
                 @NonNull
                 @Override
-                public SurfacePackageCompat[] newArray(int size) {
-                    return new SurfacePackageCompat[size];
+                public SurfacePackageWrapper[] newArray(int size) {
+                    return new SurfacePackageWrapper[size];
                 }
             };
 }
diff --git a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/TemplateSurfaceView.java b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/TemplateSurfaceView.java
index 95d4028..847f181 100644
--- a/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/TemplateSurfaceView.java
+++ b/car/app/app-aaos/src/main/java/androidx/car/app/aaos/renderer/surface/TemplateSurfaceView.java
@@ -24,6 +24,9 @@
 import static android.view.KeyEvent.KEYCODE_DPAD_RIGHT;
 import static android.view.KeyEvent.KEYCODE_DPAD_UP;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
+import static androidx.car.app.aaos.LogTags.TAG_AAOS_HOST;
+
 import static java.util.Objects.requireNonNull;
 
 import android.annotation.SuppressLint;
@@ -55,11 +58,7 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.RequiresApi;
 import androidx.annotation.RestrictTo;
-import androidx.car.app.aaos.LogTags;
-import androidx.car.app.aaos.renderer.IBackButtonListener;
-import androidx.car.app.aaos.renderer.IInputConnectionListener;
 import androidx.car.app.aaos.renderer.IProxyInputConnection;
-import androidx.car.app.aaos.renderer.IRotaryEventListener;
 
 /**
  * A surface view suitable for template rendering.
@@ -68,7 +67,7 @@
  *
  * @hide
  */
-@RestrictTo(RestrictTo.Scope.LIBRARY)
+@RestrictTo(LIBRARY)
 public final class TemplateSurfaceView extends SurfaceView {
     private static final boolean SUPPORTS_SURFACE_CONTROL =
             VERSION.SDK_INT >= Build.VERSION_CODES.R;
@@ -88,11 +87,11 @@
             + ".DIRECT_MANIPULATION";
 
     @Nullable
-    private IInputConnectionListener mInputConnectionListener;
+    private RotaryEventCallback mRotaryEventCallback;
     @Nullable
-    private IRotaryEventListener mRotaryEventListener;
+    private OnCreateInputConnectionListener mOnCreateInputConnectionListener;
     @Nullable
-    private IBackButtonListener mBackButtonListener;
+    private OnBackPressedListener mOnBackPressedListener;
 
     ISurfaceControl mSurfaceControl;
     private boolean mIsInInputMode;
@@ -110,7 +109,7 @@
                             mSurfaceControl.onWindowFocusChanged(hasFocus(), isInTouchMode);
                         }
                     } catch (RemoteException e) {
-                        Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+                        Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
                     }
                 }
             };
@@ -119,18 +118,27 @@
         super(context, attrs, 0);
     }
 
-    public void setInputConnectionListener(
-            @Nullable IInputConnectionListener inputConnectionListener) {
-        mInputConnectionListener = inputConnectionListener;
+    /**
+     * Registers a {@link RotaryEventCallback} that is notified of rotary events.
+     */
+    public void registerRotaryEventCallback(@Nullable RotaryEventCallback callback) {
+        mRotaryEventCallback = callback;
     }
 
-    public void setRotaryEventListener(
-            @Nullable IRotaryEventListener rotaryEventListener) {
-        mRotaryEventListener = rotaryEventListener;
+    /**
+     * Registers a {@link OnCreateInputConnectionListener} that is notified of invocations on
+     * {@link #onCreateInputConnection(EditorInfo)}.
+     */
+    public void setOnCreateInputConnectionListener(
+            @Nullable OnCreateInputConnectionListener listener) {
+        mOnCreateInputConnectionListener = listener;
     }
 
-    public void setBackButtonListener(@Nullable IBackButtonListener backButtonListener) {
-        mBackButtonListener = backButtonListener;
+    /**
+     * Registers a {@link OnBackPressedListener} that is notified of back button presses.
+     */
+    public void setOnBackPressedListener(@Nullable OnBackPressedListener listener) {
+        mOnBackPressedListener = listener;
     }
 
     /**
@@ -165,7 +173,7 @@
                 mSurfaceControl.onWindowFocusChanged(gainFocus, isInTouchMode());
             }
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
         enableDirectManipulationMode(this, gainFocus);
     }
@@ -175,18 +183,18 @@
     public InputConnection onCreateInputConnection(@NonNull EditorInfo editorInfo) {
         requireNonNull(editorInfo);
 
-        if (!mIsInInputMode || mInputConnectionListener == null) {
+        if (!mIsInInputMode || mOnCreateInputConnectionListener == null) {
             return null;
         }
 
         try {
             IProxyInputConnection proxyInputConnection =
-                    mInputConnectionListener.onCreateInputConnection(editorInfo);
+                    mOnCreateInputConnectionListener.onCreateInputConnection(editorInfo);
 
             // Clear the input and return null if inputConnectionListener is null or there is no
             // open input connection on the host.
             if (proxyInputConnection == null) {
-                Log.e(LogTags.TAG_AAOS_HOST,
+                Log.e(TAG_AAOS_HOST,
                         "InputConnectionListener has not been received yet. Canceling the input");
                 onStopInput();
                 return null;
@@ -195,7 +203,7 @@
             return new RemoteProxyInputConnection(proxyInputConnection);
 
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
 
         return null;
@@ -282,16 +290,16 @@
     /**
      * Updates the surface package. The surface package can be either a
      * {@link android.view.SurfaceControlViewHost.SurfacePackage} or a
-     * {@link LegacySurfacePackage} wrapped in a {@link SurfacePackageCompat}.
+     * {@link LegacySurfacePackage} wrapped in a {@link SurfacePackageWrapper}.
      */
-    public void setSurfacePackage(@NonNull SurfacePackageCompat surfacePackageCompat) {
-        Parcelable surfacePackage = surfacePackageCompat.getSurfacePackage();
+    public void setSurfacePackage(@NonNull SurfacePackageWrapper surfacePackageWrapper) {
+        Parcelable surfacePackage = surfacePackageWrapper.getSurfacePackage();
         if (SUPPORTS_SURFACE_CONTROL && surfacePackage instanceof SurfacePackage) {
             Api30Impl.setSurfacePackage(this, (SurfacePackage) surfacePackage);
         } else if (surfacePackage instanceof LegacySurfacePackage) {
             setLegacySurfacePackage((LegacySurfacePackage) surfacePackage);
         } else {
-            Log.e(LogTags.TAG_AAOS_HOST, "Unrecognized surface package");
+            Log.e(TAG_AAOS_HOST, "Unrecognized surface package");
         }
     }
 
@@ -307,7 +315,7 @@
         try {
             surfaceControl.setSurfaceWrapper(surfaceWrapper);
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
             return;
         }
         mSurfaceControl = surfaceControl;
@@ -338,7 +346,7 @@
                 return true;
             }
         } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            Log.e(TAG_AAOS_HOST, "Remote connection lost", e);
         }
 
         return false;
@@ -346,17 +354,13 @@
 
     /** Passes the generic motion events to the host. */
     boolean handleGenericMotionEvent(@NonNull MotionEvent event) {
-        try {
-            if (requireNonNull(event).getActionMasked() == MotionEvent.ACTION_SCROLL) {
-                int steps = (int) event.getAxisValue(MotionEvent.AXIS_SCROLL);
-                boolean isClockwise = steps > 0;
-                if (mRotaryEventListener != null) {
-                    mRotaryEventListener.onRotate(steps, isClockwise);
-                }
-                return true;
+        if (requireNonNull(event).getActionMasked() == MotionEvent.ACTION_SCROLL) {
+            int steps = (int) event.getAxisValue(MotionEvent.AXIS_SCROLL);
+            boolean isClockwise = steps > 0;
+            if (mRotaryEventCallback != null) {
+                mRotaryEventCallback.onRotate(steps, isClockwise);
             }
-        } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+            return true;
         }
         return false;
     }
@@ -367,39 +371,35 @@
             return false;
         }
 
-        try {
-            switch (event.getKeyCode()) {
-                case KEYCODE_BACK:
-                    if (mBackButtonListener != null) {
-                        mBackButtonListener.onBackPressed();
-                        return true;
+        switch (event.getKeyCode()) {
+            case KEYCODE_BACK:
+                if (mOnBackPressedListener != null) {
+                    mOnBackPressedListener.onBackPressed();
+                    return true;
+                }
+                break;
+            case KEYCODE_DPAD_CENTER:
+                if (mRotaryEventCallback != null) {
+                    mRotaryEventCallback.onSelect();
+                    return true;
+                }
+                break;
+            case KEYCODE_DPAD_RIGHT:
+            case KEYCODE_DPAD_LEFT:
+            case KEYCODE_DPAD_UP:
+            case KEYCODE_DPAD_DOWN:
+                if (mRotaryEventCallback != null) {
+                    boolean success = mRotaryEventCallback.onNudge(event.getKeyCode());
+                    if (!success) {
+                        // Quit direct manipulation mode if the nudge event cannot be handled.
+                        enableDirectManipulationMode(this, false);
+                        return false;
                     }
-                    break;
-                case KEYCODE_DPAD_CENTER:
-                    if (mRotaryEventListener != null) {
-                        mRotaryEventListener.onSelect();
-                        return true;
-                    }
-                    break;
-                case KEYCODE_DPAD_RIGHT:
-                case KEYCODE_DPAD_LEFT:
-                case KEYCODE_DPAD_UP:
-                case KEYCODE_DPAD_DOWN:
-                    if (mRotaryEventListener != null) {
-                        boolean success = mRotaryEventListener.onNudge(event.getKeyCode());
-                        if (!success) {
-                            // Quit direct manipulation mode if the nudge event cannot be handled.
-                            enableDirectManipulationMode(this, false);
-                            return false;
-                        }
-                        return true;
-                    }
-                    break;
-                default:
-                    return false;
-            }
-        } catch (RemoteException e) {
-            Log.e(LogTags.TAG_AAOS_HOST, "Remote connection lost", e);
+                    return true;
+                }
+                break;
+            default:
+                return false;
         }
 
         return false;
diff --git a/car/app/app-aaos/src/test/java/androidx/car/app/aaos/CarAppActivityTest.java b/car/app/app-aaos/src/test/java/androidx/car/app/aaos/CarAppActivityTest.java
index 77e8f78..1adb428 100644
--- a/car/app/app-aaos/src/test/java/androidx/car/app/aaos/CarAppActivityTest.java
+++ b/car/app/app-aaos/src/test/java/androidx/car/app/aaos/CarAppActivityTest.java
@@ -54,16 +54,13 @@
 import android.view.inputmethod.EditorInfo;
 import android.view.inputmethod.InputConnection;
 
-import androidx.car.app.aaos.renderer.IBackButtonListener;
 import androidx.car.app.aaos.renderer.ICarAppActivity;
-import androidx.car.app.aaos.renderer.IInputConnectionListener;
-import androidx.car.app.aaos.renderer.ILifecycleListener;
 import androidx.car.app.aaos.renderer.IProxyInputConnection;
+import androidx.car.app.aaos.renderer.IRendererCallback;
 import androidx.car.app.aaos.renderer.IRendererService;
-import androidx.car.app.aaos.renderer.IRotaryEventListener;
 import androidx.car.app.aaos.renderer.surface.LegacySurfacePackage;
 import androidx.car.app.aaos.renderer.surface.SurfaceControlCallback;
-import androidx.car.app.aaos.renderer.surface.SurfacePackageCompat;
+import androidx.car.app.aaos.renderer.surface.SurfacePackageWrapper;
 import androidx.lifecycle.Lifecycle;
 import androidx.test.core.app.ActivityScenario;
 import androidx.test.core.app.ApplicationProvider;
@@ -164,22 +161,22 @@
         try (ActivityScenario<CarAppActivity> scenario = ActivityScenario.launch(
                 CarAppActivity.class)) {
             scenario.onActivity(activity -> {
-                ILifecycleListener listener = mock(ILifecycleListener.class);
+                IRendererCallback callback = mock(IRendererCallback.class);
                 try {
-                    mRenderServiceDelegate.getCarAppActivity().setLifecycleListener(listener);
-                    // Last observed event is reported as soon as listener is set.
-                    verify(listener, times(1)).onResume();
-                    // Verify lifecycle events are reported to registered listener.
+                    mRenderServiceDelegate.getCarAppActivity().registerRendererCallback(callback);
+                    // Last observed event is reported as soon as callback is set.
+                    verify(callback, times(1)).onResume();
+                    // Verify lifecycle events are reported to registered callback.
                     scenario.moveToState(Lifecycle.State.STARTED);
-                    verify(listener, times(1)).onPause();
+                    verify(callback, times(1)).onPause();
                     scenario.moveToState(Lifecycle.State.RESUMED);
-                    verify(listener, times(2)).onResume();
+                    verify(callback, times(2)).onResume();
                     scenario.moveToState(Lifecycle.State.CREATED);
-                    verify(listener, times(1)).onStop();
+                    verify(callback, times(1)).onStop();
                     scenario.moveToState(Lifecycle.State.CREATED);
-                    verify(listener, times(1)).onStop();
+                    verify(callback, times(1)).onStop();
                     scenario.moveToState(Lifecycle.State.DESTROYED);
-                    verify(listener, times(1)).onDestroyed();
+                    verify(callback, times(1)).onDestroyed();
                 } catch (RemoteException e) {
                     fail(Log.getStackTraceString(e));
                 }
@@ -194,14 +191,15 @@
                 CarAppActivity.class)) {
             scenario.onActivity(activity -> {
                 try {
-                    ILifecycleListener listener = mock(ILifecycleListener.class);
-                    mRenderServiceDelegate.getCarAppActivity().setLifecycleListener(listener);
-                    // Last observed event is reported as soon as listener is set.
-                    verify(listener, times(1)).onResume();
+                    IRendererCallback callback = mock(IRendererCallback.class);
+                    mRenderServiceDelegate.getCarAppActivity().registerRendererCallback(callback);
+                    // Last observed event is reported as soon as callback is set.
+                    verify(callback, times(1)).onResume();
 
-                    // Add a test-specific lifecycle listener to activity.
-                    ActivityLifecycleCallbacks callback = mock(ActivityLifecycleCallbacks.class);
-                    activity.registerActivityLifecycleCallbacks(callback);
+                    // Add a test-specific lifecycle callback to activity.
+                    ActivityLifecycleCallbacks activityCallback = mock(
+                            ActivityLifecycleCallbacks.class);
+                    activity.registerActivityLifecycleCallbacks(activityCallback);
                     // Report service connection error.
                     activity.onServiceConnectionError("fake error");
 
@@ -210,8 +208,8 @@
                     // After service connection error has been reported, test that lifecycle
                     // events are no longer reported to host lifecycle listener.
                     scenario.moveToState(Lifecycle.State.STARTED);
-                    verify(callback, times(1)).onActivityPaused(activity);
-                    verify(listener, times(0)).onPause();
+                    verify(activityCallback, times(1)).onActivityPaused(activity);
+                    verify(callback, times(0)).onPause();
                 } catch (RemoteException e) {
                     fail(Log.getStackTraceString(e));
                 }
@@ -220,16 +218,16 @@
     }
 
     @Test
-    public void testBackButtonListener() {
+    public void testOnBackPressed() {
         setupCarAppActivityForTesting();
         try (ActivityScenario<CarAppActivity> scenario = ActivityScenario.launch(
                 CarAppActivity.class)) {
             scenario.onActivity(activity -> {
                 try {
-                    IBackButtonListener listener = mock(IBackButtonListener.class);
-                    mRenderServiceDelegate.getCarAppActivity().setBackButtonListener(listener);
+                    IRendererCallback callback = mock(IRendererCallback.class);
+                    mRenderServiceDelegate.getCarAppActivity().registerRendererCallback(callback);
                     activity.onBackPressed();
-                    verify(listener, times(1)).onBackPressed();
+                    verify(callback, times(1)).onBackPressed();
                 } catch (RemoteException e) {
                     fail(Log.getStackTraceString(e));
                 }
@@ -272,19 +270,17 @@
             scenario.onActivity(activity -> {
                 try {
                     SurfaceControlCallback callback = mock(SurfaceControlCallback.class);
-                    IBackButtonListener backButtonListener = mock(IBackButtonListener.class);
-                    IRotaryEventListener rotaryEventListener = mock(IRotaryEventListener.class);
+                    IRendererCallback rendererCallback = mock(IRendererCallback.class);
 
-                    SurfacePackageCompat wrapper =
-                            new SurfacePackageCompat(new LegacySurfacePackage(callback));
+                    SurfacePackageWrapper wrapper =
+                            new SurfacePackageWrapper(new LegacySurfacePackage(callback));
                     ICarAppActivity carAppActivity = mRenderServiceDelegate.getCarAppActivity();
                     carAppActivity.setSurfacePackage(wrapper);
-                    carAppActivity.setBackButtonListener(backButtonListener);
-                    carAppActivity.setRotaryEventListener(rotaryEventListener);
+                    carAppActivity.registerRendererCallback(rendererCallback);
 
                     // Verify back events on surfaceView are sent to host.
                     activity.mSurfaceView.dispatchKeyEvent(new KeyEvent(ACTION_UP, KEYCODE_BACK));
-                    verify(backButtonListener, times(1)).onBackPressed();
+                    verify(rendererCallback, times(1)).onBackPressed();
 
                     // Verify focus request sent to host.
                     activity.mSurfaceView.requestFocus();
@@ -295,13 +291,13 @@
                     // Verify rotary events on surfaceView are sent to host.
                     activity.mSurfaceView.dispatchKeyEvent(
                             new KeyEvent(ACTION_UP, KEYCODE_DPAD_RIGHT));
-                    verify(rotaryEventListener, times(1)).onNudge(KEYCODE_DPAD_RIGHT);
+                    verify(rendererCallback, times(1)).onNudge(KEYCODE_DPAD_RIGHT);
                     activity.mSurfaceView.dispatchKeyEvent(
                             new KeyEvent(ACTION_UP, KEYCODE_DPAD_DOWN));
-                    verify(rotaryEventListener, times(1)).onNudge(KEYCODE_DPAD_DOWN);
+                    verify(rendererCallback, times(1)).onNudge(KEYCODE_DPAD_DOWN);
                     activity.mSurfaceView.dispatchKeyEvent(
                             new KeyEvent(ACTION_UP, KEYCODE_DPAD_CENTER));
-                    verify(rotaryEventListener, times(1)).onSelect();
+                    verify(rendererCallback, times(1)).onSelect();
 
                     long downTime = SystemClock.uptimeMillis();
                     long eventTime = SystemClock.uptimeMillis();
@@ -323,7 +319,7 @@
                     event = MotionEvent.obtain(downTime, eventTime, MotionEvent.ACTION_SCROLL, x,
                             y, metaState);
                     activity.mSurfaceView.dispatchGenericMotionEvent(event);
-                    verify(rotaryEventListener, times(1)).onRotate(anyInt(), eq(false));
+                    verify(rendererCallback, times(1)).onRotate(anyInt(), eq(false));
                 } catch (RemoteException e) {
                     fail(Log.getStackTraceString(e));
                 }
@@ -339,12 +335,12 @@
             scenario.onActivity(activity -> {
                 try {
                     EditorInfo editorInfo = new EditorInfo();
-                    IInputConnectionListener listener = mock(IInputConnectionListener.class);
+                    IRendererCallback callback = mock(IRendererCallback.class);
                     IProxyInputConnection inputConnection = mock(IProxyInputConnection.class);
-                    when(listener.onCreateInputConnection(any())).thenReturn(inputConnection);
+                    when(callback.onCreateInputConnection(any())).thenReturn(inputConnection);
                     when(inputConnection.getEditorInfo()).thenReturn(editorInfo);
 
-                    mRenderServiceDelegate.getCarAppActivity().setInputConnectionListener(listener);
+                    mRenderServiceDelegate.getCarAppActivity().registerRendererCallback(callback);
                     // Create input connection without first calling ICarAppActivity#startInput().
                     InputConnection remoteProxyInputConnection =
                             activity.mSurfaceView.onCreateInputConnection(editorInfo);
@@ -365,12 +361,12 @@
             scenario.onActivity(activity -> {
                 try {
                     EditorInfo editorInfo = new EditorInfo();
-                    IInputConnectionListener listener = mock(IInputConnectionListener.class);
+                    IRendererCallback callback = mock(IRendererCallback.class);
                     IProxyInputConnection inputConnection = mock(IProxyInputConnection.class);
-                    when(listener.onCreateInputConnection(any())).thenReturn(inputConnection);
+                    when(callback.onCreateInputConnection(any())).thenReturn(inputConnection);
                     when(inputConnection.getEditorInfo()).thenReturn(editorInfo);
 
-                    mRenderServiceDelegate.getCarAppActivity().setInputConnectionListener(listener);
+                    mRenderServiceDelegate.getCarAppActivity().registerRendererCallback(callback);
                     mRenderServiceDelegate.getCarAppActivity().onStartInput();
                     InputConnection remoteProxyInputConnection =
                             activity.mSurfaceView.onCreateInputConnection(editorInfo);
diff --git a/car/app/app/src/main/java/androidx/car/app/AppInfo.java b/car/app/app/src/main/java/androidx/car/app/AppInfo.java
index 86a9a92..185f585 100644
--- a/car/app/app/src/main/java/androidx/car/app/AppInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/AppInfo.java
@@ -16,7 +16,7 @@
 
 package androidx.car.app;
 
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 
 import static java.util.Objects.requireNonNull;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java b/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
index 70ce7e9..c3cad0d 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarAppPermission.java
@@ -16,7 +16,7 @@
 
 package androidx.car.app;
 
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 
 import android.content.Context;
 import android.content.pm.PackageInfo;
@@ -26,7 +26,6 @@
 
 import androidx.annotation.NonNull;
 import androidx.annotation.StringDef;
-import androidx.car.app.utils.CommonUtils;
 
 import java.lang.annotation.Retention;
 import java.lang.annotation.RetentionPolicy;
@@ -129,8 +128,7 @@
                 }
             }
         } catch (NameNotFoundException e) {
-            Log.e(CommonUtils.TAG,
-                    "Package name not found on the system: " + context.getPackageName(), e);
+            Log.e(TAG, "Package name not found on the system: " + context.getPackageName(), e);
         }
         throw new SecurityException(
                 "The car app does not have a required permission: " + permission);
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 cc77a18..64cf178 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
@@ -20,7 +20,7 @@
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 
 import static java.util.Objects.requireNonNull;
 
@@ -157,9 +157,7 @@
      * @param name The name of the car service requested. This should be one of
      *             {@link #APP_SERVICE},
      *             {@link #NAVIGATION_SERVICE} or {@link #SCREEN_SERVICE}
-     *
      * @return The car service instance
-     *
      * @throws IllegalArgumentException if {@code name} does not refer to a valid car service
      * @throws NullPointerException     if {@code name} is {@code null}
      */
@@ -187,7 +185,6 @@
      * ScreenManager}.
      *
      * @param serviceClass the class of the requested service
-     *
      * @throws IllegalArgumentException if {@code serviceClass} is not the class of a supported car
      *                                  service
      * @throws NullPointerException     if {@code serviceClass} is {@code null}
@@ -201,13 +198,10 @@
      * Gets the name of the car service that is represented by the specified class.
      *
      * @param serviceClass the class of the requested service
-     *
      * @return the car service name to use with {@link #getCarService(String)}
-     *
      * @throws IllegalArgumentException if {@code serviceClass} is not the class of a supported car
      *                                  service
      * @throws NullPointerException     if {@code serviceClass} is {@code null}
-     *
      * @see #getCarService
      */
     @NonNull
@@ -252,7 +246,6 @@
      * </dl>
      *
      * @param intent the {@link Intent} to send to the target application
-     *
      * @throws SecurityException         if the app attempts to start a different app explicitly or
      *                                   does not have permissions for the requested action
      * @throws InvalidParameterException if {@code intent} does not meet the criteria defined
@@ -280,7 +273,6 @@
      * @param appIntent          the {@link Intent} to use for starting the car app. See {@link
      *                           #startCarApp(Intent)} for the documentation on valid
      *                           {@link Intent}s
-     *
      * @throws InvalidParameterException if {@code notificationIntent} is not an {@link Intent}
      *                                   received from a broadcast, due to an action taken by the
      *                                   user in the car
@@ -475,7 +467,6 @@
      * @return a value between {@link AppInfo#getMinCarAppApiLevel()} and
      * {@link AppInfo#getLatestCarAppApiLevel()}. In case of incompatibility, the host will
      * disconnect from the service before completing the handshake
-     *
      * @throws IllegalStateException if invoked before the connection handshake with the host has
      *                               been completed (for example, before
      *                               {@link Session#onCreateScreen(Intent)})
diff --git a/car/app/app/src/main/java/androidx/car/app/CarToast.java b/car/app/app/src/main/java/androidx/car/app/CarToast.java
index 422cc33..7db14a4 100644
--- a/car/app/app/src/main/java/androidx/car/app/CarToast.java
+++ b/car/app/app/src/main/java/androidx/car/app/CarToast.java
@@ -77,7 +77,6 @@
      *                  text will be set to empty
      * @param duration  how long to display the message. Either {@link #LENGTH_SHORT} or {@link
      *                  #LENGTH_LONG}
-     *
      * @throws NullPointerException if {@code carContext} is {@code null}
      */
     @NonNull
@@ -95,7 +94,6 @@
      * @param text     the text to show
      * @param duration how long to display the message. Either {@link #LENGTH_SHORT} or {@link
      *                 #LENGTH_LONG}
-     *
      * @throws NullPointerException if either the {@code carContext} or the {@code text} are {@code
      *                              null}
      */
diff --git a/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java b/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
index 5aee696..ff206e3 100644
--- a/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
+++ b/car/app/app/src/main/java/androidx/car/app/HostDispatcher.java
@@ -55,7 +55,6 @@
      * @param hostType the service to dispatch to
      * @param call     the request to dispatch
      * @param callName the name of the call for logging purposes
-     *
      * @throws SecurityException if the host has thrown it
      * @throws HostException     if the host throws any exception other than
      *                           {@link SecurityException}
diff --git a/car/app/app/src/main/java/androidx/car/app/OnScreenResultListener.java b/car/app/app/src/main/java/androidx/car/app/OnScreenResultListener.java
index d7b4eb8f..41422f3 100644
--- a/car/app/app/src/main/java/androidx/car/app/OnScreenResultListener.java
+++ b/car/app/app/src/main/java/androidx/car/app/OnScreenResultListener.java
@@ -31,7 +31,6 @@
      *
      * @param result the result provided by the {@link Screen} that was pushed using {@link
      *               ScreenManager#pushForResult} or {@code null} if no result was set
-     *
      * @see Screen#setResult
      */
     void onScreenResult(@Nullable Object result);
diff --git a/car/app/app/src/main/java/androidx/car/app/Screen.java b/car/app/app/src/main/java/androidx/car/app/Screen.java
index 254cddf..2df5d5e 100644
--- a/car/app/app/src/main/java/androidx/car/app/Screen.java
+++ b/car/app/app/src/main/java/androidx/car/app/Screen.java
@@ -17,7 +17,7 @@
 package androidx.car.app;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 
 import static java.util.Objects.requireNonNull;
 
diff --git a/car/app/app/src/main/java/androidx/car/app/ScreenManager.java b/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
index d017ad5..8dc452d 100644
--- a/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/ScreenManager.java
@@ -17,7 +17,7 @@
 package androidx.car.app;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 import static androidx.car.app.utils.ThreadUtils.checkMainThread;
 
 import static java.util.Objects.requireNonNull;
@@ -91,7 +91,6 @@
      * @param onScreenResultListener the listener that will be executed with the result pushed by
      *                               the {@code screen} through {@link Screen#setResult}. This
      *                               callback will be executed on the main thread
-     *
      * @throws NullPointerException  if either the {@code screen} or the {@code
      *                               onScreenResultCallback} are {@code null}
      * @throws IllegalStateException if the current thread is not the main thread
@@ -126,7 +125,6 @@
      *
      * @throws NullPointerException  if {@code marker} is {@code null}
      * @throws IllegalStateException if the current thread is not the main thread
-     *
      * @see Screen#setMarker
      */
     public void popTo(@NonNull String marker) {
diff --git a/car/app/app/src/main/java/androidx/car/app/Session.java b/car/app/app/src/main/java/androidx/car/app/Session.java
index da71ee3..abf1512 100644
--- a/car/app/app/src/main/java/androidx/car/app/Session.java
+++ b/car/app/app/src/main/java/androidx/car/app/Session.java
@@ -64,7 +64,6 @@
      * @param intent the intent that was used to start this app. If the app was started with a
      *               call to {@link CarContext#startCarApp}, this intent will be equal to the
      *               intent passed to that method
-     *
      * @see CarContext#startCarApp
      */
     public void onNewIntent(@NonNull Intent intent) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Action.java b/car/app/app/src/main/java/androidx/car/app/model/Action.java
index 7e7150d..d749c07 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Action.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Action.java
@@ -286,7 +286,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java b/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
index 211c872..fb81efe 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarIconSpan.java
@@ -123,10 +123,8 @@
      * @param alignment the alignment of the {@link CarIcon} relative to the text. This should be
      *                  one of {@link #ALIGN_BASELINE}, {@link #ALIGN_BOTTOM} or
      *                  {@link #ALIGN_CENTER}
-     *
      * @throws NullPointerException     if {@code icon} is {@code null}
      * @throws IllegalArgumentException if {@code alignment} is not a valid value
-     *
      * @see #ALIGN_BASELINE
      * @see #ALIGN_BOTTOM
      * @see #ALIGN_CENTER
diff --git a/car/app/app/src/main/java/androidx/car/app/model/CarText.java b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
index 135123b..f30e582 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/CarText.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/CarText.java
@@ -63,7 +63,6 @@
  * <p>The {@link CarText#toString} method can be used to get a string representation of the string,
  * whereas the {@link CarText#toCharSequence()} method returns the reconstructed
  * {@link CharSequence}, with the non{@link CarSpan} spans removed.
- *
  */
 public final class CarText {
     @Keep
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Distance.java b/car/app/app/src/main/java/androidx/car/app/model/Distance.java
index 7787185..416873fe 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Distance.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Distance.java
@@ -117,7 +117,6 @@
      * @param displayUnit     the unit of distance to use when displaying the value in {@code
      *                        displayUnit}. This should be one of the {@code UNIT_*} static
      *                        constants defined in this class. See {@link #getDisplayUnit()}
-     *
      * @throws IllegalArgumentException if {@code displayDistance} is negative
      */
     @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
index cebf436..640fd1c 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/GridItem.java
@@ -270,7 +270,6 @@
          * Sets an image to show in the grid item with the default size {@link #IMAGE_TYPE_LARGE}.
          *
          * @throws NullPointerException if {@code image} is {@code null}
-         *
          * @see #setImage(CarIcon, int)
          */
         @NonNull
@@ -297,7 +296,6 @@
          *
          * @param image     the {@link CarIcon} to display
          * @param imageType one of {@link #IMAGE_TYPE_ICON} or {@link #IMAGE_TYPE_LARGE}
-         *
          * @throws NullPointerException if {@code image} is {@code null}
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
index 5ea1d970..e344f9b 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/ItemList.java
@@ -260,7 +260,6 @@
          * radio button group, while others may highlight the selected item's background.
          *
          * @throws NullPointerException if {@code onSelectedListener} is {@code null}
-         *
          * @see #setSelectedIndex(int)
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
index fb5150b..ff13005 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/MessageTemplate.java
@@ -196,7 +196,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -338,7 +337,6 @@
          * Returns a {@link Builder} instance.
          *
          * @param message the text message to display in the template
-         *
          * @throws NullPointerException if the {@code message} is {@code null}
          */
         public Builder(@NonNull CharSequence message) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
index 8a9e32e..7fb8b85 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PaneTemplate.java
@@ -160,7 +160,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Place.java b/car/app/app/src/main/java/androidx/car/app/model/Place.java
index 2442464..8706e46 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Place.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Place.java
@@ -101,7 +101,6 @@
          * Returns a builder instance for a {@link CarLocation}.
          *
          * @param location the geographical location associated with the place
-         *
          * @throws NullPointerException if {@code location} is {@code null}
          */
         public Builder(@NonNull CarLocation location) {
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java b/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
index ea9299d..5eb6f9e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PlaceListMapTemplate.java
@@ -263,7 +263,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java b/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
index 9497000..b7ca8fa 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/PlaceMarker.java
@@ -196,7 +196,6 @@
          *
          * @param icon     the {@link CarIcon} to display inside the marker
          * @param iconType one of {@link #TYPE_ICON} or {@link #TYPE_IMAGE}
-         *
          * @throws NullPointerException if the {@code icon} is {@code null}
          */
         @NonNull
@@ -219,9 +218,7 @@
          * @param label the text to display inside of the marker. The string must have a maximum
          *              size of 3 characters. Set to {@code null} to let the host choose a
          *              labelling scheme (for example, using a sequence of numbers)
-         *
          * @throws NullPointerException if the {@code label} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/model/Row.java b/car/app/app/src/main/java/androidx/car/app/model/Row.java
index 6b4e055..47aae4e 100644
--- a/car/app/app/src/main/java/androidx/car/app/model/Row.java
+++ b/car/app/app/src/main/java/androidx/car/app/model/Row.java
@@ -371,7 +371,6 @@
          * </pre>
          *
          * @throws NullPointerException if {@code text} is {@code null}
-         *
          * @see ForegroundCarColorSpan
          */
         @NonNull
@@ -384,7 +383,6 @@
          * Sets an image to show in the row with the default size {@link #IMAGE_TYPE_SMALL}.
          *
          * @throws NullPointerException if {@code image} is {@code null}
-         *
          * @see #setImage(CarIcon, int)
          */
         @NonNull
@@ -412,7 +410,6 @@
          * @param image     the {@link CarIcon} to display or {@code null} to not display one
          * @param imageType one of {@link #IMAGE_TYPE_ICON}, {@link #IMAGE_TYPE_SMALL} or {@link
          *                  #IMAGE_TYPE_LARGE}
-         *
          * @throws NullPointerException if {@code image} is {@code null}
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
index c5c7624..763335c 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/NavigationManager.java
@@ -94,7 +94,6 @@
      * displays the associated icon may be shown.
      *
      * @param trip destination, steps, and trip estimates to be sent to the host
-     *
      * @throws HostException            if the call is invoked by an app that is not declared as
      *                                  a navigation app in the manifest
      * @throws IllegalStateException    if the call occurs when navigation is not started (see
@@ -134,7 +133,6 @@
      * {@link #setNavigationManagerCallback(Executor, NavigationManagerCallback)}.
      *
      * @param callback the {@link NavigationManagerCallback} to use
-     *
      * @throws IllegalStateException if the current thread is not the main thread
      */
     @SuppressLint("ExecutorRegistration")
@@ -150,7 +148,6 @@
      *
      * @param executor the executor which will be used for invoking the callback
      * @param callback the {@link NavigationManagerCallback} to use
-     *
      * @throws IllegalStateException if the current thread is not the main thread
      */
     @MainThread
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
index eda9390..4683cb0 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Destination.java
@@ -130,7 +130,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code name} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -145,7 +144,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code address} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -182,7 +180,6 @@
          * <p>At least one of the name or the address must be set and not empty.
          *
          * @throws IllegalStateException if both the name and the address are {@code null} or empty.
-         *
          * @see #setName(CharSequence)
          * @see #setAddress(CharSequence)
          */
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
index f822633..f4a06f2 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Maneuver.java
@@ -587,7 +587,6 @@
          * units will not display any information in that case.
          *
          * @param type one of the {@code TYPE_*} static constants defined in this class
-         *
          * @throws IllegalArgumentException if {@code type} is not a valid maneuver type
          */
         public Builder(@Type int type) {
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
index 3cb430b..186bb9d 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/MessageInfo.java
@@ -126,7 +126,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code message} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -143,7 +142,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code text} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
index 4948cca..8c8833d 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/PlaceListNavigationTemplate.java
@@ -197,7 +197,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is null
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
index d5f4bb3..66a0da6 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/RoutePreviewNavigationTemplate.java
@@ -224,7 +224,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code title} is null
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
index df4af78..3c52e43 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Step.java
@@ -185,7 +185,6 @@
          * unsupported characters will not be displayed properly.
          *
          * @throws NullPointerException if {@code cue} is {@code null}
-         *
          * @see Builder#setCue(CharSequence)
          */
         public Builder(@NonNull CharSequence cue) {
@@ -284,7 +283,6 @@
          * that work with different car screen pixel densities.
          *
          * @throws NullPointerException if {@code cue} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -304,7 +302,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code destinations} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
index 024ad74..608feac 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/TravelEstimate.java
@@ -171,7 +171,6 @@
          *                                 arriving at the destination
          * @param arrivalTimeAtDestination The arrival time with the time zone information
          *                                 provided for the destination
-         *
          * @throws NullPointerException if {@code remainingDistance} or
          *                              {@code arrivalTimeAtDestination} are {@code null}
          */
@@ -189,7 +188,6 @@
          *                                 arriving at the destination
          * @param arrivalTimeAtDestination The arrival time with the time zone information
          *                                 provided for the destination
-         *
          * @throws NullPointerException if {@code remainingDistance} or
          *                              {@code arrivalTimeAtDestination} are {@code null}
          */
diff --git a/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java b/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
index 18b45a6..1e2ed08 100644
--- a/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
+++ b/car/app/app/src/main/java/androidx/car/app/navigation/model/Trip.java
@@ -234,7 +234,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code currentRoad} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
diff --git a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
index dcb933a..02fb8fc 100644
--- a/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
+++ b/car/app/app/src/main/java/androidx/car/app/notification/CarAppExtender.java
@@ -399,7 +399,6 @@
          * <p>Spans are not supported in the input string.
          *
          * @throws NullPointerException if {@code contentTitle} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -418,9 +417,7 @@
          *
          * @param contentText override for the notification's content text. If set to an empty
          *                    string, it will be treated as if there is no context text
-         *
          * @throws NullPointerException if {@code contentText} is {@code null}
-         *
          * @see CarText for details on text handling and span support.
          */
         @NonNull
@@ -475,7 +472,6 @@
          * {@link NotificationCompat.Builder#setContentIntent(PendingIntent)} for the car screen.
          *
          * @param contentIntent override for the notification's content intent.
-         *
          * @throws NullPointerException if {@code contentIntent} is {@code null}
          */
         @NonNull
@@ -495,7 +491,6 @@
          * {@link NotificationCompat.Builder#setDeleteIntent(PendingIntent)} for the car screen.
          *
          * @param deleteIntent override for the notification's delete intent
-         *
          * @throws NullPointerException if {@code deleteIntent} is {@code null}
          */
         @NonNull
@@ -529,7 +524,6 @@
          *               navigation notifications in the rail widget, this intent will be sent
          *               when the user taps on the action icon in the rail
          *               widget
-         *
          * @throws NullPointerException if {@code title} or {@code intent} are {@code null}
          */
         @SuppressWarnings("deprecation")
diff --git a/car/app/app/src/main/java/androidx/car/app/serialization/BundlerException.java b/car/app/app/src/main/java/androidx/car/app/serialization/BundlerException.java
index cae4434..42691f5 100644
--- a/car/app/app/src/main/java/androidx/car/app/serialization/BundlerException.java
+++ b/car/app/app/src/main/java/androidx/car/app/serialization/BundlerException.java
@@ -26,6 +26,7 @@
     public BundlerException(@Nullable String msg, @NonNull Throwable e) {
         super(msg, e);
     }
+
     public BundlerException(@Nullable String msg) {
         super(msg);
     }
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/CommonUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/LogTags.java
similarity index 84%
rename from car/app/app/src/main/java/androidx/car/app/utils/CommonUtils.java
rename to car/app/app/src/main/java/androidx/car/app/utils/LogTags.java
index 3ddd55e..1ffe2b0 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/CommonUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/utils/LogTags.java
@@ -21,18 +21,18 @@
 import androidx.annotation.RestrictTo;
 
 /**
- * Assorted common utilities.
+ * Declares the log tags to use in the library.
  *
  * @hide
  */
 @RestrictTo(LIBRARY)
-public final class CommonUtils {
+public final class LogTags {
     /** Tag to use for logging in the library. */
     public static final String TAG = "CarApp";
 
     /** Tag to use for host validation */
-    public static final String TAG_HOST_VALIDATION = "CarApp.Val";
+    public static final String TAG_HOST_VALIDATION = TAG + ".Val";
 
-    private CommonUtils() {
+    private LogTags() {
     }
 }
diff --git a/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java b/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
index 61b4585..6c2c042 100644
--- a/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
+++ b/car/app/app/src/main/java/androidx/car/app/utils/RemoteUtils.java
@@ -17,7 +17,7 @@
 package androidx.car.app.utils;
 
 import static androidx.annotation.RestrictTo.Scope.LIBRARY;
-import static androidx.car.app.utils.CommonUtils.TAG;
+import static androidx.car.app.utils.LogTags.TAG;
 
 import android.annotation.SuppressLint;
 import android.graphics.Rect;
diff --git a/car/app/app/src/main/java/androidx/car/app/validation/HostValidator.java b/car/app/app/src/main/java/androidx/car/app/validation/HostValidator.java
index cd11784..346da8a 100644
--- a/car/app/app/src/main/java/androidx/car/app/validation/HostValidator.java
+++ b/car/app/app/src/main/java/androidx/car/app/validation/HostValidator.java
@@ -16,7 +16,7 @@
 
 package androidx.car.app.validation;
 
-import static androidx.car.app.utils.CommonUtils.TAG_HOST_VALIDATION;
+import static androidx.car.app.utils.LogTags.TAG_HOST_VALIDATION;
 
 import static java.util.Objects.requireNonNull;
 
@@ -375,7 +375,6 @@
          * package-name formatting.
          *
          * @param allowListedHostsRes string-array resource identifier
-         *
          * @throws IllegalArgumentException if the provided resource doesn't exist or if the entries
          *                                  in the given resource are not formatted as expected
          */
diff --git a/compose/animation/animation-core/api/1.0.0-beta02.txt b/compose/animation/animation-core/api/1.0.0-beta02.txt
index cc3acca..2016497 100644
--- a/compose/animation/animation-core/api/1.0.0-beta02.txt
+++ b/compose/animation/animation-core/api/1.0.0-beta02.txt
@@ -603,6 +603,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/api/current.txt b/compose/animation/animation-core/api/current.txt
index cc3acca..2016497 100644
--- a/compose/animation/animation-core/api/current.txt
+++ b/compose/animation/animation-core/api/current.txt
@@ -603,6 +603,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta02.txt b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta02.txt
index cc3acca..2016497 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta02.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_1.0.0-beta02.txt
@@ -603,6 +603,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/api/public_plus_experimental_current.txt b/compose/animation/animation-core/api/public_plus_experimental_current.txt
index cc3acca..2016497 100644
--- a/compose/animation/animation-core/api/public_plus_experimental_current.txt
+++ b/compose/animation/animation-core/api/public_plus_experimental_current.txt
@@ -603,6 +603,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/api/restricted_1.0.0-beta02.txt b/compose/animation/animation-core/api/restricted_1.0.0-beta02.txt
index a4a2771..7df77c6 100644
--- a/compose/animation/animation-core/api/restricted_1.0.0-beta02.txt
+++ b/compose/animation/animation-core/api/restricted_1.0.0-beta02.txt
@@ -606,6 +606,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/api/restricted_current.txt b/compose/animation/animation-core/api/restricted_current.txt
index a4a2771..7df77c6 100644
--- a/compose/animation/animation-core/api/restricted_current.txt
+++ b/compose/animation/animation-core/api/restricted_current.txt
@@ -606,6 +606,9 @@
 
   public final class VectorizedInfiniteRepeatableSpec<V extends androidx.compose.animation.core.AnimationVector> implements androidx.compose.animation.core.VectorizedAnimationSpec<V> {
     ctor public VectorizedInfiniteRepeatableSpec(androidx.compose.animation.core.VectorizedDurationBasedAnimationSpec<V> animation, androidx.compose.animation.core.RepeatMode repeatMode);
+    method public long getDurationNanos(V initialValue, V targetValue, V initialVelocity);
+    method public V getValueFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
+    method public V getVelocityFromNanos(long playTimeNanos, V initialValue, V targetValue, V initialVelocity);
     method public boolean isInfinite();
     property public boolean isInfinite;
   }
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt
index 6f69027..5f51eda 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/Animatable.kt
@@ -170,7 +170,7 @@
         if (!isRunning) {
             val clampedValue = clampToBounds(value)
             if (clampedValue != value) {
-                this.internalState.value = value
+                this.internalState.value = clampedValue
             }
         }
     }
@@ -318,7 +318,7 @@
     private fun clampToBounds(value: T): T {
         if (
             lowerBoundVector == negativeInfinityBounds &&
-            upperBoundVector == negativeInfinityBounds
+            upperBoundVector == positiveInfinityBounds
         ) {
             // Expect this to be the most common use case
             return value
@@ -351,7 +351,11 @@
     /**
      * Sets the current value to the target value, without any animation. This will also cancel any
      * on-going animation with a [CancellationException]. This function will return *after*
-     * canceling any on-going animation and updating the [value] to the provided [targetValue].
+     * canceling any on-going animation and updating the [Animatable.value] and
+     * [Animatable.targetValue] to the provided [targetValue].
+     *
+     * __Note__: If the [lowerBound] or [upperBound] is specified, the provided [targetValue]
+     * will be clamped to the bounds to ensure [Animatable.value] is always within bounds.
      *
      * See [animateTo] and [animateDecay] for more details about animation being canceled.
      *
@@ -364,8 +368,9 @@
     suspend fun snapTo(targetValue: T) {
         mutatorMutex.mutate {
             endAnimation()
-            internalState.value = targetValue
-            this.targetValue = targetValue
+            val clampedValue = clampToBounds(targetValue)
+            internalState.value = clampedValue
+            this.targetValue = clampedValue
         }
     }
 
diff --git a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
index f9d455c..0912403 100644
--- a/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
+++ b/compose/animation/animation-core/src/commonMain/kotlin/androidx/compose/animation/core/VectorizedAnimationSpec.kt
@@ -360,9 +360,68 @@
 class VectorizedInfiniteRepeatableSpec<V : AnimationVector>(
     private val animation: VectorizedDurationBasedAnimationSpec<V>,
     private val repeatMode: RepeatMode = RepeatMode.Restart
-) : VectorizedAnimationSpec<V> by
-    VectorizedRepeatableSpec<V>(InfiniteIterations, animation, repeatMode) {
+) : VectorizedAnimationSpec<V> {
     override val isInfinite: Boolean get() = true
+
+    /**
+     * Single iteration duration
+     */
+    internal val durationNanos: Long =
+        (animation.delayMillis + animation.durationMillis) * MillisToNanos
+
+    private fun repetitionPlayTimeNanos(playTimeNanos: Long): Long {
+        val repeatsCount = playTimeNanos / durationNanos
+        if (repeatMode == RepeatMode.Restart || repeatsCount % 2 == 0L) {
+            return playTimeNanos - repeatsCount * durationNanos
+        } else {
+            return (repeatsCount + 1) * durationNanos - playTimeNanos
+        }
+    }
+
+    private fun repetitionStartVelocity(
+        playTimeNanos: Long,
+        start: V,
+        startVelocity: V,
+        end: V
+    ): V = if (playTimeNanos > durationNanos) {
+        // Start velocity of the 2nd and subsequent iteration will be the velocity at the end
+        // of the first iteration, instead of the initial velocity.
+        getVelocityFromNanos(durationNanos, start, startVelocity, end)
+    } else {
+        startVelocity
+    }
+
+    override fun getValueFromNanos(
+        playTimeNanos: Long,
+        initialValue: V,
+        targetValue: V,
+        initialVelocity: V
+    ): V {
+        return animation.getValueFromNanos(
+            repetitionPlayTimeNanos(playTimeNanos),
+            initialValue,
+            targetValue,
+            repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
+        )
+    }
+
+    override fun getVelocityFromNanos(
+        playTimeNanos: Long,
+        initialValue: V,
+        targetValue: V,
+        initialVelocity: V
+    ): V {
+        return animation.getVelocityFromNanos(
+            repetitionPlayTimeNanos(playTimeNanos),
+            initialValue,
+            targetValue,
+            repetitionStartVelocity(playTimeNanos, initialValue, initialVelocity, targetValue)
+        )
+    }
+
+    @Suppress("MethodNameUnits")
+    override fun getDurationNanos(initialValue: V, targetValue: V, initialVelocity: V): Long =
+        Long.MAX_VALUE
 }
 
 /**
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt
index e97bd85..78a236a 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/AnimatableTest.kt
@@ -264,4 +264,34 @@
             }
         }
     }
+
+    @Test
+    fun testUpdateBounds() {
+        val animatable = Animatable(5f)
+        // Update bounds when *not* running
+        animatable.updateBounds(0f, 4f)
+        assertEquals(4f, animatable.value)
+        runBlocking {
+            val clock = SuspendAnimationTest.TestFrameClock()
+            // Put two frames in clock
+            clock.frame(0L)
+            clock.frame(200 * 1_000_000L)
+
+            withContext(clock) {
+                animatable.animateTo(4f, tween(100)) {
+                    if (animatable.upperBound == 4f) {
+                        // Update bounds while running
+                        animatable.updateBounds(-4f, 0f)
+                    }
+                }
+            }
+        }
+        assertEquals(0f, animatable.value)
+
+        // Snap to value out of bounds
+        runBlocking {
+            animatable.snapTo(animatable.lowerBound!! - 100f)
+        }
+        assertEquals(animatable.lowerBound!!, animatable.value)
+    }
 }
diff --git a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
index 30ccaf0..2761e91 100644
--- a/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
+++ b/compose/animation/animation-core/src/test/java/androidx/compose/animation/core/RepeatableAnimationTest.kt
@@ -18,6 +18,7 @@
 
 import com.google.common.truth.Truth.assertThat
 import junit.framework.TestCase.assertEquals
+import junit.framework.TestCase.assertFalse
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
@@ -25,8 +26,6 @@
 @RunWith(JUnit4::class)
 class RepeatableAnimationTest {
 
-    private val Animation = TweenSpec<AnimationVector1D>(durationMillis = Duration)
-
     private val DelayedAnimation = VectorizedTweenSpec<AnimationVector1D>(
         delayMillis = DelayDuration,
         durationMillis = Duration
@@ -63,13 +62,13 @@
             animation = DelayedAnimation
         )
 
-        val duration = repeat.getDurationMillis(
+        val duration = repeat.getDurationNanos(
             AnimationVector1D(0f),
             AnimationVector1D(0f),
             AnimationVector1D(0f)
         )
 
-        assertEquals((DelayDuration + Duration) * iters.toLong(), duration)
+        assertEquals((DelayDuration + Duration) * iters * MillisToNanos, duration)
     }
 
     @Test
@@ -106,20 +105,71 @@
 
     @Test
     fun testInfiniteRepeat() {
-        val repeat = infiniteRepeatable(
+        val repeatShortAnimation = infiniteRepeatable(
             animation = TweenSpec<Float>(
                 durationMillis = 100, easing = LinearEasing
             ),
             repeatMode = RepeatMode.Reverse
         )
 
+        val extraLongDurationNanos = 1000000000
+        val repeatLongAnimation = infiniteRepeatable(
+            animation = TweenSpec<Float>(
+                durationMillis = extraLongDurationNanos, easing = LinearEasing
+            ),
+            repeatMode = RepeatMode.Restart
+        )
+        val vectorizedInfiniteRepeatingShort = repeatShortAnimation.vectorize(Float.VectorConverter)
+        val vectorizedInfiniteRepeatingLong = repeatLongAnimation.vectorize(Float.VectorConverter)
+
         assertEquals(
-            Int.MAX_VALUE.toLong() * 100,
-            repeat.vectorize(Float.VectorConverter).getDurationMillis(
-                AnimationVector(0f),
-                AnimationVector(100f),
-                AnimationVector(0f)
-            )
+            Long.MAX_VALUE,
+            vectorizedInfiniteRepeatingShort
+                .getDurationNanos(
+                    AnimationVector(0f),
+                    AnimationVector(100f),
+                    AnimationVector(0f)
+                )
+        )
+
+        assertEquals(
+            Long.MAX_VALUE,
+            vectorizedInfiniteRepeatingLong
+                .getDurationNanos(
+                    AnimationVector(0f),
+                    AnimationVector(100f),
+                    AnimationVector(0f)
+                )
+        )
+
+        val repeatShort = TargetBasedAnimation(
+            repeatShortAnimation,
+            Float.VectorConverter,
+            0f,
+            100f
+        )
+        val repeatLong = TargetBasedAnimation(
+            repeatLongAnimation,
+            Float.VectorConverter,
+            0f,
+            extraLongDurationNanos.toFloat()
+        )
+
+        assertEquals(repeatShort.durationNanos, Long.MAX_VALUE)
+        assertEquals(repeatLong.durationNanos, Long.MAX_VALUE)
+        assertFalse(repeatShort.isFinishedFromNanos(100000000000000000L))
+        assertFalse(repeatShort.isFinishedFromNanos(100000000000000000L))
+
+        // Also check on repeating value. Repeat mode: reverse
+        assertEquals(31f, repeatShort.getValueFromNanos(31 * MillisToNanos))
+        assertEquals(67f, repeatShort.getValueFromNanos(133 * MillisToNanos))
+
+        // Also check on repeating value. Repeat mode: restart
+        assertEquals(31f, repeatLong.getValueFromNanos(31 * MillisToNanos), 0.1f)
+        assertEquals(
+            31f,
+            repeatLong.getValueFromNanos((extraLongDurationNanos + 31) * MillisToNanos),
+            0.1f
         )
     }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutResultProxy.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutResultProxy.kt
index 1778378..e17f108 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutResultProxy.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/TextLayoutResultProxy.kt
@@ -23,14 +23,34 @@
 
 internal class TextLayoutResultProxy(val value: TextLayoutResult) {
     // TextLayoutResult methods
-    fun getOffsetForPosition(position: Offset): Int {
-        val shiftedOffset = shiftedOffset(position)
-        return value.getOffsetForPosition(shiftedOffset)
+    /**
+     * Translates the position of the touch on the screen to the position in text. Because touch
+     * is relative to the decoration box, we need to translate it to the inner text field's
+     * coordinates first before calculating position of the symbol in text.
+     *
+     * @param position original position of the gesture relative to the decoration box
+     * @param coerceInVisibleBounds if true and original [position] is outside visible bounds
+     * of the inner text field, the [position] will be shifted to the closest edge of the inner
+     * text field's visible bounds. This is useful when you have a decoration box
+     * bigger than the inner text field, so when user touches to the decoration box area, the cursor
+     * goes to the beginning or the end of the visible inner text field; otherwise if we put the
+     * cursor under the touch in the invisible part of the inner text field, it would scroll to
+     * make the cursor visible. This behavior is not needed, and therefore
+     * [coerceInVisibleBounds] should be set to false, when the user drags outside visible bounds
+     * to make a selection.
+     */
+    fun getOffsetForPosition(position: Offset, coerceInVisibleBounds: Boolean = true): Int {
+        val relativePosition = position
+            .let { if (coerceInVisibleBounds) it.coercedInVisibleBoundsOfInputText() else it }
+            .relativeToInputText()
+        return value.getOffsetForPosition(relativePosition)
     }
 
     fun getLineForVerticalPosition(vertical: Float): Int {
-        val shiftedVertical = shiftedOffset(Offset(0f, vertical)).y
-        return value.getLineForVerticalPosition(shiftedVertical)
+        val relativeVertical = Offset(0f, vertical)
+            .coercedInVisibleBoundsOfInputText()
+            .relativeToInputText().y
+        return value.getLineForVerticalPosition(relativeVertical)
     }
 
     fun getLineEnd(lineIndex: Int, visibleEnd: Boolean = false): Int =
@@ -40,10 +60,10 @@
      * in the view. Returns false when the position is in the empty space of left/right of text.
      */
     fun isPositionOnText(offset: Offset): Boolean {
-        val shiftedOffset = shiftedOffset(offset)
-        val line = value.getLineForVerticalPosition(shiftedOffset.y)
-        return shiftedOffset.x >= value.getLineLeft(line) &&
-            shiftedOffset.x <= value.getLineRight(line)
+        val relativeOffset = offset.coercedInVisibleBoundsOfInputText().relativeToInputText()
+        val line = value.getLineForVerticalPosition(relativeOffset.y)
+        return relativeOffset.x >= value.getLineLeft(line) &&
+            relativeOffset.x <= value.getLineRight(line)
     }
 
     // Shift offset
@@ -54,19 +74,39 @@
     var innerTextFieldCoordinates: LayoutCoordinates? = null
     var decorationBoxCoordinates: LayoutCoordinates? = null
 
-    private fun shiftedOffset(offset: Offset): Offset {
-        // If offset is outside visible bounds of the inner text field, use visible bounds edges
-        val visibleInnerTextFieldRect = innerTextFieldCoordinates?.let { inner ->
-            decorationBoxCoordinates?.localBoundingBoxOf(inner)
-        } ?: Rect.Zero
-        val coercedOffset = offset.coerceIn(visibleInnerTextFieldRect)
-
+    /**
+     * Translates the click happened on the decoration box to the position in the inner text
+     * field coordinates. This relative position is then used to determine symbol position in
+     * text using TextLayoutResult object.
+     */
+    private fun Offset.relativeToInputText(): Offset {
         // Translates touch to the inner text field coordinates
         return innerTextFieldCoordinates?.let { innerTextFieldCoordinates ->
             decorationBoxCoordinates?.let { decorationBoxCoordinates ->
-                innerTextFieldCoordinates.localPositionOf(decorationBoxCoordinates, coercedOffset)
+                if (innerTextFieldCoordinates.isAttached && decorationBoxCoordinates.isAttached) {
+                    innerTextFieldCoordinates.localPositionOf(decorationBoxCoordinates, this)
+                } else {
+                    this
+                }
             }
-        } ?: coercedOffset
+        } ?: this
+    }
+
+    /**
+     * If click on the decoration box happens outside visible inner text field, coerce the click
+     * position to the visible edges of the inner text field.
+     */
+    private fun Offset.coercedInVisibleBoundsOfInputText(): Offset {
+        // If offset is outside visible bounds of the inner text field, use visible bounds edges
+        val visibleInnerTextFieldRect =
+            innerTextFieldCoordinates?.let { innerTextFieldCoordinates ->
+                if (innerTextFieldCoordinates.isAttached) {
+                    decorationBoxCoordinates?.localBoundingBoxOf(innerTextFieldCoordinates)
+                } else {
+                    Rect.Zero
+                }
+            } ?: Rect.Zero
+        return this.coerceIn(visibleInnerTextFieldRect)
     }
 }
 
diff --git a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
index 7b824e1..e8c775c 100644
--- a/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
+++ b/compose/foundation/foundation/src/commonMain/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManager.kt
@@ -114,6 +114,15 @@
     private var dragBeginPosition = Offset.Zero
 
     /**
+     * The beginning offset of the drag gesture translated into position in text. Every time a
+     * new drag gesture starts, it wil be recalculated.
+     * Unlike [dragBeginPosition] that is relative to the decoration box,
+     * [dragBeginOffsetInText] represents index in text. Essentially, it is equal to
+     * `layoutResult.getOffsetForPosition(dragBeginPosition)`.
+     */
+    private var dragBeginOffsetInText: Int? = null
+
+    /**
      * The total distance being dragged of the drag gesture. Every time a new drag gesture starts,
      * it will be zeroed out.
      */
@@ -166,6 +175,7 @@
                     isStartHandle = false,
                     wordBasedSelection = true
                 )
+                dragBeginOffsetInText = offset
             }
             dragBeginPosition = pxPosition
             dragTotalDistance = Offset.Zero
@@ -177,9 +187,13 @@
 
             dragTotalDistance += dragDistance
             state?.layoutResult?.let { layoutResult ->
-                val startOffset = layoutResult.getOffsetForPosition(dragBeginPosition)
+                val startOffset = dragBeginOffsetInText ?: layoutResult.getOffsetForPosition(
+                    position = dragBeginPosition,
+                    coerceInVisibleBounds = false
+                )
                 val endOffset = layoutResult.getOffsetForPosition(
-                    dragBeginPosition + dragTotalDistance
+                    position = dragBeginPosition + dragTotalDistance,
+                    coerceInVisibleBounds = false
                 )
                 updateSelection(
                     value = value,
@@ -197,6 +211,7 @@
             super.onStop(velocity)
             state?.showFloatingToolbar = true
             if (textToolbar?.status == TextToolbarStatus.Hidden) showSelectionToolbar()
+            dragBeginOffsetInText = null
         }
     }
 
@@ -212,6 +227,7 @@
                     offsetMapping,
                     onValueChange
                 )
+                dragBeginOffsetInText = layoutResult.getOffsetForPosition(downPosition)
             }
 
             dragBeginPosition = downPosition
@@ -224,9 +240,12 @@
 
             dragTotalDistance += dragDistance
             state?.layoutResult?.let { layoutResult ->
-                val startOffset = layoutResult.getOffsetForPosition(dragBeginPosition)
-                val endOffset =
-                    layoutResult.getOffsetForPosition(dragBeginPosition + dragTotalDistance)
+                val startOffset = dragBeginOffsetInText ?: layoutResult
+                    .getOffsetForPosition(dragBeginPosition, false)
+                val endOffset = layoutResult.getOffsetForPosition(
+                    position = dragBeginPosition + dragTotalDistance,
+                    coerceInVisibleBounds = false
+                )
                 updateSelection(
                     value = value,
                     transformedStartOffset = startOffset,
diff --git a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
index 029cfd7..4d87a55 100644
--- a/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
+++ b/compose/foundation/foundation/src/test/kotlin/androidx/compose/foundation/text/selection/TextFieldSelectionManagerTest.kt
@@ -74,7 +74,7 @@
     private val dragTextRange = TextRange("Hello".length + 1, text.length)
     private val layoutResult: TextLayoutResult = mock()
     private val layoutResultProxy: TextLayoutResultProxy = mock()
-    private val manager = TextFieldSelectionManager()
+    private lateinit var manager: TextFieldSelectionManager
 
     private val clipboardManager = mock<ClipboardManager>()
     private val textToolbar = mock<TextToolbar>()
@@ -84,6 +84,7 @@
     @OptIn(InternalFoundationTextApi::class)
     @Before
     fun setup() {
+        manager = TextFieldSelectionManager()
         manager.offsetMapping = offsetMapping
         manager.onValueChange = lambda
         manager.value = value
@@ -116,10 +117,15 @@
         whenever(layoutResult.getBoundingBox(any())).thenReturn(Rect.Zero)
         // left or right handle drag
         whenever(layoutResult.getOffsetForPosition(dragBeginPosition)).thenReturn(beginOffset)
-        whenever(layoutResult.getOffsetForPosition(dragDistance)).thenReturn(dragOffset)
+        whenever(layoutResult.getOffsetForPosition(dragBeginPosition + dragDistance))
+            .thenReturn(dragOffset)
         // touch drag
-        whenever(layoutResultProxy.getOffsetForPosition(dragBeginPosition)).thenReturn(beginOffset)
-        whenever(layoutResultProxy.getOffsetForPosition(dragDistance)).thenReturn(dragOffset)
+        whenever(
+            layoutResultProxy.getOffsetForPosition(dragBeginPosition, false)
+        ).thenReturn(beginOffset)
+        whenever(
+            layoutResultProxy.getOffsetForPosition(dragBeginPosition + dragDistance, false)
+        ).thenReturn(dragOffset)
 
         whenever(layoutResultProxy.value).thenReturn(layoutResult)
 
diff --git a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
index c49f0e8..bcf4af3 100644
--- a/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
+++ b/compose/integration-tests/demos/src/androidTest/java/androidx/compose/integration/demos/test/DemoTest.kt
@@ -42,7 +42,6 @@
 import androidx.test.filters.LargeTest
 import androidx.test.filters.MediumTest
 import com.google.common.truth.Truth.assertThat
-import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -95,7 +94,6 @@
     }
 
     @Test
-    @Ignore("b/179339732")
     fun navigateThroughAllDemos_2() {
         navigateThroughAllDemos(SplitDemoCategories[1])
     }
diff --git a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderTest.kt b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderTest.kt
index 6667f7e..8a7be37 100644
--- a/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderTest.kt
+++ b/compose/material/material/src/androidAndroidTest/kotlin/androidx/compose/material/SliderTest.kt
@@ -246,6 +246,38 @@
     }
 
     @Test
+    fun slider_tap_rangeChange() {
+        val state = mutableStateOf(0f)
+        val rangeEnd = mutableStateOf(0.25f)
+
+        rule.setMaterialContent {
+            Slider(
+                modifier = Modifier.testTag(tag),
+                value = state.value,
+                onValueChange = { state.value = it },
+                valueRange = 0f..rangeEnd.value
+            )
+        }
+        // change to 1 since [calculateFraction] coerces between 0..1
+        rule.runOnUiThread {
+            rangeEnd.value = 1f
+        }
+
+        var expected = 0f
+
+        rule.onNodeWithTag(tag)
+            .performGesture {
+                down(Offset(centerX + 50, centerY))
+                up()
+                expected = calculateFraction(left, right, centerX + 50)
+            }
+
+        rule.runOnIdle {
+            Truth.assertThat(abs(state.value - expected)).isLessThan(0.001f)
+        }
+    }
+
+    @Test
     fun slider_drag_rtl() {
         val state = mutableStateOf(0f)
         var slop = 0f
diff --git a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
index 5c1cfa9..9920b05 100644
--- a/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
+++ b/compose/material/material/src/commonMain/kotlin/androidx/compose/material/Slider.kt
@@ -66,6 +66,7 @@
 import androidx.compose.ui.unit.LayoutDirection
 import androidx.compose.ui.unit.dp
 import androidx.compose.ui.util.lerp
+import kotlinx.coroutines.CancellationException
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.coroutineScope
 import kotlinx.coroutines.flow.collect
@@ -152,7 +153,7 @@
         }
 
         val press = if (enabled) {
-            Modifier.pointerInput(Unit) {
+            Modifier.pointerInput(position, interactionSource, maxPx, isRtl) {
                 detectTapGestures(
                     onPress = { pos ->
                         position.snapTo(if (isRtl) maxPx - pos.x else pos.x)
@@ -161,12 +162,16 @@
                             launch {
                                 interactionSource.emit(interaction)
                             }
-                        }
-                        val success = tryAwaitRelease()
-                        if (success) gestureEndAction(0f)
-                        coroutineScope {
-                            launch {
-                                interactionSource.emit(PressInteraction.Release(interaction))
+                            try {
+                                val success = tryAwaitRelease()
+                                if (success) gestureEndAction(0f)
+                                launch {
+                                    interactionSource.emit(PressInteraction.Release(interaction))
+                                }
+                            } catch (c: CancellationException) {
+                                launch {
+                                    interactionSource.emit(PressInteraction.Cancel(interaction))
+                                }
                             }
                         }
                     }
diff --git a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ModelViewTests.kt b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ModelViewTests.kt
index f019e79..6095c44 100644
--- a/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ModelViewTests.kt
+++ b/compose/runtime/runtime/src/test/kotlin/androidx/compose/runtime/ModelViewTests.kt
@@ -24,12 +24,12 @@
 import androidx.compose.runtime.mock.validate
 import kotlin.test.Test
 
-const val PRESIDENT_NAME_1 = "George Washington"
-const val PRESIDENT_AGE_1 = 57
-const val PRESIDENT_NAME_16 = "Abraham Lincoln"
-const val PRESIDENT_AGE_16 = 52
+internal const val PRESIDENT_NAME_1 = "George Washington"
+internal const val PRESIDENT_AGE_1 = 57
+internal const val PRESIDENT_NAME_16 = "Abraham Lincoln"
+internal const val PRESIDENT_AGE_16 = 52
 
-class Person(name: String, age: Int) {
+internal class Person(name: String, age: Int) {
     var name by mutableStateOf(name)
     var age by mutableStateOf(age)
 }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
index 01bdda7..e82524a 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/compose/AndroidComposeViewWrapper.kt
@@ -16,7 +16,10 @@
 
 package androidx.compose.ui.inspection.compose
 
+import android.content.res.Resources
 import android.view.View
+import androidx.compose.ui.inspection.framework.ancestors
+import androidx.compose.ui.inspection.framework.isRoot
 import androidx.compose.ui.inspection.inspector.LayoutInspectorTree
 import androidx.compose.ui.inspection.proto.StringTable
 import androidx.compose.ui.inspection.proto.toComposableNodes
@@ -35,6 +38,31 @@
 }
 
 /**
+ * Returns true if the view is contained in a layout generated by the platform / system.
+ *
+ * <ul>
+ *   <li>DecorView, ViewStub, AndroidComposeView, View will have a null layout
+ *   <li>Layouts from the "android" namespace are from the platform
+ *   <li>AppCompat will typically use an "abc_" prefix for their layout names
+ * </ul>
+ */
+private fun View.isSystemView(): Boolean {
+    return try {
+        val layoutId = sourceLayoutResId
+        if (layoutId == 0) {
+            // Programmatically added Views are treated as system views:
+            return true
+        }
+        val namespace = resources.getResourcePackageName(layoutId)
+        val name = resources.getResourceEntryName(layoutId)
+
+        namespace == "android" || name.startsWith("abc_")
+    } catch (ignored: Resources.NotFoundException) {
+        false
+    }
+}
+
+/**
  * The `AndroidComposeView` class inside the compose library is internal, so we make our own fake
  * class there that wraps a normal [View], verifies it's the expected type, and exposes compose
  * related data that we care about.
@@ -42,7 +70,7 @@
  * As this class extracts information about the view it's targeting, it must be instantiated on the
  * UI thread.
  */
-class AndroidComposeViewWrapper(private val composeView: View, skipSystemComposables: Boolean) {
+class AndroidComposeViewWrapper(composeView: View, skipSystemComposables: Boolean) {
     companion object {
         fun tryCreateFor(view: View, skipSystemComposables: Boolean): AndroidComposeViewWrapper? {
             return if (view.isAndroidComposeView()) {
@@ -58,6 +86,11 @@
         check(composeView.isAndroidComposeView())
     }
 
+    private val viewParent =
+        if (!skipSystemComposables) composeView
+        else composeView.ancestors().first { !it.isSystemView() || it.isRoot() }
+
+    // TODO: Reuse LayoutInspectorTree to avoid redoing the constant searching in ParameterFactory
     val inspectorNodes = LayoutInspectorTree().apply {
         this.hideSystemNodes = skipSystemComposables
     }.convert(composeView)
@@ -66,7 +99,7 @@
         ThreadUtils.assertOnMainThread()
 
         return ComposableRoot.newBuilder().apply {
-            viewId = composeView.uniqueDrawingId
+            viewId = viewParent.uniqueDrawingId
             addAllNodes(inspectorNodes.toComposableNodes(stringTable))
         }.build()
     }
diff --git a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/framework/ViewExtensions.kt b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/framework/ViewExtensions.kt
index 84ddac5..da400e4 100644
--- a/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/framework/ViewExtensions.kt
+++ b/compose/ui/ui-inspection/src/main/java/androidx/compose/ui/inspection/framework/ViewExtensions.kt
@@ -25,6 +25,12 @@
     return (0 until childCount).map { i -> getChildAt(i) }
 }
 
+fun View.ancestors(): Sequence<View> =
+    generateSequence(this) { it.parent as? View }
+
+fun View.isRoot(): Boolean =
+    parent as? View == null
+
 /**
  * Return a list of this view and all its children in depth-first order
  */
diff --git a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
index bc405cb..1df07e6 100644
--- a/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
+++ b/compose/ui/ui-test-junit4/src/androidAndroidTest/kotlin/androidx/compose/ui/test/junit4/MultipleActivitiesClickTest.kt
@@ -27,7 +27,6 @@
 import androidx.compose.ui.test.click
 import androidx.compose.ui.test.onNodeWithTag
 import androidx.compose.ui.test.performGesture
-import androidx.test.filters.FlakyTest
 import androidx.test.runner.lifecycle.ActivityLifecycleMonitorRegistry
 import androidx.test.runner.lifecycle.Stage
 import com.google.common.truth.Truth.assertThat
@@ -40,9 +39,8 @@
     @get:Rule
     val rule = createAndroidComposeRule<Activity1>()
 
-    @Ignore("b/178044284")
     @Test
-    @FlakyTest(bugId = 178003554)
+    @Ignore("b/155774664")
     fun test() {
         lateinit var activity1: Activity1
         rule.activityRule.scenario.onActivity { activity1 = it }
diff --git a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/PopupDemo.kt b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/PopupDemo.kt
index 28d1e5e..6e7516c 100644
--- a/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/PopupDemo.kt
+++ b/compose/ui/ui/integration-tests/ui-demos/src/main/java/androidx/compose/ui/demos/PopupDemo.kt
@@ -140,7 +140,6 @@
 
 @Composable
 private fun ColumnScope.PopupElevation() {
-    var isFocusable by remember { mutableStateOf(false) }
     var shape by remember { mutableStateOf(RectangleShape) }
     var background by remember { mutableStateOf(Color.Transparent) }
     var contentSize by remember { mutableStateOf(100.dp) }
@@ -152,7 +151,6 @@
         Box(Modifier.size(110.dp).background(background)) {
             Popup(
                 alignment = Alignment.Center,
-                properties = PopupProperties(focusable = true),
                 onDismissRequest = { dismissCounter++ }
             ) {
                 Card(
@@ -166,7 +164,7 @@
         }
 
         Spacer(Modifier.requiredHeight(20.dp))
-        Text("Dismiss clicked: $dismissCounter (focusable: $isFocusable)")
+        Text("Dismiss clicked: $dismissCounter")
         Spacer(Modifier.requiredHeight(20.dp))
         Row {
             Button(onClick = { elevation -= 1.dp }) {
@@ -182,9 +180,6 @@
             Text("Toggle shape")
         }
         Spacer(Modifier.requiredHeight(10.dp))
-        Button(onClick = { isFocusable = !isFocusable }) {
-            Text("Toggle focusable")
-        }
         Spacer(Modifier.requiredHeight(10.dp))
         Button(
             onClick = {
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
index bc85617..39b8c81 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/AndroidAccessibilityTest.kt
@@ -316,6 +316,7 @@
     }
 
     @Test
+    @SdkSuppress(minSdkVersion = Build.VERSION_CODES.O)
     fun reportedText_inTextFieldWithLabel_whenEditableTextEmpty() {
         textFieldValue.value = TextFieldValue()
         val textFieldNode = rule.onNodeWithTag(TextFieldTag)
diff --git a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
index cc53d27..1966425 100644
--- a/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
+++ b/compose/ui/ui/src/androidAndroidTest/kotlin/androidx/compose/ui/window/DialogTest.kt
@@ -37,6 +37,7 @@
 import androidx.test.platform.app.InstrumentationRegistry.getInstrumentation
 import androidx.test.uiautomator.UiDevice
 import org.junit.Assert.assertEquals
+import org.junit.Ignore
 import org.junit.Rule
 import org.junit.Test
 import org.junit.runner.RunWith
@@ -65,7 +66,7 @@
     }
 
     @Test
-    @FlakyTest(bugId = 159364185)
+    @Ignore("100% failing b/179359518")
     fun dialogTest_isNotDismissed_whenClicked() {
         val textBeforeClick = "textBeforeClick"
         val textAfterClick = "textAfterClick"
diff --git a/core/core-google-shortcuts/api/current.txt b/core/core-google-shortcuts/api/current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/public_plus_experimental_current.txt b/core/core-google-shortcuts/api/public_plus_experimental_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/public_plus_experimental_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/api/res-current.txt b/core/core-google-shortcuts/api/res-current.txt
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/core/core-google-shortcuts/api/res-current.txt
diff --git a/core/core-google-shortcuts/api/restricted_current.txt b/core/core-google-shortcuts/api/restricted_current.txt
new file mode 100644
index 0000000..e6f50d0
--- /dev/null
+++ b/core/core-google-shortcuts/api/restricted_current.txt
@@ -0,0 +1 @@
+// Signature format: 4.0
diff --git a/core/core-google-shortcuts/build.gradle b/core/core-google-shortcuts/build.gradle
new file mode 100644
index 0000000..8451eed
--- /dev/null
+++ b/core/core-google-shortcuts/build.gradle
@@ -0,0 +1,40 @@
+/*
+ * Copyright (C) 2021 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ *      http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+import static androidx.build.dependencies.DependenciesKt.*
+import androidx.build.LibraryGroups
+import androidx.build.LibraryVersions
+import androidx.build.LibraryType
+import androidx.build.Publish
+
+plugins {
+    id("AndroidXPlugin")
+    id("com.android.library")
+    id("kotlin-android")
+}
+
+dependencies {
+    api(KOTLIN_STDLIB)
+    // Add dependencies here
+}
+
+androidx {
+    name = "Google Shortcuts Integration Library"
+    type = LibraryType.PUBLISHED_LIBRARY
+    mavenVersion = LibraryVersions.CORE_GOOGLE_SHORTCUTS
+    mavenGroup = LibraryGroups.CORE
+    inceptionYear = "2021"
+    description = "Library for powering Google features with Android app shortcuts"
+}
diff --git a/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
new file mode 100644
index 0000000..db37e46
--- /dev/null
+++ b/core/core-google-shortcuts/src/androidTest/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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"
+    package="androidx.core.google.shortcuts.test">
+
+</manifest>
diff --git a/core/core-google-shortcuts/src/main/AndroidManifest.xml b/core/core-google-shortcuts/src/main/AndroidManifest.xml
new file mode 100644
index 0000000..c97471d
--- /dev/null
+++ b/core/core-google-shortcuts/src/main/AndroidManifest.xml
@@ -0,0 +1,20 @@
+<?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"
+    package="androidx.core.google.shortcuts">
+
+</manifest>
\ No newline at end of file
diff --git a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl b/core/core-google-shortcuts/src/main/androidx/core/package-info.java
similarity index 65%
copy from car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl
copy to core/core-google-shortcuts/src/main/androidx/core/package-info.java
index d48ca1b..2b06a14 100644
--- a/car/app/app-aaos/src/main/aidl/androidx/car/app/aaos/renderer/IBackButtonListener.aidl
+++ b/core/core-google-shortcuts/src/main/androidx/core/package-info.java
@@ -1,5 +1,5 @@
 /*
- * Copyright 2021 The Android Open Source Project
+ * Copyright (C) 2021 The Android Open Source Project
  *
  * Licensed under the Apache License, Version 2.0 (the "License");
  * you may not use this file except in compliance with the License.
@@ -14,14 +14,7 @@
  * limitations under the License.
  */
 
-package androidx.car.app.aaos.renderer;
-
 /**
- * Classes that wish to listen for back button events should implement this.
- *
- * @hide
+ * Insert package level documentation here
  */
-interface IBackButtonListener {
-  /** Notifies that the button was pressed. */
-  void onBackPressed() = 1;
-}
+package androidx.core.google.shortcuts;
diff --git a/core/core/api/restricted_1.5.0-beta02.txt b/core/core/api/restricted_1.5.0-beta02.txt
index 1bb8297..4fb5f40 100644
--- a/core/core/api/restricted_1.5.0-beta02.txt
+++ b/core/core/api/restricted_1.5.0-beta02.txt
@@ -1940,7 +1940,7 @@
     ctor public FontRequest(String, String, String, @ArrayRes int);
     method public java.util.List<java.util.List<byte[]!>!>? getCertificates();
     method @ArrayRes public int getCertificatesArrayResId();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
     method public String getProviderAuthority();
     method public String getProviderPackage();
     method public String getQuery();
@@ -1949,12 +1949,12 @@
   public class FontsContractCompat {
     method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![]);
     method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
     method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
   }
 
   public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
@@ -1972,7 +1972,7 @@
   }
 
   public static class FontsContractCompat.FontFamilyResult {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo![]?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo![]?);
     method public androidx.core.provider.FontsContractCompat.FontInfo![]! getFonts();
     method public int getStatusCode();
     field public static final int STATUS_OK = 0; // 0x0
@@ -1981,7 +1981,7 @@
   }
 
   public static class FontsContractCompat.FontInfo {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
     method public int getResultCode();
     method @IntRange(from=0) public int getTtcIndex();
     method public android.net.Uri getUri();
@@ -2000,22 +2000,22 @@
     field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
     field public static final int FAIL_REASON_SECURITY_VIOLATION = -4; // 0xfffffffc
     field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
   }
 
   @IntDef({androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_UNAVAILABLE, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_MALFORMED_QUERY, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, androidx.core.provider.FontsContractCompat.FontRequestCallback.RESULT_OK}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontsContractCompat.FontRequestCallback.FontRequestFailReason {
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
-    ctor public SelfDestructiveThread(String!, int, int);
-    method @VisibleForTesting public int getGeneration();
-    method @VisibleForTesting public boolean isRunning();
-    method public <T> void postAndReply(java.util.concurrent.Callable<T!>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T!>!);
-    method public <T> T! postAndWait(java.util.concurrent.Callable<T!>!, int) throws java.lang.InterruptedException;
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
+    ctor @Deprecated public SelfDestructiveThread(String!, int, int);
+    method @Deprecated @VisibleForTesting public int getGeneration();
+    method @Deprecated @VisibleForTesting public boolean isRunning();
+    method @Deprecated public <T> void postAndReply(java.util.concurrent.Callable<T!>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T!>!);
+    method @Deprecated public <T> T! postAndWait(java.util.concurrent.Callable<T!>!, int) throws java.lang.InterruptedException;
   }
 
-  public static interface SelfDestructiveThread.ReplyCallback<T> {
-    method public void onReply(T!);
+  @Deprecated public static interface SelfDestructiveThread.ReplyCallback<T> {
+    method @Deprecated public void onReply(T!);
   }
 
 }
diff --git a/core/core/api/restricted_current.txt b/core/core/api/restricted_current.txt
index 1bb8297..4fb5f40 100644
--- a/core/core/api/restricted_current.txt
+++ b/core/core/api/restricted_current.txt
@@ -1940,7 +1940,7 @@
     ctor public FontRequest(String, String, String, @ArrayRes int);
     method public java.util.List<java.util.List<byte[]!>!>? getCertificates();
     method @ArrayRes public int getCertificatesArrayResId();
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public String! getIdentifier();
     method public String getProviderAuthority();
     method public String getProviderPackage();
     method public String getQuery();
@@ -1949,12 +1949,12 @@
   public class FontsContractCompat {
     method public static android.graphics.Typeface? buildTypeface(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontsContractCompat.FontInfo![]);
     method public static androidx.core.provider.FontsContractCompat.FontFamilyResult fetchFonts(android.content.Context, android.os.CancellationSignal?, androidx.core.provider.FontRequest) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
-    method @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static android.graphics.Typeface! getFontSync(android.content.Context!, androidx.core.provider.FontRequest!, androidx.core.content.res.ResourcesCompat.FontCallback?, android.os.Handler?, boolean, int, int);
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @VisibleForTesting public static android.content.pm.ProviderInfo? getProvider(android.content.pm.PackageManager, androidx.core.provider.FontRequest, android.content.res.Resources?) throws android.content.pm.PackageManager.NameNotFoundException;
+    method @Deprecated @RequiresApi(19) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static java.util.Map<android.net.Uri!,java.nio.ByteBuffer!>! prepareFontData(android.content.Context!, androidx.core.provider.FontsContractCompat.FontInfo![]!, android.os.CancellationSignal!);
     method public static void requestFont(android.content.Context, androidx.core.provider.FontRequest, androidx.core.provider.FontsContractCompat.FontRequestCallback, android.os.Handler);
-    method @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
+    method @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static void resetCache();
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final String PARCEL_FONT_RESULTS = "font_results";
   }
 
   public static final class FontsContractCompat.Columns implements android.provider.BaseColumns {
@@ -1972,7 +1972,7 @@
   }
 
   public static class FontsContractCompat.FontFamilyResult {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo![]?);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontFamilyResult(int, androidx.core.provider.FontsContractCompat.FontInfo![]?);
     method public androidx.core.provider.FontsContractCompat.FontInfo![]! getFonts();
     method public int getStatusCode();
     field public static final int STATUS_OK = 0; // 0x0
@@ -1981,7 +1981,7 @@
   }
 
   public static class FontsContractCompat.FontInfo {
-    ctor @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
+    ctor @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public FontsContractCompat.FontInfo(android.net.Uri, @IntRange(from=0) int, @IntRange(from=1, to=1000) int, boolean, int);
     method public int getResultCode();
     method @IntRange(from=0) public int getTtcIndex();
     method public android.net.Uri getUri();
@@ -2000,22 +2000,22 @@
     field public static final int FAIL_REASON_PROVIDER_NOT_FOUND = -1; // 0xffffffff
     field public static final int FAIL_REASON_SECURITY_VIOLATION = -4; // 0xfffffffc
     field public static final int FAIL_REASON_WRONG_CERTIFICATES = -2; // 0xfffffffe
-    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
+    field @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public static final int RESULT_OK = 0; // 0x0
   }
 
   @IntDef({androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_LOAD_ERROR, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_NOT_FOUND, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_FONT_UNAVAILABLE, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_MALFORMED_QUERY, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES, androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_SECURITY_VIOLATION, androidx.core.provider.FontsContractCompat.FontRequestCallback.RESULT_OK}) @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) public static @interface FontsContractCompat.FontRequestCallback.FontRequestFailReason {
   }
 
-  @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
-    ctor public SelfDestructiveThread(String!, int, int);
-    method @VisibleForTesting public int getGeneration();
-    method @VisibleForTesting public boolean isRunning();
-    method public <T> void postAndReply(java.util.concurrent.Callable<T!>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T!>!);
-    method public <T> T! postAndWait(java.util.concurrent.Callable<T!>!, int) throws java.lang.InterruptedException;
+  @Deprecated @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX) public class SelfDestructiveThread {
+    ctor @Deprecated public SelfDestructiveThread(String!, int, int);
+    method @Deprecated @VisibleForTesting public int getGeneration();
+    method @Deprecated @VisibleForTesting public boolean isRunning();
+    method @Deprecated public <T> void postAndReply(java.util.concurrent.Callable<T!>!, androidx.core.provider.SelfDestructiveThread.ReplyCallback<T!>!);
+    method @Deprecated public <T> T! postAndWait(java.util.concurrent.Callable<T!>!, int) throws java.lang.InterruptedException;
   }
 
-  public static interface SelfDestructiveThread.ReplyCallback<T> {
-    method public void onReply(T!);
+  @Deprecated public static interface SelfDestructiveThread.ReplyCallback<T> {
+    method @Deprecated public void onReply(T!);
   }
 
 }
diff --git a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
index 976407c..fdc55dc 100644
--- a/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
+++ b/core/core/src/androidTest/java/androidx/core/provider/FontsContractCompatTest.java
@@ -402,7 +402,7 @@
         inst.runOnMainSync(new Runnable() {
             @Override
             public void run() {
-                FontsContractCompat.getFontSync(mContext, request, callback, null,
+                FontsContractCompat.getFont(mContext, request, callback, null,
                         false /* isBlockingFetch */, 300 /* timeout */, Typeface.NORMAL);
             }
         });
diff --git a/core/core/src/androidTest/java/androidx/core/provider/SelfDestructiveThreadTest.java b/core/core/src/androidTest/java/androidx/core/provider/SelfDestructiveThreadTest.java
index 7b0387e..c2b4165 100644
--- a/core/core/src/androidTest/java/androidx/core/provider/SelfDestructiveThreadTest.java
+++ b/core/core/src/androidTest/java/androidx/core/provider/SelfDestructiveThreadTest.java
@@ -16,8 +16,6 @@
 
 package androidx.core.provider;
 
-import static androidx.core.provider.SelfDestructiveThread.ReplyCallback;
-
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertNotEquals;
@@ -43,12 +41,13 @@
 /**
  * Tests for {@link SelfDestructiveThread}
  */
+// TODO Remove
 @RunWith(AndroidJUnit4.class)
 @MediumTest
 public class SelfDestructiveThreadTest {
     private static final int DEFAULT_TIMEOUT = 1000;
 
-    private void waitUntilDestruction(SelfDestructiveThread thread, long timeoutMs) {
+    private void waitUntilDestruction(FontRequestThreadPool thread, long timeoutMs) {
         if (!thread.isRunning()) {
             return;
         }
@@ -86,11 +85,11 @@
     @Test
     public void testDestruction() throws InterruptedException {
         final int destructAfterLastActivityInMs = 300;
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
         thread.postAndWait(new Callable<Object>() {
             @Override
-            public Object call() throws Exception {
+            public Object call() {
                 return null;
             }
         }, DEFAULT_TIMEOUT);
@@ -98,25 +97,22 @@
         assertFalse(thread.isRunning());
     }
 
+    private static class IdCallable implements Callable<Integer> {
+        @Override
+        public Integer call() {
+            return System.identityHashCode(Thread.currentThread());
+        }
+    }
+
     @Test
     public void testReconstruction() throws InterruptedException {
         final int destructAfterLastActivityInMs = 300;
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
-        Integer generation = thread.postAndWait(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return thread.getGeneration();
-            }
-        }, DEFAULT_TIMEOUT);
+        Integer generation = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
         assertNotNull(generation);
         waitUntilDestruction(thread, DEFAULT_TIMEOUT);
-        Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return thread.getGeneration();
-            }
-        }, DEFAULT_TIMEOUT);
+        Integer nextGeneration = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
         assertNotNull(nextGeneration);
         assertNotEquals(generation.intValue(), nextGeneration.intValue());
     }
@@ -124,21 +120,11 @@
     @Test
     public void testReuseSameThread() throws InterruptedException {
         final int destructAfterLastActivityInMs = 300;
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
-        Integer generation = thread.postAndWait(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return thread.getGeneration();
-            }
-        }, DEFAULT_TIMEOUT);
+        Integer generation = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
         assertNotNull(generation);
-        Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return thread.getGeneration();
-            }
-        }, DEFAULT_TIMEOUT);
+        Integer nextGeneration = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
         assertNotNull(nextGeneration);
         waitUntilDestruction(thread, DEFAULT_TIMEOUT);
         assertEquals(generation.intValue(), nextGeneration.intValue());
@@ -148,25 +134,16 @@
     @Test
     public void testReuseSameThread_Multiple() throws InterruptedException {
         final int destructAfterLastActivityInMs = 300;
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
-        Integer generation = thread.postAndWait(new Callable<Integer>() {
-            @Override
-            public Integer call() throws Exception {
-                return thread.getGeneration();
-            }
-        }, DEFAULT_TIMEOUT);
+        Integer generation = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
         assertNotNull(generation);
         int firstGeneration = generation.intValue();
+
         for (int i = 0; i < 10; ++i) {
             // Less than renewal duration, so that the same thread must be used.
             waitMillis(destructAfterLastActivityInMs / 2);
-            Integer nextGeneration = thread.postAndWait(new Callable<Integer>() {
-                @Override
-                public Integer call() throws Exception {
-                    return thread.getGeneration();
-                }
-            }, DEFAULT_TIMEOUT);
+            Integer nextGeneration = thread.postAndWait(new IdCallable(), DEFAULT_TIMEOUT);
             assertNotNull(nextGeneration);
             assertEquals(firstGeneration, nextGeneration.intValue());
         }
@@ -176,14 +153,14 @@
     @Test
     public void testTimeout() {
         final int destructAfterLastActivityInMs = 300;
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
 
         final int timeoutMs = 300;
         try {
             thread.postAndWait(new Callable<Object>() {
                 @Override
-                public Object call() throws Exception {
+                public Object call() {
                     waitMillis(timeoutMs * 3);  // Wait longer than timeout.
                     return new Object();
                 }
@@ -194,7 +171,7 @@
         }
     }
 
-    private class WaitableReplyCallback implements ReplyCallback<Integer> {
+    private class WaitableReplyCallback implements FontRequestThreadPool.ReplyCallback<Integer> {
         private final ReentrantLock mLock = new ReentrantLock();
         private final Condition mCond = mLock.newCondition();
 
@@ -258,12 +235,12 @@
 
         final Callable<Integer> callable = new Callable<Integer>() {
             @Override
-            public Integer call() throws Exception {
+            public Integer call() {
                 return expectedResult;
             }
         };
         final WaitableReplyCallback reply = new WaitableReplyCallback();
-        final SelfDestructiveThread thread = new SelfDestructiveThread(
+        final FontRequestThreadPool thread = new FontRequestThreadPool(
                 "test", Process.THREAD_PRIORITY_BACKGROUND, destructAfterLastActivityInMs);
         InstrumentationRegistry.getInstrumentation().runOnMainSync(new Runnable() {
             @Override
diff --git a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
index f91741c7..83bd973 100644
--- a/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
+++ b/core/core/src/main/java/androidx/core/graphics/TypefaceCompat.java
@@ -139,7 +139,7 @@
                     : fontCallback == null;
             final int timeout = isRequestFromLayoutInflator ? providerEntry.getTimeout()
                     : FontResourcesParserCompat.INFINITE_TIMEOUT_VALUE;
-            typeface = FontsContractCompat.getFontSync(context, providerEntry.getRequest(),
+            typeface = FontsContractCompat.getFont(context, providerEntry.getRequest(),
                     fontCallback, handler, isBlocking, timeout, style);
         } else {
             typeface = sTypefaceCompatImpl.createFromFontFamilyFilesResourceEntry(
diff --git a/core/core/src/main/java/androidx/core/provider/FontProvider.java b/core/core/src/main/java/androidx/core/provider/FontProvider.java
index c9729e1..5a1e691 100644
--- a/core/core/src/main/java/androidx/core/provider/FontProvider.java
+++ b/core/core/src/main/java/androidx/core/provider/FontProvider.java
@@ -34,6 +34,8 @@
 import androidx.annotation.Nullable;
 import androidx.annotation.VisibleForTesting;
 import androidx.core.content.res.FontResourcesParserCompat;
+import androidx.core.provider.FontsContractCompat.FontFamilyResult;
+import androidx.core.provider.FontsContractCompat.FontInfo;
 
 import java.util.ArrayList;
 import java.util.Arrays;
@@ -45,20 +47,18 @@
     private FontProvider() {}
 
     @NonNull
-    static FontsContractCompat.FontFamilyResult getFontFamilyResult(@NonNull Context context,
+    static FontFamilyResult getFontFamilyResult(@NonNull Context context,
             @NonNull FontRequest request, @Nullable CancellationSignal cancellationSignal)
             throws PackageManager.NameNotFoundException {
         ProviderInfo providerInfo = getProvider(
                 context.getPackageManager(), request, context.getResources());
         if (providerInfo == null) {
-            return new FontsContractCompat.FontFamilyResult(
-                    FontsContractCompat.FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
+            return FontFamilyResult.create(FontFamilyResult.STATUS_WRONG_CERTIFICATES, null);
 
         }
-        FontsContractCompat.FontInfo[] fonts = query(
+        FontInfo[] fonts = query(
                 context, request, providerInfo.authority, cancellationSignal);
-        return new FontsContractCompat.FontFamilyResult(
-                FontsContractCompat.FontFamilyResult.STATUS_OK, fonts);
+        return FontFamilyResult.create(FontFamilyResult.STATUS_OK, fonts);
     }
 
     /**
@@ -112,13 +112,13 @@
     @SuppressWarnings("UnsafeNewApiCall")
     @VisibleForTesting
     @NonNull
-    static FontsContractCompat.FontInfo[] query(
+    static FontInfo[] query(
             Context context,
             FontRequest request,
             String authority,
             CancellationSignal cancellationSignal
     ) {
-        ArrayList<FontsContractCompat.FontInfo> result = new ArrayList<>();
+        ArrayList<FontInfo> result = new ArrayList<>();
         final Uri uri = new Uri.Builder().scheme(ContentResolver.SCHEME_CONTENT)
                 .authority(authority)
                 .build();
@@ -175,8 +175,7 @@
                     int weight = weightColumnIndex != -1 ? cursor.getInt(weightColumnIndex) : 400;
                     boolean italic = italicColumnIndex != -1 && cursor.getInt(italicColumnIndex)
                             == 1;
-                    result.add(new FontsContractCompat.FontInfo(fileUri, ttcIndex, weight, italic,
-                            resultCode));
+                    result.add(FontInfo.create(fileUri, ttcIndex, weight, italic, resultCode));
                 }
             }
         } finally {
@@ -184,7 +183,7 @@
                 cursor.close();
             }
         }
-        return result.toArray(new FontsContractCompat.FontInfo[0]);
+        return result.toArray(new FontInfo[0]);
     }
 
     private static List<List<byte[]>> getCertificates(FontRequest request, Resources resources) {
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequest.java b/core/core/src/main/java/androidx/core/provider/FontRequest.java
index 6fb880e..4b692c3 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequest.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequest.java
@@ -16,6 +16,7 @@
 
 package androidx.core.provider;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
 import android.util.Base64;
@@ -137,12 +138,25 @@
         return mCertificatesArray;
     }
 
-    /** @hide */
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     *
+     * @hide
+     */
+    @Deprecated
     @RestrictTo(LIBRARY_GROUP_PREFIX)
     public String getIdentifier() {
         return mIdentifier;
     }
 
+    /** @hide */
+    @RestrictTo(LIBRARY)
+    @NonNull
+    String getId() {
+        return mIdentifier;
+    }
+
     @Override
     public String toString() {
         StringBuilder builder = new StringBuilder();
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java b/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java
new file mode 100644
index 0000000..74e0970
--- /dev/null
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestThreadPool.java
@@ -0,0 +1,147 @@
+/*
+ * 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.provider;
+
+import android.os.Handler;
+import android.os.Process;
+
+import androidx.annotation.IntRange;
+import androidx.annotation.NonNull;
+import androidx.annotation.VisibleForTesting;
+
+import java.util.concurrent.Callable;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.Future;
+import java.util.concurrent.LinkedBlockingDeque;
+import java.util.concurrent.ThreadPoolExecutor;
+import java.util.concurrent.TimeUnit;
+import java.util.concurrent.TimeoutException;
+
+class FontRequestThreadPool {
+    private final ThreadPoolExecutor mExecutor;
+    private final ThreadFactory mThreadFactory;
+
+    FontRequestThreadPool(
+            @NonNull String threadName,
+            int threadPriority,
+            @IntRange(from = 0) int keepAliveTimeInMillis
+    ) {
+        mThreadFactory = new ThreadFactory(threadName, threadPriority);
+
+        // allow core thread timeout to timeout the core threads so that
+        // when no tasks arrive, the core threads can be killed
+        mExecutor = new ThreadPoolExecutor(
+                0 /* corePoolSize */,
+                1 /* maximumPoolSize */,
+                keepAliveTimeInMillis /* keepAliveTime */,
+                TimeUnit.MILLISECONDS /* keepAliveTime TimeUnit */,
+                new LinkedBlockingDeque<Runnable>() /* unbounded queue*/,
+                mThreadFactory
+        );
+        mExecutor.allowCoreThreadTimeOut(true);
+    }
+
+    <T> T postAndWait(
+            @NonNull final Callable<T> callable,
+            @IntRange(from = 0) int timeoutMillis
+    ) throws InterruptedException {
+        Future<T> future = mExecutor.submit(callable);
+        try {
+            return future.get(timeoutMillis, TimeUnit.MILLISECONDS);
+        } catch (ExecutionException e) {
+            throw new RuntimeException(e);
+        } catch (InterruptedException e) {
+            throw e;
+        } catch (TimeoutException e) {
+            throw new InterruptedException("timeout");
+        }
+    }
+
+    <T> void postAndReply(
+            @NonNull final Callable<T> callable,
+            @NonNull final ReplyCallback<T> callback
+    ) {
+        final Handler calleeHandler = CalleeHandler.create();
+        mExecutor.execute(new Runnable() {
+            @Override
+            public void run() {
+                T t;
+                try {
+                    t = callable.call();
+                } catch (Exception e) {
+                    t = null;
+                }
+                final T result = t;
+
+                calleeHandler.post(new Runnable() {
+                    @Override
+                    public void run() {
+                        callback.onReply(result);
+                    }
+                });
+            }
+        });
+    }
+
+    // TODO Remove
+    @VisibleForTesting
+    boolean isRunning() {
+        return mExecutor.getPoolSize() != 0;
+    }
+
+    /**
+     * Reply callback for postAndReply
+     *
+     * @param <T> A type which will be received as the argument.
+     */
+    interface ReplyCallback<T> {
+        /**
+         * Called when the task was finished.
+         */
+        void onReply(T value);
+    }
+
+    private static class ThreadFactory implements java.util.concurrent.ThreadFactory {
+        private String mThreadName;
+        private int mPriority;
+
+        ThreadFactory(@NonNull String threadName, int priority) {
+            mThreadName = threadName;
+            mPriority = priority;
+        }
+
+        @Override
+        public Thread newThread(Runnable runnable) {
+            return new ProcessPriorityThread(runnable, mThreadName, mPriority);
+        }
+
+        private static class ProcessPriorityThread extends Thread {
+            private final int mPriority;
+
+            ProcessPriorityThread(Runnable target, String name, int priority) {
+                super(target, name);
+                mPriority = priority;
+            }
+
+            @Override
+            public void run() {
+                Process.setThreadPriority(mPriority);
+                super.run();
+            }
+        }
+    }
+}
diff --git a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
index b599ab3..e57f6c55 100644
--- a/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
+++ b/core/core/src/main/java/androidx/core/provider/FontRequestWorker.java
@@ -23,6 +23,7 @@
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_PROVIDER_NOT_FOUND;
 import static androidx.core.provider.FontsContractCompat.FontRequestCallback.FAIL_REASON_WRONG_CERTIFICATES;
 
+import android.annotation.SuppressLint;
 import android.content.Context;
 import android.content.pm.PackageManager;
 import android.graphics.Typeface;
@@ -37,7 +38,7 @@
 import androidx.core.content.res.FontResourcesParserCompat;
 import androidx.core.content.res.ResourcesCompat;
 import androidx.core.graphics.TypefaceCompat;
-import androidx.core.provider.SelfDestructiveThread.ReplyCallback;
+import androidx.core.provider.FontRequestThreadPool.ReplyCallback;
 
 import java.util.ArrayList;
 import java.util.concurrent.Callable;
@@ -48,10 +49,11 @@
 
     static final LruCache<String, Typeface> sTypefaceCache = new LruCache<>(16);
 
-    private static final int BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS = 10000;
-    private static final SelfDestructiveThread BACKGROUND_THREAD =
-            new SelfDestructiveThread("fonts-androidx", Process.THREAD_PRIORITY_BACKGROUND,
-                    BACKGROUND_THREAD_KEEP_ALIVE_DURATION_MS);
+    private static final FontRequestThreadPool BACKGROUND_THREAD = new FontRequestThreadPool(
+            "fonts-androidx",
+            Process.THREAD_PRIORITY_BACKGROUND,
+            10000 /* keepAliveTime */
+    );
 
     /** Package protected to prevent synthetic accessor */
     static final Object LOCK = new Object();
@@ -174,7 +176,7 @@
             @Nullable final ResourcesCompat.FontCallback fontCallback,
             @Nullable final Handler handler, boolean isBlockingFetch, int timeout,
             final int style) {
-        final String id = request.getIdentifier() + "-" + style;
+        final String id = request.getId() + "-" + style;
         Typeface cached = sTypefaceCache.get(id);
         if (cached != null) {
             if (fontCallback != null) {
@@ -198,7 +200,7 @@
 
         final Callable<TypefaceResult> fetcher = new Callable<TypefaceResult>() {
             @Override
-            public TypefaceResult call() throws Exception {
+            public TypefaceResult call() {
                 TypefaceResult typeface = getFontInternal(context, request, style);
                 if (typeface.mTypeface != null) {
                     sTypefaceCache.put(id, typeface.mTypeface);
@@ -214,21 +216,21 @@
                 return null;
             }
         } else {
-            final ReplyCallback<TypefaceResult> reply = fontCallback == null ? null
-                    : new ReplyCallback<TypefaceResult>() {
-                        @Override
-                        public void onReply(final TypefaceResult typeface) {
-                            if (typeface == null) {
-                                fontCallback.callbackFailAsync(
-                                        FAIL_REASON_FONT_NOT_FOUND, handler);
-                            } else if (typeface.mResult
-                                    == FontsContractCompat.FontFamilyResult.STATUS_OK) {
-                                fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
-                            } else {
-                                fontCallback.callbackFailAsync(typeface.mResult, handler);
-                            }
-                        }
-                    };
+            final ReplyCallback<TypefaceResult> reply = fontCallback == null ? null :
+                    new ReplyCallback<TypefaceResult>() {
+                @Override
+                public void onReply(final TypefaceResult typeface) {
+                    if (typeface == null) {
+                        fontCallback.callbackFailAsync(
+                                FAIL_REASON_FONT_NOT_FOUND, handler);
+                    } else if (typeface.mResult
+                            == FontsContractCompat.FontFamilyResult.STATUS_OK) {
+                        fontCallback.callbackSuccessAsync(typeface.mTypeface, handler);
+                    } else {
+                        fontCallback.callbackFailAsync(typeface.mResult, handler);
+                    }
+                }
+            };
 
             synchronized (LOCK) {
                 ArrayList<ReplyCallback<TypefaceResult>> pendingReplies = PENDING_REPLIES.get(id);
@@ -267,6 +269,7 @@
     }
 
     /** Package protected to prevent synthetic accessor */
+    @SuppressLint("WrongConstant")
     @NonNull
     static TypefaceResult getFontInternal(
             @NonNull final Context context,
@@ -282,7 +285,7 @@
             final Typeface typeface = TypefaceCompat.createFromFontInfo(
                     context, null /* CancellationSignal */, result.getFonts(), style);
             return new TypefaceResult(typeface, typeface != null
-                    ? FontsContractCompat.FontRequestCallback.RESULT_OK
+                    ? FontsContractCompat.FontRequestCallback.RESULT_SUCCESS
                     : FAIL_REASON_FONT_LOAD_ERROR);
         }
         int resultCode = result.getStatusCode() == STATUS_WRONG_CERTIFICATES
diff --git a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
index 4f9327a..a878fd3 100644
--- a/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
+++ b/core/core/src/main/java/androidx/core/provider/FontsContractCompat.java
@@ -16,6 +16,7 @@
 
 package androidx.core.provider;
 
+import static androidx.annotation.RestrictTo.Scope.LIBRARY;
 import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP_PREFIX;
 
 import android.content.Context;
@@ -52,28 +53,6 @@
 public class FontsContractCompat {
     private FontsContractCompat() { }
 
-    // TODO remove unused
-    /**
-     * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
-     * returned to the ResultReceiver in getFont.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    public static final String PARCEL_FONT_RESULTS = "font_results";
-
-    // TODO remove unused
-    // Error codes internal to the system, which can not come from a provider. To keep the number
-    // space open for new provider codes, these should all be negative numbers.
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
-
-    // TODO remove unused
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
-    // Note -3 is used by FontRequestCallback to indicate the font failed to load.
-
     // TODO deprecated from here, move to TypefaceCompat
     /**
      * Build a Typeface from an array of {@link FontInfo}
@@ -88,8 +67,8 @@
      * @return A Typeface object. Returns null if typeface creation fails.
      */
     @Nullable
-    public static Typeface buildTypeface(@
-            NonNull Context context,
+    public static Typeface buildTypeface(
+            @NonNull Context context,
             @Nullable CancellationSignal cancellationSignal,
             @NonNull FontInfo[] fonts
     ) {
@@ -143,66 +122,30 @@
         return FontProvider.getFontFamilyResult(context, request, cancellationSignal);
     }
 
-    // TODO remove, replace with LIBRARY private, used for tests
     /**
-     * Used for tests, should not be used otherwise.
+     * Used by TypefaceCompat and tests.
      * @hide
-     **/
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    public static void resetCache() {
-        FontRequestWorker.resetTypefaceCache();
-    }
-
-    // TODO @RestrictTo(LIBRARY)
-    /** @hide */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    public static Typeface getFontSync(
-            final Context context,
-            final FontRequest request,
-            final @Nullable ResourcesCompat.FontCallback fontCallback,
-            final @Nullable Handler handler,
+     */
+    @RestrictTo(LIBRARY)
+    @Nullable
+    public static Typeface getFont(
+            @NonNull final Context context,
+            @NonNull final FontRequest request,
+            @Nullable final ResourcesCompat.FontCallback fontCallback,
+            @Nullable final Handler handler,
             boolean isBlockingFetch,
-            int timeout,
+            @IntRange(from = 0) int timeout,
             final int style
     ) {
         return FontRequestWorker.getTypeface(context, request, fontCallback, handler,
                 isBlockingFetch, timeout, style);
     }
 
-    // TODO remove
-    /**
-     * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
-     *
-     * Skip if the file contents is not ready to be read.
-     *
-     * @param context A {@link Context} to be used for resolving content URI in
-     *                {@link FontInfo}.
-     * @param fonts An array of {@link FontInfo}.
-     * @return A map from {@link Uri} to {@link ByteBuffer}.
-     * @hide
-     */
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    @RequiresApi(19)
-    public static Map<Uri, ByteBuffer> prepareFontData(
-            Context context,
-            FontInfo[] fonts,
-            CancellationSignal cancellationSignal
-    ) {
-        return TypefaceCompatUtil.readFontInfoIntoByteBuffer(context, fonts, cancellationSignal);
-    }
-
-
-    // TODO: Remove, unused
     /** @hide */
     @VisibleForTesting
-    @RestrictTo(LIBRARY_GROUP_PREFIX)
-    @Nullable
-    public static ProviderInfo getProvider(
-            @NonNull PackageManager packageManager,
-            @NonNull FontRequest request,
-            @Nullable Resources resources
-    ) throws PackageManager.NameNotFoundException {
-        return FontProvider.getProvider(packageManager, request, resources);
+    @RestrictTo(LIBRARY)
+    public static void resetTypefaceCache() {
+        FontRequestWorker.resetTypefaceCache();
     }
 
     /**
@@ -302,14 +245,20 @@
          * @param italic A boolean that indicates the font is italic style or not.
          * @param resultCode A boolean that indicates the font contents is ready.
          *
+         * @deprecated Not being used by any cross library, and should not be used, internal
+         * implementation detail.
+         *
          * @hide
          */
+        // TODO after removing from public API make package private.
+        @Deprecated
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         public FontInfo(
                 @NonNull Uri uri,
                 @IntRange(from = 0) int ttcIndex,
                 @IntRange(from = 1, to = 1000) int weight,
-                boolean italic, int resultCode
+                boolean italic,
+                int resultCode
         ) {
             mUri = Preconditions.checkNotNull(uri);
             mTtcIndex = ttcIndex;
@@ -318,6 +267,17 @@
             mResultCode = resultCode;
         }
 
+        @SuppressWarnings("deprecation")
+        static FontInfo create(
+                @NonNull Uri uri,
+                @IntRange(from = 0) int ttcIndex,
+                @IntRange(from = 1, to = 1000) int weight,
+                boolean italic,
+                int resultCode
+        ) {
+            return new FontInfo(uri, ttcIndex, weight, italic, resultCode);
+        }
+
         /**
          * Returns a URI associated to this record.
          */
@@ -381,7 +341,7 @@
         public static final int STATUS_UNEXPECTED_DATA_PROVIDED = 2;
 
         /** @hide */
-        @RestrictTo(LIBRARY_GROUP_PREFIX)
+        @RestrictTo(LIBRARY)
         @IntDef({STATUS_OK, STATUS_WRONG_CERTIFICATES, STATUS_UNEXPECTED_DATA_PROVIDED})
         @Retention(RetentionPolicy.SOURCE)
         @interface FontResultStatus {}
@@ -389,7 +349,13 @@
         private final @FontResultStatus int mStatusCode;
         private final FontInfo[] mFonts;
 
-        /** @hide */
+        /**
+         * @deprecated Not being used by any cross library, and should not be used, internal
+         * implementation detail.
+         * @hide
+         **/
+        // TODO after removing from public API make package private.
+        @Deprecated
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         public FontFamilyResult(@FontResultStatus int statusCode, @Nullable FontInfo[] fonts) {
             mStatusCode = statusCode;
@@ -403,16 +369,30 @@
         public FontInfo[] getFonts() {
             return mFonts;
         }
+
+        @SuppressWarnings("deprecation")
+        static FontFamilyResult create(
+                @FontResultStatus int statusCode,
+                @Nullable FontInfo[] fonts) {
+            return new FontFamilyResult(statusCode, fonts);
+        }
     }
 
     /**
      * Interface used to receive asynchronously fetched typefaces.
      */
     public static class FontRequestCallback {
-        /** @hide */
+        /**
+         * @deprecated Not being used by any cross library, and should not be used, internal
+         * implementation detail.
+         * @hide
+         */
+        @Deprecated
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         public static final int RESULT_OK = Columns.RESULT_CODE_OK;
 
+        static final int RESULT_SUCCESS = Columns.RESULT_CODE_OK;
+
         /**
          * Constant returned by {@link #onTypefaceRequestFailed(int)} signaling that the given
          * provider was not found on the device.
@@ -455,7 +435,9 @@
          */
         public static final int FAIL_REASON_MALFORMED_QUERY = Columns.RESULT_CODE_MALFORMED_QUERY;
 
+        // TODO Move to @RestrictTo(LIBRARY)
         /** @hide */
+        @SuppressWarnings("deprecation")
         @RestrictTo(LIBRARY_GROUP_PREFIX)
         @IntDef({ FAIL_REASON_PROVIDER_NOT_FOUND, FAIL_REASON_FONT_LOAD_ERROR,
                 FAIL_REASON_FONT_NOT_FOUND, FAIL_REASON_FONT_UNAVAILABLE,
@@ -488,5 +470,111 @@
         public void onTypefaceRequestFailed(@FontRequestFailReason int reason) {}
     }
 
+    /**
+     * Constant used to identify the List of {@link ParcelFileDescriptor} item in the Bundle
+     * returned to the ResultReceiver in getFont.
+     *
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     *
+     * @hide
+     */
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    public static final String PARCEL_FONT_RESULTS = "font_results";
 
+    // Error codes internal to the system, which can not come from a provider. To keep the number
+    // space open for new provider codes, these should all be negative numbers.
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     * @hide
+     **/
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    static final int RESULT_CODE_PROVIDER_NOT_FOUND = -1;
+
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     * @hide
+     **/
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    static final int RESULT_CODE_WRONG_CERTIFICATES = -2;
+    // Note -3 is used by FontRequestCallback to indicate the font failed to load.
+
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     * @hide
+     **/
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    public static Typeface getFontSync(
+            final Context context,
+            final FontRequest request,
+            final @Nullable ResourcesCompat.FontCallback fontCallback,
+            final @Nullable Handler handler,
+            boolean isBlockingFetch,
+            int timeout,
+            final int style
+    ) {
+        return FontRequestWorker.getTypeface(context, request, fontCallback, handler,
+                isBlockingFetch, timeout, style);
+    }
+
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     * @hide
+     **/
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    public static void resetCache() {
+        FontRequestWorker.resetTypefaceCache();
+    }
+
+    /**
+     * A helper function to create a mapping from {@link Uri} to {@link ByteBuffer}.
+     *
+     * Skip if the file contents is not ready to be read.
+     *
+     * @param context A {@link Context} to be used for resolving content URI in
+     *                {@link FontInfo}.
+     * @param fonts An array of {@link FontInfo}.
+     * @return A map from {@link Uri} to {@link ByteBuffer}.
+     *
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     *
+     * @hide
+     */
+    @Deprecated // unused
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @RequiresApi(19)
+    public static Map<Uri, ByteBuffer> prepareFontData(
+            Context context,
+            FontInfo[] fonts,
+            CancellationSignal cancellationSignal
+    ) {
+        return TypefaceCompatUtil.readFontInfoIntoByteBuffer(context, fonts, cancellationSignal);
+    }
+
+    /**
+     * @deprecated Not being used by any cross library, and should not be used, internal
+     * implementation detail.
+     * @hide
+     **/
+    @Deprecated // unused
+    @VisibleForTesting
+    @RestrictTo(LIBRARY_GROUP_PREFIX)
+    @Nullable
+    public static ProviderInfo getProvider(
+            @NonNull PackageManager packageManager,
+            @NonNull FontRequest request,
+            @Nullable Resources resources
+    ) throws PackageManager.NameNotFoundException {
+        return FontProvider.getProvider(packageManager, request, resources);
+    }
 }
diff --git a/core/core/src/main/java/androidx/core/provider/SelfDestructiveThread.java b/core/core/src/main/java/androidx/core/provider/SelfDestructiveThread.java
index 7a8e69c..9ccb661 100644
--- a/core/core/src/main/java/androidx/core/provider/SelfDestructiveThread.java
+++ b/core/core/src/main/java/androidx/core/provider/SelfDestructiveThread.java
@@ -37,8 +37,12 @@
 /**
  * Background thread which is destructed after certain period after all pending activities are
  * finished.
+ *
+ * @deprecated Not being used by any cross library, and should not be used, internal
+ * implementation detail.
  * @hide
  */
+@Deprecated
 @RestrictTo(LIBRARY_GROUP_PREFIX)
 public class SelfDestructiveThread {
     private final Object mLock = new Object();
diff --git a/datastore/datastore-preferences/api/current.txt b/datastore/datastore-preferences/api/current.txt
index 2dbaa4c..c31d7d3 100644
--- a/datastore/datastore-preferences/api/current.txt
+++ b/datastore/datastore-preferences/api/current.txt
@@ -9,7 +9,8 @@
   }
 
   public final class SharedPreferencesMigrationKt {
-    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate, optional boolean deleteEmptyPreferences);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String>? keysToMigrate);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName);
   }
diff --git a/datastore/datastore-preferences/api/public_plus_experimental_current.txt b/datastore/datastore-preferences/api/public_plus_experimental_current.txt
index 2dbaa4c..c31d7d3 100644
--- a/datastore/datastore-preferences/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-preferences/api/public_plus_experimental_current.txt
@@ -9,7 +9,8 @@
   }
 
   public final class SharedPreferencesMigrationKt {
-    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate, optional boolean deleteEmptyPreferences);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String>? keysToMigrate);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName);
   }
diff --git a/datastore/datastore-preferences/api/restricted_current.txt b/datastore/datastore-preferences/api/restricted_current.txt
index 2dbaa4c..c31d7d3 100644
--- a/datastore/datastore-preferences/api/restricted_current.txt
+++ b/datastore/datastore-preferences/api/restricted_current.txt
@@ -9,7 +9,8 @@
   }
 
   public final class SharedPreferencesMigrationKt {
-    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate, optional boolean deleteEmptyPreferences);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, optional java.util.Set<java.lang.String>? keysToMigrate);
+    method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, optional java.util.Set<java.lang.String>? keysToMigrate);
     method public static androidx.datastore.migrations.SharedPreferencesMigration<androidx.datastore.preferences.core.Preferences> SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName);
   }
diff --git a/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
index f24d608..786da57 100644
--- a/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
+++ b/datastore/datastore-preferences/src/androidTest/java/androidx/datastore/preferences/SharedPreferencesToPreferencesTest.kt
@@ -250,7 +250,6 @@
             context = context,
             sharedPreferencesName = sharedPrefsName,
             keysToMigrate = setOf(integerKey.name),
-            deleteEmptyPreferences = true
         )
 
         val preferenceStore = getDataStoreWithMigrations(listOf(migration))
@@ -260,27 +259,6 @@
     }
 
     @Test
-    fun sharedPreferencesFileNotDeletedIfDisabled() = runBlockingTest {
-        val integerKey = intPreferencesKey("integer_key")
-
-        assertTrue { sharedPrefs.edit().putInt(integerKey.name, 123).commit() }
-
-        val migration = SharedPreferencesMigration(
-            context = context,
-            sharedPreferencesName = sharedPrefsName,
-            keysToMigrate = setOf(integerKey.name),
-            deleteEmptyPreferences = false
-        )
-
-        val preferenceStore = getDataStoreWithMigrations(listOf(migration))
-        preferenceStore.data.first()
-
-        assertTrue {
-            getSharedPrefsFile(context, sharedPrefsName).exists()
-        }
-    }
-
-    @Test
     fun sharedPreferencesFileNotDeletedIfPrefsNotEmpty() = runBlockingTest {
         val integerKey1 = intPreferencesKey("integer_key1")
         val integerKey2 = intPreferencesKey("integer_key2")
@@ -293,7 +271,6 @@
             context = context,
             sharedPreferencesName = sharedPrefsName,
             keysToMigrate = setOf(integerKey1.name),
-            deleteEmptyPreferences = true
         )
 
         val preferenceStore = getDataStoreWithMigrations(listOf(migration))
@@ -317,7 +294,6 @@
             context = context,
             sharedPreferencesName = sharedPrefsName,
             keysToMigrate = setOf(integerKey.name),
-            deleteEmptyPreferences = true
         )
 
         val preferenceStore = getDataStoreWithMigrations(listOf(migration))
@@ -399,6 +375,21 @@
         assertEquals(1, prefs.asMap().size)
     }
 
+    @Test
+    fun producedSharedPreferencesIsUsed() = runBlockingTest {
+        val integerKey = intPreferencesKey("integer_key")
+        val integerValue = 123
+
+        assertTrue { sharedPrefs.edit().putInt(integerKey.name, integerValue).commit() }
+
+        val migration = SharedPreferencesMigration(produceSharedPreferences = { sharedPrefs })
+
+        val preferencesStore = getDataStoreWithMigrations(listOf(migration))
+        val prefs = preferencesStore.data.first()
+        assertEquals(integerValue, prefs[integerKey])
+        assertEquals(1, prefs.asMap().size)
+    }
+
     private fun getDataStoreWithMigrations(
         migrations: List<DataMigration<Preferences>>
     ): DataStore<Preferences> {
diff --git a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
index b91abe3..157a9bf 100644
--- a/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
+++ b/datastore/datastore-preferences/src/main/java/androidx/datastore/preferences/SharedPreferencesMigration.kt
@@ -17,6 +17,7 @@
 package androidx.datastore.preferences
 
 import android.content.Context
+import android.content.SharedPreferences
 import androidx.datastore.migrations.SharedPreferencesView
 import androidx.datastore.migrations.SharedPreferencesMigration
 import androidx.datastore.preferences.core.Preferences
@@ -30,78 +31,103 @@
 /**
  * Creates a SharedPreferencesMigration for DataStore<Preferences>.
  *
+ * Note: This migration only supports the basic SharedPreferences types: boolean, float, int,
+ * long, string and string set. If the result of getAll contains other types, they will be ignored.
+ *
+ * @param produceSharedPreferences Should return the instance of SharedPreferences to migrate from.
+ * @param keysToMigrate The list of keys to migrate. The keys will be mapped to
+ * datastore.Preferences with their same values. If the key is already present in the new
+ * Preferences, the key will not be migrated again. If the key is not present in the
+ * SharedPreferences it will not be migrated. If keysToMigrate is not set, all keys will be
+ * migrated from the existing SharedPreferences.
+ */
+@JvmOverloads // Generate methods for default params for java users.
+public fun SharedPreferencesMigration(
+    produceSharedPreferences: () -> SharedPreferences,
+    keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS
+): SharedPreferencesMigration<Preferences> {
+    return SharedPreferencesMigration(
+        produceSharedPreferences = produceSharedPreferences,
+        keysToMigrate = keysToMigrate,
+        shouldRunMigration = getShouldRunMigration(keysToMigrate),
+        migrate = getMigrationFunction(),
+    )
+}
+
+/**
+ * Creates a SharedPreferencesMigration for DataStore<Preferences>.
+ *
+ * If the SharedPreferences is empty once the migration completes, this migration will attempt to
+ * delete it.
+ *
  * @param context Context used for getting SharedPreferences.
  * @param sharedPreferencesName The name of the SharedPreferences.
- * @param keysToMigrate The list of keys to migrate. The keys will be mapped to datastore.Preferences with
- * their same values. If the key is already present in the new Preferences, the key
- * will not be migrated again. If the key is not present in the SharedPreferences it
- * will not be migrated. If keysToMigrate is not set, all keys will be migrated from the existing
- * SharedPreferences.
- * @param deleteEmptyPreferences If enabled and the SharedPreferences are empty (i.e. no remaining
- * keys) after this migration runs, the leftover SharedPreferences file is deleted. Note that
- * this cleanup runs only if the migration itself runs, i.e., if the keys were never in
- * SharedPreferences to begin with then the (potentially) empty SharedPreferences
- * won't be cleaned up by this option. This functionality is best effort - if there
- * is an issue deleting the SharedPreferences file it will be silently ignored.
- *
- * TODO(rohitsat): determine whether to remove the deleteEmptyPreferences option.
+ * @param keysToMigrate The list of keys to migrate. The keys will be mapped to
+ * datastore.Preferences with their same values. If the key is already present in the new
+ * Preferences, the key will not be migrated again. If the key is not present in the
+ * SharedPreferences it will not be migrated. If keysToMigrate is not set, all keys will be
+ * migrated from the existing SharedPreferences.
  */
 @JvmOverloads // Generate methods for default params for java users.
 public fun SharedPreferencesMigration(
     context: Context,
     sharedPreferencesName: String,
     keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
-    deleteEmptyPreferences: Boolean = true
 ): SharedPreferencesMigration<Preferences> {
     return SharedPreferencesMigration(
         context = context,
         sharedPreferencesName = sharedPreferencesName,
         keysToMigrate = keysToMigrate,
-        deleteEmptyPreferences = deleteEmptyPreferences,
-        shouldRunMigration = { prefs ->
-            // If any key hasn't been migrated to currentData, we can't skip the migration. If
-            // the key set is not specified, we can't skip the migration.
-            val allKeys = prefs.asMap().keys.map { it.name }
-            keysToMigrate?.any { it !in allKeys } ?: true
-        },
-        migrate = { sharedPrefs: SharedPreferencesView, currentData: Preferences ->
-            // prefs.getAll is already filtered to our key set, but we don't want to overwrite
-            // already existing keys.
-            val currentKeys = currentData.asMap().keys.map { it.name }
-
-            val filteredSharedPreferences =
-                sharedPrefs.getAll().filter { (key, _) -> key !in currentKeys }
-
-            val mutablePreferences = currentData.toMutablePreferences()
-            for ((key, value) in filteredSharedPreferences) {
-                when (value) {
-                    is Boolean -> mutablePreferences[
-                        booleanPreferencesKey(key)
-                    ] = value
-                    is Float -> mutablePreferences[
-                        floatPreferencesKey(key)
-                    ] = value
-                    is Int -> mutablePreferences[
-                        intPreferencesKey(key)
-                    ] = value
-                    is Long -> mutablePreferences[
-                        longPreferencesKey(key)
-                    ] = value
-                    is String -> mutablePreferences[
-                        stringPreferencesKey(key)
-                    ] = value
-                    is Set<*> -> {
-                        @Suppress("UNCHECKED_CAST")
-                        mutablePreferences[
-                            stringSetPreferencesKey(key)
-                        ] = value as Set<String>
-                    }
-                }
-            }
-
-            mutablePreferences.toPreferences()
-        }
+        shouldRunMigration = getShouldRunMigration(keysToMigrate),
+        migrate = getMigrationFunction()
     )
 }
 
+private fun getMigrationFunction(): suspend (SharedPreferencesView, Preferences) -> Preferences =
+    { sharedPrefs: SharedPreferencesView, currentData: Preferences ->
+        // prefs.getAll is already filtered to our key set, but we don't want to overwrite
+        // already existing keys.
+        val currentKeys = currentData.asMap().keys.map { it.name }
+
+        val filteredSharedPreferences =
+            sharedPrefs.getAll().filter { (key, _) -> key !in currentKeys }
+
+        val mutablePreferences = currentData.toMutablePreferences()
+        for ((key, value) in filteredSharedPreferences) {
+            when (value) {
+                is Boolean -> mutablePreferences[
+                    booleanPreferencesKey(key)
+                ] = value
+                is Float -> mutablePreferences[
+                    floatPreferencesKey(key)
+                ] = value
+                is Int -> mutablePreferences[
+                    intPreferencesKey(key)
+                ] = value
+                is Long -> mutablePreferences[
+                    longPreferencesKey(key)
+                ] = value
+                is String -> mutablePreferences[
+                    stringPreferencesKey(key)
+                ] = value
+                is Set<*> -> {
+                    @Suppress("UNCHECKED_CAST")
+                    mutablePreferences[
+                        stringSetPreferencesKey(key)
+                    ] = value as Set<String>
+                }
+            }
+        }
+
+        mutablePreferences.toPreferences()
+    }
+
+private fun getShouldRunMigration(keysToMigrate: Set<String>?): suspend (Preferences) -> Boolean =
+    { prefs ->
+        // If any key hasn't been migrated to currentData, we can't skip the migration. If
+        // the key set is not specified, we can't skip the migration.
+        val allKeys = prefs.asMap().keys.map { it.name }
+        keysToMigrate?.any { it !in allKeys } ?: true
+    }
+
 internal val MIGRATE_ALL_KEYS = null
\ No newline at end of file
diff --git a/datastore/datastore-rxjava2/api/current.txt b/datastore/datastore-rxjava2/api/current.txt
index 4878ed3..b55ea08 100644
--- a/datastore/datastore-rxjava2/api/current.txt
+++ b/datastore/datastore-rxjava2/api/current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt b/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
index 4878ed3..b55ea08 100644
--- a/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-rxjava2/api/public_plus_experimental_current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava2/api/restricted_current.txt b/datastore/datastore-rxjava2/api/restricted_current.txt
index 4878ed3..b55ea08 100644
--- a/datastore/datastore-rxjava2/api/restricted_current.txt
+++ b/datastore/datastore-rxjava2/api/restricted_current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava2.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava2.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java
index d97261b..1461ee7 100644
--- a/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java
+++ b/datastore/datastore-rxjava2/src/androidTest/java/androidx/datastore/rxjava2/RxSharedPreferencesMigrationTest.java
@@ -156,26 +156,6 @@
         assertThat(mSharedPrefs.contains(includedKey2)).isFalse();
     }
 
-    @Test
-    public void testDeletesEmptySharedPreferences() {
-        String key = "key";
-        String value = "value";
-        assertThat(mSharedPrefs.edit().putString(key, value).commit()).isTrue();
-
-        DataMigration<Byte> dataMigration =
-                getSpMigrationBuilder(new DefaultMigration()).setDeleteEmptyPreferences(
-                        true).build();
-        RxDataStore<Byte> byteStore = getDataStoreWithMigration(dataMigration);
-        assertThat(byteStore.data().blockingFirst()).isEqualTo(0);
-
-        // Check that the shared preferences files are deleted
-        File prefsDir = new File(mContext.getApplicationInfo().dataDir, "shared_prefs");
-        File prefsFile = new File(prefsDir, mSharedPrefsName + ".xml");
-        File backupPrefsFile = new File(prefsFile.getPath() + ".bak");
-        assertThat(prefsFile.exists()).isFalse();
-        assertThat(backupPrefsFile.exists()).isFalse();
-    }
-
     private RxSharedPreferencesMigrationBuilder<Byte> getSpMigrationBuilder(
             RxSharedPreferencesMigration<Byte> rxSharedPreferencesMigration) {
         return new RxSharedPreferencesMigrationBuilder<Byte>(mContext, mSharedPrefsName,
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 e84e377..0b294be 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
@@ -71,8 +71,6 @@
     private val rxSharedPreferencesMigration: RxSharedPreferencesMigration<T>
 ) {
 
-    /** Optional */
-    private var deleteEmptyPreference: Boolean = true
     private var keysToMigrate: Set<String>? = null
 
     /**
@@ -94,24 +92,10 @@
         }
 
     /**
-     * If enabled and the SharedPreferences are empty (i.e. no remaining
-     * keys) after this migration runs, the leftover SharedPreferences file is deleted. Note that
-     * this cleanup runs only if the migration itself runs, i.e., if the keys were never in
-     * SharedPreferences to begin with then the (potentially) empty SharedPreferences
-     * won't be cleaned up by this option. This functionality is best effort - if there
-     * is an issue deleting the SharedPreferences file it will be silently ignored.
+     * Build and return the DataMigration instance.
      *
-     * This method is optional and defaults to true.
-     *
-     * @param deleteEmptyPreferences whether or not to delete the empty shared preferences file
-     * @return this
+     * @return the DataMigration.
      */
-    @Suppress("MissingGetterMatchingBuilder")
-    public fun setDeleteEmptyPreferences(deleteEmptyPreferences: Boolean):
-        RxSharedPreferencesMigrationBuilder<T> = apply {
-            this.deleteEmptyPreference = deleteEmptyPreferences
-        }
-
     public fun build(): DataMigration<T> {
         return SharedPreferencesMigration(
             context = context,
@@ -120,7 +104,6 @@
                 rxSharedPreferencesMigration.migrate(spView, curData).await()
             },
             keysToMigrate = keysToMigrate,
-            deleteEmptyPreferences = deleteEmptyPreference,
             shouldRunMigration = { curData ->
                 rxSharedPreferencesMigration.shouldMigrate(curData).await()
             }
diff --git a/datastore/datastore-rxjava3/api/current.txt b/datastore/datastore-rxjava3/api/current.txt
index 35b64273..2a6f395 100644
--- a/datastore/datastore-rxjava3/api/current.txt
+++ b/datastore/datastore-rxjava3/api/current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava3.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava3/api/public_plus_experimental_current.txt b/datastore/datastore-rxjava3/api/public_plus_experimental_current.txt
index 35b64273..2a6f395 100644
--- a/datastore/datastore-rxjava3/api/public_plus_experimental_current.txt
+++ b/datastore/datastore-rxjava3/api/public_plus_experimental_current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava3.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava3/api/restricted_current.txt b/datastore/datastore-rxjava3/api/restricted_current.txt
index 35b64273..2a6f395 100644
--- a/datastore/datastore-rxjava3/api/restricted_current.txt
+++ b/datastore/datastore-rxjava3/api/restricted_current.txt
@@ -33,7 +33,6 @@
   public final class RxSharedPreferencesMigrationBuilder<T> {
     ctor public RxSharedPreferencesMigrationBuilder(android.content.Context context, String sharedPreferencesName, androidx.datastore.rxjava3.RxSharedPreferencesMigration<T> rxSharedPreferencesMigration);
     method public androidx.datastore.core.DataMigration<T> build();
-    method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setDeleteEmptyPreferences(boolean deleteEmptyPreferences);
     method public androidx.datastore.rxjava3.RxSharedPreferencesMigrationBuilder<T> setKeysToMigrate(java.lang.String... keys);
   }
 
diff --git a/datastore/datastore-rxjava3/src/androidTest/java/androidx/datastore/rxjava3/RxSharedPreferencesMigrationTest.java b/datastore/datastore-rxjava3/src/androidTest/java/androidx/datastore/rxjava3/RxSharedPreferencesMigrationTest.java
index 9d3d36b..10a141c 100644
--- a/datastore/datastore-rxjava3/src/androidTest/java/androidx/datastore/rxjava3/RxSharedPreferencesMigrationTest.java
+++ b/datastore/datastore-rxjava3/src/androidTest/java/androidx/datastore/rxjava3/RxSharedPreferencesMigrationTest.java
@@ -156,25 +156,7 @@
         assertThat(mSharedPrefs.contains(includedKey2)).isFalse();
     }
 
-    @Test
-    public void testDeletesEmptySharedPreferences() {
-        String key = "key";
-        String value = "value";
-        assertThat(mSharedPrefs.edit().putString(key, value).commit()).isTrue();
 
-        DataMigration<Byte> dataMigration =
-                getSpMigrationBuilder(new DefaultMigration()).setDeleteEmptyPreferences(
-                        true).build();
-        RxDataStore<Byte> byteStore = getDataStoreWithMigration(dataMigration);
-        assertThat(byteStore.data().blockingFirst()).isEqualTo(0);
-
-        // Check that the shared preferences files are deleted
-        File prefsDir = new File(mContext.getApplicationInfo().dataDir, "shared_prefs");
-        File prefsFile = new File(prefsDir, mSharedPrefsName + ".xml");
-        File backupPrefsFile = new File(prefsFile.getPath() + ".bak");
-        assertThat(prefsFile.exists()).isFalse();
-        assertThat(backupPrefsFile.exists()).isFalse();
-    }
 
     private RxSharedPreferencesMigrationBuilder<Byte> getSpMigrationBuilder(
             RxSharedPreferencesMigration<Byte> rxSharedPreferencesMigration) {
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 e5f71c6..7e76603 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
@@ -71,8 +71,6 @@
     private val rxSharedPreferencesMigration: RxSharedPreferencesMigration<T>
 ) {
 
-    /** Optional */
-    private var deleteEmptyPreference: Boolean = true
     private var keysToMigrate: Set<String>? = null
 
     /**
@@ -88,30 +86,16 @@
      * @return this
      */
     @Suppress("MissingGetterMatchingBuilder")
-    public fun setKeysToMigrate(vararg keys: String):
-        RxSharedPreferencesMigrationBuilder<T> = apply {
+    public fun setKeysToMigrate(vararg keys: String): RxSharedPreferencesMigrationBuilder<T> =
+        apply {
             keysToMigrate = setOf(*keys)
         }
 
     /**
-     * If enabled and the SharedPreferences are empty (i.e. no remaining
-     * keys) after this migration runs, the leftover SharedPreferences file is deleted. Note that
-     * this cleanup runs only if the migration itself runs, i.e., if the keys were never in
-     * SharedPreferences to begin with then the (potentially) empty SharedPreferences
-     * won't be cleaned up by this option. This functionality is best effort - if there
-     * is an issue deleting the SharedPreferences file it will be silently ignored.
+     * Build and return the DataMigration instance.
      *
-     * This method is optional and defaults to true.
-     *
-     * @param deleteEmptyPreferences whether or not to delete the empty shared preferences file
-     * @return this
+     * @return the DataMigration.
      */
-    @Suppress("MissingGetterMatchingBuilder")
-    public fun setDeleteEmptyPreferences(deleteEmptyPreferences: Boolean):
-        RxSharedPreferencesMigrationBuilder<T> = apply {
-            this.deleteEmptyPreference = deleteEmptyPreferences
-        }
-
     public fun build(): DataMigration<T> {
         return SharedPreferencesMigration(
             context = context,
@@ -120,10 +104,9 @@
                 rxSharedPreferencesMigration.migrate(spView, curData).await()
             },
             keysToMigrate = keysToMigrate,
-            deleteEmptyPreferences = deleteEmptyPreference,
             shouldRunMigration = { curData ->
                 rxSharedPreferencesMigration.shouldMigrate(curData).await()
             }
         )
     }
-}
+}
\ No newline at end of file
diff --git a/datastore/datastore/api/current.txt b/datastore/datastore/api/current.txt
index dab188c..7497360 100644
--- a/datastore/datastore/api/current.txt
+++ b/datastore/datastore/api/current.txt
@@ -13,8 +13,10 @@
 package androidx.datastore.migrations {
 
   public final class SharedPreferencesMigration<T> implements androidx.datastore.core.DataMigration<T> {
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit> p) throws java.io.IOException;
diff --git a/datastore/datastore/api/public_plus_experimental_current.txt b/datastore/datastore/api/public_plus_experimental_current.txt
index dab188c..7497360 100644
--- a/datastore/datastore/api/public_plus_experimental_current.txt
+++ b/datastore/datastore/api/public_plus_experimental_current.txt
@@ -13,8 +13,10 @@
 package androidx.datastore.migrations {
 
   public final class SharedPreferencesMigration<T> implements androidx.datastore.core.DataMigration<T> {
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit> p) throws java.io.IOException;
diff --git a/datastore/datastore/api/restricted_current.txt b/datastore/datastore/api/restricted_current.txt
index dab188c..7497360 100644
--- a/datastore/datastore/api/restricted_current.txt
+++ b/datastore/datastore/api/restricted_current.txt
@@ -13,8 +13,10 @@
 package androidx.datastore.migrations {
 
   public final class SharedPreferencesMigration<T> implements androidx.datastore.core.DataMigration<T> {
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
-    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, boolean deleteEmptyPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(kotlin.jvm.functions.Function0<? extends android.content.SharedPreferences> produceSharedPreferences, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
+    ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function2<? super T,? super kotlin.coroutines.Continuation<? super java.lang.Boolean>,?> shouldRunMigration, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, java.util.Set<java.lang.String>? keysToMigrate, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     ctor public SharedPreferencesMigration(android.content.Context context, String sharedPreferencesName, kotlin.jvm.functions.Function3<? super androidx.datastore.migrations.SharedPreferencesView,? super T,? super kotlin.coroutines.Continuation<? super T>,?> migrate);
     method @kotlin.jvm.Throws(exceptionClasses=IOException::class) public suspend Object? cleanUp(kotlin.coroutines.Continuation<? super kotlin.Unit> p) throws java.io.IOException;
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 6ef13b2..37bd740 100644
--- a/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
+++ b/datastore/datastore/src/androidTest/java/androidx/datastore/migrations/SharedPreferencesMigrationTest.kt
@@ -136,6 +136,22 @@
         assertThat(sharedPrefs.contains(key2)).isFalse()
     }
 
+    @Test
+    fun producedSharedPreferencesIsUsed() = runBlockingTest {
+        assertThat(sharedPrefs.edit().putInt("integer_key", 123).commit()).isTrue()
+
+        val migration = SharedPreferencesMigration(
+            produceSharedPreferences = { sharedPrefs }
+        ) { prefs: SharedPreferencesView, _: Byte ->
+            assertThat(prefs.getAll().size).isEqualTo(1)
+            assertThat(prefs.getInt("integer_key", 0)).isEqualTo(123)
+            123
+        }
+
+        val dataStore = getDataStoreWithMigrations(listOf(migration))
+        assertThat(dataStore.data.first()).isEqualTo(123)
+    }
+
     private fun getDataStoreWithMigrations(
         migrations: List<DataMigration<Byte>>
     ): DataStore<Byte> {
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 5d9240d..2252461 100644
--- a/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt
+++ b/datastore/datastore/src/main/java/androidx/datastore/migrations/SharedPreferencesMigration.kt
@@ -25,50 +25,109 @@
 import kotlin.jvm.Throws
 
 /**
- * DataMigration from SharedPreferences to DataStore.
- *
- * Example usage:
- *
- * val sharedPrefsMigration = SharedPreferencesMigration(
- *   context,
- *   mySharedPreferencesName
- * ) { prefs: SharedPreferencesView, myData: MyData ->
- *    myData.toBuilder().setCounter(prefs.getCounter(COUNTER_KEY, default = 0)).build()
- * }
- *
- * @param context Context used for getting SharedPreferences.
- * @param sharedPreferencesName The name of the SharedPreferences.
- * @param keysToMigrate The list of keys to migrate. The keys will be mapped to datastore
- * .Preferences with their same values. If the key is already present in the new Preferences, the key
- * will not be migrated again. If the key is not present in the SharedPreferences it will not be
- * migrated. If keysToMigrate is null, all keys will be migrated from the existing
- * SharedPreferences.
- * @param deleteEmptyPreferences If enabled and the SharedPreferences are empty (i.e. no remaining
- * keys) after this migration runs, the leftover SharedPreferences file is deleted. Note that
- * this cleanup runs only if the migration itself runs, i.e., if the keys were never in
- * SharedPreferences to begin with then the (potentially) empty SharedPreferences won't be
- * cleaned up by this option. This functionality is best effort - if there is an issue deleting
- * the SharedPreferences file it will be silently ignored.
- * @param migrate maps SharedPreferences into T. Implementations should be idempotent
- * 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.
+ * DataMigration instance for migrating from SharedPreferences to DataStore.
  */
 public class SharedPreferencesMigration<T>
-@JvmOverloads // Generate constructors for default params for java users.
-constructor(
-    private val context: Context,
-    private val sharedPreferencesName: String,
-    keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
-    private val deleteEmptyPreferences: Boolean = true,
+private constructor(
+    produceSharedPreferences: () -> SharedPreferences,
+    keysToMigrate: Set<String>?,
     private val shouldRunMigration: suspend (T) -> Boolean = { true },
-    private val migrate: suspend (SharedPreferencesView, T) -> T
+    private val migrate: suspend (SharedPreferencesView, T) -> T,
+    private val context: Context?,
+    private val name: String?
 ) : DataMigration<T> {
 
-    private val sharedPrefs: SharedPreferences by lazy {
-        context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE)
-    }
+    /**
+     * DataMigration from SharedPreferences to DataStore.
+     *
+     * Note: This migration only supports the basic SharedPreferences types: boolean, float, int,
+     * long, string and string set. If the result of getAll contains other types, they will be
+     * ignored.
+     *
+     *
+     * Example usage:
+     * ```
+     * val sharedPrefsMigration = SharedPreferencesMigration(
+     *   produceSharedPreferences = { EncryptedSharedPreferences.create(...) }
+     * ) { prefs: SharedPreferencesView, myData: MyData ->
+     *    myData.toBuilder().setCounter(prefs.getCounter(COUNTER_KEY, default = 0)).build()
+     * }
+     * ```
+     * @param produceSharedPreferences Should return the instance of SharedPreferences to migrate
+     * from.
+     * @param keysToMigrate The list of keys to migrate. The keys will be mapped to datastore
+     * .Preferences with their same values. If the key is already present in the new Preferences,
+     * the key will not be migrated again. If the key is not present in the SharedPreferences it
+     * will not be migrated. If keysToMigrate is null, all keys will be migrated from the existing
+     * SharedPreferences.
+     * @param migrate maps SharedPreferences into T. Implementations should be idempotent
+     * 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.
+     */
+    @JvmOverloads // Generate constructors for default params for java users.
+    public constructor(
+        produceSharedPreferences: () -> SharedPreferences,
+        keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
+        shouldRunMigration: suspend (T) -> Boolean = { true },
+        migrate: suspend (SharedPreferencesView, T) -> T
+    ) : this(
+        produceSharedPreferences,
+        keysToMigrate,
+        shouldRunMigration,
+        migrate,
+        context = null,
+        name = null
+    )
+
+    /**
+     * DataMigration from SharedPreferences to DataStore.
+     *
+     * If the SharedPreferences is empty once the migration completes, this migration will
+     * attempt to delete it.
+     *
+     * Example usage:
+     *
+     * ```
+     * val sharedPrefsMigration = SharedPreferencesMigration(
+     *   context,
+     *   mySharedPreferencesName
+     * ) { prefs: SharedPreferencesView, myData: MyData ->
+     *    myData.toBuilder().setCounter(prefs.getCounter(COUNTER_KEY, default = 0)).build()
+     * }
+     * ```
+     *
+     * @param context Context used for getting SharedPreferences.
+     * @param sharedPreferencesName The name of the SharedPreferences.
+     * @param keysToMigrate The list of keys to migrate. The keys will be mapped to datastore
+     * .Preferences with their same values. If the key is already present in the new Preferences,
+     * the key will not be migrated again. If the key is not present in the SharedPreferences it
+     * will not be migrated. If keysToMigrate is null, all keys will be migrated from the existing
+     * SharedPreferences.
+     * @param migrate maps SharedPreferences into T. Implementations should be idempotent
+     * 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.
+     */
+    @JvmOverloads // Generate constructors for default params for java users.
+    public constructor(
+        context: Context,
+        sharedPreferencesName: String,
+        keysToMigrate: Set<String>? = MIGRATE_ALL_KEYS,
+        shouldRunMigration: suspend (T) -> Boolean = { true },
+        migrate: suspend (SharedPreferencesView, T) -> T
+    ) : this(
+        { context.getSharedPreferences(sharedPreferencesName, Context.MODE_PRIVATE) },
+        keysToMigrate,
+        shouldRunMigration,
+        migrate,
+        context,
+        sharedPreferencesName
+    )
+
+    private val sharedPrefs: SharedPreferences by lazy(produceSharedPreferences)
 
     private val keySet: MutableSet<String> by lazy {
         (keysToMigrate ?: sharedPrefs.all.keys).toMutableSet()
@@ -100,13 +159,11 @@
         }
 
         if (!sharedPrefsEditor.commit()) {
-            throw IOException(
-                "Unable to delete migrated keys from SharedPreferences: $sharedPreferencesName"
-            )
+            throw IOException("Unable to delete migrated keys from SharedPreferences.")
         }
 
-        if (deleteEmptyPreferences && sharedPrefs.all.isEmpty()) {
-            deleteSharedPreferences(context, sharedPreferencesName)
+        if (sharedPrefs.all.isEmpty() && context != null && name != null) {
+            deleteSharedPreferences(context, name)
         }
 
         keySet.clear()
@@ -226,7 +283,7 @@
         }
     }
 
-    private fun checkKey(key: String): String? {
+    private fun checkKey(key: String): String {
         check(key in keySet) { "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 12395f5..c70a1b0 100644
--- a/development/build_log_simplifier/message-flakes.ignore
+++ b/development/build_log_simplifier/message-flakes.ignore
@@ -28,3 +28,8 @@
 # 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\)
+# > Task :webkit:integration-tests:testapp:compileReleaseJavaWithJavac
+\[ant\:jacocoReport\] Note\: Some input files use or override a deprecated API\.
+\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:deprecation for details\.
+\[ant\:jacocoReport\] Note\: Some input files use unchecked or unsafe operations\.
+\[ant\:jacocoReport\] Note\: Recompile with \-Xlint\:unchecked for details\.
\ No newline at end of file
diff --git a/development/split_change_into_owners.sh b/development/split_change_into_owners.sh
new file mode 100755
index 0000000..9405140
--- /dev/null
+++ b/development/split_change_into_owners.sh
@@ -0,0 +1,31 @@
+set -e
+
+if [ ! -e .git ]; then
+  echo "This script must be run from the root of the git repository"
+  exit 1
+fi
+
+function usage() {
+  echo "Usage: split_change_into_owners.sh <commit message>"
+  echo
+  echo "Splits changes in the current repository based on OWNERS files"
+  exit 1
+}
+
+commitMessage="$1"
+if [ "$commitMessage" == "" ]; then
+  usage
+fi
+
+ownersFiles="$(find -name OWNERS)"
+ownedDirs="$(echo "$ownersFiles" | sed 's|/OWNERS||' | sort -r)"
+
+for d in $ownedDirs; do
+  git add "$d"
+  if git status | grep -i "changes to be committed" >/dev/null; then
+    echo making commit for "$d"
+    git commit -m "$commitMessage
+
+This change includes files under $d"
+  fi
+done
diff --git a/docs-tip-of-tree/build.gradle b/docs-tip-of-tree/build.gradle
index 7075406..265b8c8 100644
--- a/docs-tip-of-tree/build.gradle
+++ b/docs-tip-of-tree/build.gradle
@@ -91,6 +91,7 @@
     docs(project(":core:core-animation"))
     docs(project(":core:core-animation-testing"))
     docs(project(":core:core-appdigest"))
+    docs(project(":core:core-google-shortcuts"))
     docs(project(":core:core-ktx"))
     docs(project(":cursoradapter:cursoradapter"))
     docs(project(":customview:customview"))
diff --git a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
index 9b18a2d..c39e98f9 100644
--- a/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
+++ b/fragment/fragment/src/androidTest/java/androidx/fragment/app/ViewModelTest.kt
@@ -210,6 +210,35 @@
             assertThat(vm.cleared).isTrue()
         }
     }
+
+    @Test
+    fun testDefaultFactoryAfterReuse() {
+        with(ActivityScenario.launch(ViewModelActivity::class.java)) {
+            val fragment = withActivity {
+                Fragment().also {
+                    supportFragmentManager.beginTransaction().add(it, "temp").commitNow()
+                }
+            }
+
+            val defaultFactory = fragment.defaultViewModelProviderFactory
+
+            onActivity { activity ->
+                activity.supportFragmentManager.beginTransaction().remove(fragment).commitNow()
+            }
+
+            // Now re-add the removed fragment
+            onActivity { activity ->
+                activity.supportFragmentManager.beginTransaction()
+                    .add(fragment, "temp")
+                    .commitNow()
+            }
+
+            val newDefaultFactory = fragment.defaultViewModelProviderFactory
+
+            // New Fragment should have a new default factory
+            assertThat(newDefaultFactory).isNotSameInstanceAs(defaultFactory)
+        }
+    }
 }
 
 private fun FragmentActivity.getFragment(tag: String) =
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
index cb57854..3245b4c 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/Fragment.java
@@ -567,6 +567,9 @@
     private void initLifecycle() {
         mLifecycleRegistry = new LifecycleRegistry(this);
         mSavedStateRegistryController = SavedStateRegistryController.create(this);
+        // The default factory depends on the SavedStateRegistry so it
+        // needs to be reset when the SavedStateRegistry is reset
+        mDefaultFactory = null;
     }
 
     /**
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
index 0afbeaf..318929d 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentLayoutInflaterFactory.java
@@ -102,8 +102,7 @@
             fragment.mHost = mFragmentManager.getHost();
             fragment.onInflate(mFragmentManager.getHost().getContext(), attrs,
                     fragment.mSavedFragmentState);
-            fragmentStateManager = mFragmentManager.createOrGetFragmentStateManager(fragment);
-            mFragmentManager.addFragment(fragment);
+            fragmentStateManager = mFragmentManager.addFragment(fragment);
             if (FragmentManager.isLoggingEnabled(Log.VERBOSE)) {
                 Log.v(FragmentManager.TAG, "Fragment " + fragment + " has been inflated via "
                         + "the <fragment> tag: id=0x" + Integer.toHexString(id));
@@ -155,23 +154,20 @@
         // Fragments added via the <fragment> tag cannot move above VIEW_CREATED
         // during inflation. Instead, we'll wait for the view to be attached to
         // window and its parent view and trigger moveToExpectedState() at that point.
-        if (mFragmentManager.mCurState > fragment.mState) {
-            fragment.mView.addOnAttachStateChangeListener(
-                    new View.OnAttachStateChangeListener() {
-                        @Override
-                        public void onViewAttachedToWindow(View v) {
-                            fragmentStateManager.moveToExpectedState();
-                            SpecialEffectsController controller = SpecialEffectsController
-                                    .getOrCreateController((ViewGroup) parent, mFragmentManager);
-                            controller.forceCompleteAllOperations();
-                        }
-
-                        @Override
-                        public void onViewDetachedFromWindow(View v) {
-                        }
+        fragment.mView.addOnAttachStateChangeListener(
+                new View.OnAttachStateChangeListener() {
+                    @Override
+                    public void onViewAttachedToWindow(View v) {
+                        fragmentStateManager.moveToExpectedState();
+                        SpecialEffectsController controller = SpecialEffectsController
+                                .getOrCreateController((ViewGroup) parent, mFragmentManager);
+                        controller.forceCompleteAllOperations();
                     }
-            );
-        }
+
+                    @Override
+                    public void onViewDetachedFromWindow(View v) { }
+                }
+        );
         return fragment.mView;
     }
 }
diff --git a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
index 8dfc448..ee8da34 100644
--- a/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
+++ b/fragment/fragment/src/main/java/androidx/fragment/app/FragmentManager.java
@@ -1710,7 +1710,7 @@
         return fragmentStateManager;
     }
 
-    void addFragment(@NonNull Fragment fragment) {
+    FragmentStateManager addFragment(@NonNull Fragment fragment) {
         if (isLoggingEnabled(Log.VERBOSE)) Log.v(TAG, "add: " + fragment);
         FragmentStateManager fragmentStateManager = createOrGetFragmentStateManager(fragment);
         fragment.mFragmentManager = this;
@@ -1725,6 +1725,7 @@
                 mNeedMenuInvalidate = true;
             }
         }
+        return fragmentStateManager;
     }
 
     void removeFragment(@NonNull Fragment fragment) {
diff --git a/lifecycle/lifecycle-runtime-ktx/api/current.txt b/lifecycle/lifecycle-runtime-ktx/api/current.txt
index 9df7a7b..cd4e431 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/current.txt
@@ -29,6 +29,11 @@
     method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T> p);
   }
 
+  public final class RepeatOnLifecycleKt {
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+  }
+
   public final class ViewKt {
     method public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View);
   }
diff --git a/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt b/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
index 9df7a7b..cd4e431 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/public_plus_experimental_current.txt
@@ -29,6 +29,11 @@
     method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T> p);
   }
 
+  public final class RepeatOnLifecycleKt {
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+  }
+
   public final class ViewKt {
     method public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View);
   }
diff --git a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
index 6205755..07c02b7 100644
--- a/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
+++ b/lifecycle/lifecycle-runtime-ktx/api/restricted_current.txt
@@ -29,6 +29,11 @@
     method public static suspend <T> Object? whenStateAtLeast(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State minState, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super T>,?> block, kotlin.coroutines.Continuation<? super T> p);
   }
 
+  public final class RepeatOnLifecycleKt {
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.LifecycleOwner, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+    method public static kotlinx.coroutines.Job repeatOnLifecycle(androidx.lifecycle.Lifecycle, androidx.lifecycle.Lifecycle.State state, optional kotlin.coroutines.CoroutineContext coroutineContext, kotlin.jvm.functions.Function2<? super kotlinx.coroutines.CoroutineScope,? super kotlin.coroutines.Continuation<? super kotlin.Unit>,?> block);
+  }
+
   public final class ViewKt {
     method public static androidx.lifecycle.LifecycleOwner? findViewTreeLifecycleOwner(android.view.View);
   }
diff --git a/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt
new file mode 100644
index 0000000..2a8ae8e
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/src/androidTest/java/androidx/lifecycle/RepeatOnLifecycleTest.kt
@@ -0,0 +1,279 @@
+/*
+ * 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.lifecycle
+
+import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
+import kotlinx.coroutines.CancellationException
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.awaitCancellation
+import kotlinx.coroutines.runBlocking
+import kotlinx.coroutines.test.TestCoroutineDispatcher
+import kotlinx.coroutines.test.runBlockingTest
+import kotlinx.coroutines.withContext
+import kotlinx.coroutines.yield
+import org.junit.Test
+
+@SmallTest
+class RepeatOnLifecycleTest {
+
+    private val expectations = Expectations()
+    private val owner = FakeLifecycleOwner()
+
+    @Test
+    fun testBlockRunsWhenCreatedStateIsReached() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.CREATED) {
+            expectations.expect(2)
+        }
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlockRunsWhenStartedStateIsReached() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            expectations.expect(2)
+        }
+        owner.setState(Lifecycle.State.STARTED)
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlockRunsWhenResumedStateIsReached() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+            expectations.expect(3)
+        }
+        owner.setState(Lifecycle.State.STARTED)
+        expectations.expect(2)
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(4)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlocksRepeatsExecution() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        var restarted = false
+        expectations.expect(1)
+
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+            if (!restarted) {
+                expectations.expect(2)
+            } else {
+                expectations.expect(5)
+            }
+        }
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.STARTED)
+        expectations.expect(4)
+
+        restarted = true
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(6)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlockIsCancelledWhenLifecycleIsDestroyed() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            try {
+                expectations.expect(2)
+                awaitCancellation()
+            } catch (e: CancellationException) {
+                expectations.expect(4)
+            }
+        }
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        yield() // let the cancellation code run before asserting it happened
+        expectations.expect(5)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlockRunsOnSubsequentLifecycleState() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            expectations.expect(2)
+        }
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testBlockDoesNotStartIfLifecycleIsDestroyed() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.DESTROYED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            expectations.expectUnreached()
+        }
+        expectations.expect(2)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testCancellingTheReturnedJobCancelsTheBlock() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(1)
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+            try {
+                expectations.expect(2)
+                awaitCancellation()
+            } catch (e: CancellationException) {
+                expectations.expect(4)
+            }
+        }
+        expectations.expect(3)
+        repeatingWorkJob.cancel()
+        yield() // let the cancellation code run before asserting it happened
+        expectations.expect(5)
+        assertThat(repeatingWorkJob.isCancelled).isTrue()
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testCancellingACustomJobCancelsTheBlock() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(1)
+        val customJob = Job()
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.STARTED, customJob) {
+            try {
+                expectations.expect(2)
+                awaitCancellation()
+            } catch (e: CancellationException) {
+                expectations.expect(4)
+            }
+        }
+        expectations.expect(3)
+        customJob.cancel()
+        yield() // let the cancellation code run before asserting it happened
+        expectations.expect(5)
+        assertThat(customJob.isCancelled).isTrue()
+        assertThat(customJob.isCompleted).isTrue()
+        assertThat(repeatingWorkJob.isCancelled).isTrue()
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+    }
+
+    @Test
+    fun testCancellingTheJobDoesNotRestartTheBlockOnNewStates() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.RESUMED)
+        expectations.expect(1)
+
+        var restarted = false
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
+            if (!restarted) {
+                expectations.expect(2)
+            } else {
+                expectations.expectUnreached()
+            }
+        }
+        expectations.expect(3)
+        repeatingWorkJob.cancel()
+        assertThat(repeatingWorkJob.isCancelled).isTrue()
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+
+        owner.setState(Lifecycle.State.STARTED)
+        restarted = true
+        expectations.expect(4)
+        owner.setState(Lifecycle.State.RESUMED)
+        yield() // Block shouldn't restart
+        expectations.expect(5)
+        owner.setState(Lifecycle.State.DESTROYED)
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class, ExperimentalStdlibApi::class)
+    @Test
+    fun testBlockRunsWhenUsingCustomDispatchers() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        expectations.expect(1)
+
+        val testDispatcher = TestCoroutineDispatcher()
+        val repeatingWorkJob = owner.repeatOnLifecycle(Lifecycle.State.CREATED, testDispatcher) {
+            // testDispatcher is ignored. This still runs on Dispatchers.Main
+            assertThat(coroutineContext[CoroutineDispatcher]).isEqualTo(Dispatchers.Main)
+            expectations.expect(2)
+        }
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        assertThat(repeatingWorkJob.isCompleted).isTrue()
+        testDispatcher.cleanupTestCoroutines()
+    }
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    @Test
+    fun testBlockRunsWhenLogicUsesWithContext() = runBlocking(Dispatchers.Main) {
+        owner.setState(Lifecycle.State.CREATED)
+        expectations.expect(1)
+
+        val testDispatcher = TestCoroutineDispatcher().apply {
+            runBlockingTest {
+                owner.repeatOnLifecycle(Lifecycle.State.CREATED) {
+                    withContext(this@apply) {
+                        expectations.expect(2)
+                    }
+                }
+            }
+        }
+
+        expectations.expect(3)
+        owner.setState(Lifecycle.State.DESTROYED)
+        testDispatcher.cleanupTestCoroutines()
+    }
+
+    @Test
+    fun testAddRepeatingWorkFailsWithInitializedState() = runBlocking(Dispatchers.Main) {
+        try {
+            owner.repeatOnLifecycle(Lifecycle.State.INITIALIZED) {
+                // IllegalArgumentException expected
+            }
+        } catch (e: Throwable) {
+            assertThat(e is IllegalArgumentException).isTrue()
+        }
+        Unit // runBlocking tries to return the result of the try expression, using Unit instead
+    }
+
+    @Test
+    fun testAddRepeatingWorkFailsWithDestroyedState() = runBlocking(Dispatchers.Main) {
+        try {
+            owner.repeatOnLifecycle(Lifecycle.State.DESTROYED) {
+                // IllegalArgumentException expected
+            }
+        } catch (e: Throwable) {
+            assertThat(e is IllegalArgumentException).isTrue()
+        }
+        Unit // runBlocking tries to return the result of the try expression, using Unit instead
+    }
+}
diff --git a/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
new file mode 100644
index 0000000..92e1234
--- /dev/null
+++ b/lifecycle/lifecycle-runtime-ktx/src/main/java/androidx/lifecycle/RepeatOnLifecycle.kt
@@ -0,0 +1,200 @@
+/*
+ * 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.lifecycle
+
+import kotlinx.coroutines.CoroutineDispatcher
+import kotlinx.coroutines.CoroutineScope
+import kotlinx.coroutines.CoroutineStart
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.ExperimentalCoroutinesApi
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.Job
+import kotlinx.coroutines.launch
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.EmptyCoroutineContext
+
+/**
+ * Launches and runs the given [block] in a coroutine that executes the [block] on the
+ * main thread when this [LifecycleOwner]'s [Lifecycle] is at least at [state].
+ * The launched coroutine will be cancelled when the lifecycle state falls below [state].
+ *
+ * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state.
+ * To permanently remove the work from the lifecycle, [Job.cancel] the returned [Job].
+ *
+ * [Lifecycle] is bound to the Android **main thread** and the lifecycle state is only
+ * guaranteed to be valid and consistent on that main thread. [block] always runs on
+ * [Dispatchers.Main] and launches when the lifecycle [state] is first reached.
+ * **This overrides any [CoroutineDispatcher] specified in [coroutineContext].**
+ *
+ * An active coroutine is a child [Job] of any job present in [coroutineContext]. This returned
+ * [Job] is the parent of any currently running [block] and will **complete** when the [Lifecycle]
+ * is [destroyed][Lifecycle.Event.ON_DESTROY]. To perform an action when a [RepeatingWorkObserver]
+ * is completed or cancelled, see [Job.join] or [Job.invokeOnCompletion].
+ *
+ * ```
+ * // Runs the block of code in a coroutine when the lifecycleOwner is at least STARTED.
+ * // The coroutine will be cancelled when the ON_STOP event happens and will restart executing
+ * // if the lifecycleOwner's lifecycle receives the ON_START event again.
+ * lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ *     uiStateFlow.collect { uiState ->
+ *         updateUi(uiState)
+ *     }
+ * }
+ * ```
+ *
+ * The best practice is to call this function when the lifecycleOwner is initialized. For
+ * example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise, multiple
+ * repeating jobs doing the same could be registered and be executed at the same time.
+ *
+ * Warning: [Lifecycle.State.DESTROYED] and [Lifecycle.State.INITIALIZED] are not allowed in this
+ * API. Passing those as a parameter will throw an [IllegalArgumentException].
+ *
+ * @param state [Lifecycle.State] in which the coroutine starts. That coroutine will cancel
+ * if the lifecycle falls below that state, and will restart if it's in that state again.
+ * @param coroutineContext [CoroutineContext] used to execute [block]. Note that its
+ * [CoroutineDispatcher] will be replaced by [Dispatchers.Main].
+ * @param block The block to run when the lifecycle is at least in [state] state.
+ * @return [Job] to manage the repeating work.
+ */
+public fun LifecycleOwner.repeatOnLifecycle(
+    state: Lifecycle.State,
+    coroutineContext: CoroutineContext = EmptyCoroutineContext,
+    block: suspend CoroutineScope.() -> Unit
+): Job = lifecycle.repeatOnLifecycle(state, coroutineContext, block)
+
+/**
+ * Launches and runs the given [block] in a coroutine that executes the [block] on the
+ * main thread when this [Lifecycle] is at least at [state].
+ * The launched coroutine will be cancelled when the lifecycle state falls below [state].
+ *
+ * The [block] will cancel and re-launch as the lifecycle moves in and out of the target state.
+ * To permanently remove the work from the lifecycle, [Job.cancel] the returned [Job].
+ *
+ * [Lifecycle] is bound to the Android **main thread** and the lifecycle state is only
+ * guaranteed to be valid and consistent on that main thread. [block] always runs on
+ * [Dispatchers.Main] and launches when the lifecycle [state] is first reached.
+ * **This overrides any [CoroutineDispatcher] specified in [coroutineContext].**
+ *
+ * An active coroutine is a child [Job] of any job present in [coroutineContext]. This returned
+ * [Job] is the parent of any currently running [block] and will **complete** when the [Lifecycle]
+ * is [destroyed][Lifecycle.Event.ON_DESTROY]. To perform an action when a [RepeatingWorkObserver]
+ * is completed or cancelled, see [Job.join] or [Job.invokeOnCompletion].
+ *
+ * ```
+ * class MyActivity : AppCompatActivity() {
+ *     override fun onCreate(savedInstanceState: Bundle?) {
+ *         /* ... */
+ *         // Runs the block of code in a coroutine when the lifecycle is at least STARTED.
+ *         // The coroutine will be cancelled when the ON_STOP event happens and will restart
+ *         // executing if the lifecycle receives the ON_START event again.
+ *         lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
+ *             uiStateFlow.collect { uiState ->
+ *                 updateUi(uiState)
+ *             }
+ *         }
+ *     }
+ * }
+ * ```
+ *
+ * The best practice is to call this function in the lifecycle callback when the View gets
+ * created. For example, `onCreate` in an Activity, or `onViewCreated` in a Fragment. Otherwise,
+ * multiple repeating jobs doing the same could be registered and be executed at the same time.
+ *
+ * Warning: [Lifecycle.State.DESTROYED] and [Lifecycle.State.INITIALIZED] are not allowed in this
+ * API. Passing those as a parameter will throw an [IllegalArgumentException].
+ *
+ * @param state [Lifecycle.State] in which the coroutine starts. That coroutine will cancel
+ * if the lifecycle falls below that state, and will restart if it's in that state again.
+ * @param coroutineContext [CoroutineContext] used to execute [block]. Note that its
+ * [CoroutineDispatcher] will be replaced by [Dispatchers.Main].
+ * @param block The block to run when the lifecycle is at least in [state].
+ * @return [Job] to manage the repeating work.
+ */
+public fun Lifecycle.repeatOnLifecycle(
+    state: Lifecycle.State,
+    coroutineContext: CoroutineContext = EmptyCoroutineContext,
+    block: suspend CoroutineScope.() -> Unit
+): Job {
+    if (currentState === Lifecycle.State.DESTROYED) {
+        // Fast-path! As the work would immediately complete, return a completed Job
+        // to avoid extra allocations and adding/removing observers
+        return Job().apply { complete() }
+    }
+
+    return RepeatingWorkObserver(this, state, coroutineContext + Dispatchers.Main, block)
+        .also { observer ->
+            // Immediately add the LifecycleEventObserver to ensure that RepeatingWorkObserver Job's
+            // `invokeOnCompletion` that removes the observer doesn't happen before this in the case
+            // of a parent job cancelled early or an ON_DESTROY completing the observer
+            observer.launch(Dispatchers.Main.immediate) {
+                addObserver(observer)
+            }
+        }.job
+}
+
+/**
+ * [LifecycleEventObserver] that executes work periodically when the Lifecycle reaches
+ * certain State.
+ */
+private class RepeatingWorkObserver(
+    private val lifecycle: Lifecycle,
+    state: Lifecycle.State,
+    coroutineContext: CoroutineContext = EmptyCoroutineContext,
+    private val block: suspend CoroutineScope.() -> Unit,
+) : LifecycleEventObserver, CoroutineScope {
+
+    init {
+        if (state === Lifecycle.State.INITIALIZED || state === Lifecycle.State.DESTROYED) {
+            throw IllegalArgumentException(
+                "RepeatingWorkObserver cannot start work with lifecycle states that are " +
+                    "at least INITIALIZED or DESTROYED."
+            )
+        }
+    }
+
+    private val startWorkEvent = Lifecycle.Event.upTo(state)
+    private val cancelWorkEvent = Lifecycle.Event.downFrom(state)
+
+    // Exposing this job to enable cancelling RepeatingWorkObserver from the outside
+    val job = Job(coroutineContext[Job]).apply { invokeOnCompletion { removeSelf() } }
+    override val coroutineContext = coroutineContext + job
+
+    private var launchedJob: Job? = null
+
+    @OptIn(ExperimentalCoroutinesApi::class)
+    override fun onStateChanged(source: LifecycleOwner, event: Lifecycle.Event) {
+        if (event == startWorkEvent) {
+            launchedJob = launch(start = CoroutineStart.UNDISPATCHED, block = block)
+            return
+        }
+        if (event == cancelWorkEvent) {
+            launchedJob?.cancel()
+            launchedJob = null
+        }
+        if (event == Lifecycle.Event.ON_DESTROY) {
+            job.complete()
+        }
+    }
+
+    private fun removeSelf() {
+        if (Dispatchers.Main.immediate.isDispatchNeeded(EmptyCoroutineContext)) {
+            GlobalScope.launch(Dispatchers.Main.immediate) {
+                lifecycle.removeObserver(this@RepeatingWorkObserver)
+            }
+        } else lifecycle.removeObserver(this@RepeatingWorkObserver)
+    }
+}
diff --git a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
index dcc9d01..4e1e00a 100644
--- a/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
+++ b/navigation/navigation-safe-args-gradle-plugin/src/test/kotlin/androidx/navigation/safeargs/gradle/BasePluginTest.kt
@@ -72,7 +72,9 @@
         File(projectRoot(), "$NAV_RESOURCES/$name")
 
     internal fun gradleBuilder(vararg args: String) = GradleRunner.create()
-        .withProjectDir(projectRoot()).withPluginClasspath().withArguments(*args)
+        .withProjectDir(projectRoot()).withPluginClasspath()
+        // b/175897186 set explicit metaspace size in hopes of fewer crashes
+        .withArguments("-Dorg.gradle.jvmargs=-XX:MaxMetaspaceSize=512m", *args)
 
     internal fun runGradle(vararg args: String) = gradleBuilder(*args).build()
     internal fun runAndFailGradle(vararg args: String) = gradleBuilder(*args).buildAndFail()
@@ -147,4 +149,4 @@
 internal fun BuildResult.assertFailingTask(name: String): BuildResult {
     assertThat(task(":$name")!!.outcome, CoreMatchers.`is`(TaskOutcome.FAILED))
     return this
-}
\ No newline at end of file
+}
diff --git a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
index 2804dcd..f8ee981 100644
--- a/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
+++ b/paging/common/src/main/kotlin/androidx/paging/PositionalDataSource.kt
@@ -87,7 +87,19 @@
          */
         @JvmField
         val placeholdersEnabled: Boolean
-    )
+    ) {
+        init {
+            check(requestedStartPosition >= 0) {
+                "invalid start position: $requestedStartPosition"
+            }
+            check(requestedLoadSize >= 0) {
+                "invalid load size: $requestedLoadSize"
+            }
+            check(pageSize >= 0) {
+                "invalid page size: $pageSize"
+            }
+        }
+    }
 
     /**
      * Holder object for inputs to [loadRange].
@@ -315,7 +327,7 @@
                     initialPosition = maxOf(0, idealStart / params.pageSize * params.pageSize)
                 } else {
                     // not tiled, so don't try to snap or force multiple of a page size
-                    initialPosition -= initialLoadSize / 2
+                    initialPosition = maxOf(0, initialPosition - initialLoadSize / 2)
                 }
             }
             val initParams = LoadInitialParams(
diff --git a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
index e6c02d2..7fd6143 100644
--- a/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
+++ b/paging/runtime/src/androidTest/java/androidx/paging/LivePagedListTest.kt
@@ -16,9 +16,14 @@
 
 package androidx.paging
 
+import android.view.View
+import android.view.ViewGroup
 import androidx.arch.core.executor.testing.InstantTaskExecutorRule
+import androidx.recyclerview.widget.DiffUtil
+import androidx.recyclerview.widget.RecyclerView
 import androidx.test.ext.junit.runners.AndroidJUnit4
 import androidx.test.filters.SmallTest
+import com.google.common.truth.Truth.assertThat
 import kotlinx.coroutines.Dispatchers
 import org.junit.Assert.assertEquals
 import org.junit.Assert.assertNotNull
@@ -28,6 +33,7 @@
 
 @RunWith(AndroidJUnit4::class)
 @SmallTest
+@Suppress("DEPRECATION")
 class LivePagedListTest {
     @JvmField
     @Rule
@@ -35,7 +41,6 @@
 
     @Test
     fun toLiveData_dataSourceConfig() {
-        @Suppress("DEPRECATION")
         val livePagedList = dataSourceFactory.toLiveData(config)
         livePagedList.observeForever {}
         assertNotNull(livePagedList.value)
@@ -44,7 +49,6 @@
 
     @Test
     fun toLiveData_dataSourcePageSize() {
-        @Suppress("DEPRECATION")
         val livePagedList = dataSourceFactory.toLiveData(24)
         livePagedList.observeForever {}
         assertNotNull(livePagedList.value)
@@ -53,7 +57,6 @@
 
     @Test
     fun toLiveData_pagingSourceConfig() {
-        @Suppress("DEPRECATION")
         val livePagedList = pagingSourceFactory.toLiveData(config)
         livePagedList.observeForever {}
         assertNotNull(livePagedList.value)
@@ -62,13 +65,71 @@
 
     @Test
     fun toLiveData_pagingSourcePageSize() {
-        @Suppress("DEPRECATION")
         val livePagedList = pagingSourceFactory.toLiveData(24)
         livePagedList.observeForever {}
         assertNotNull(livePagedList.value)
         assertEquals(24, livePagedList.value!!.config.pageSize)
     }
 
+    /**
+     * Some paging2 tests might be using InstantTaskExecutor and expect first page to be loaded
+     * immediately. This test replicates that by checking observe forever receives the value in
+     * its own call stack.
+     */
+    @Test
+    fun instantExecutionWorksWithLegacy() {
+        val totalSize = 300
+        val data = (0 until totalSize).map { "$it/$it" }
+        val factory = object : DataSource.Factory<Int, String>() {
+            override fun create(): DataSource<Int, String> {
+                return TestPositionalDataSource(data)
+            }
+        }
+
+        class TestAdapter : PagedListAdapter<String, RecyclerView.ViewHolder>(
+            DIFF_STRING
+        ) {
+            // open it up by overriding
+            public override fun getItem(position: Int): String? {
+                return super.getItem(position)
+            }
+
+            override fun onCreateViewHolder(
+                parent: ViewGroup,
+                viewType: Int
+            ): RecyclerView.ViewHolder {
+                return object : RecyclerView.ViewHolder(View(parent.context)) {}
+            }
+
+            override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
+            }
+        }
+
+        val livePagedList = LivePagedListBuilder(
+            factory,
+            PagedList.Config.Builder()
+                .setEnablePlaceholders(false)
+                .setPageSize(30)
+                .build()
+        ).build()
+
+        val adapter = TestAdapter()
+        livePagedList.observeForever { pagedList ->
+            // make sure observeForever worked sync where it did load the data immediately
+            assertThat(
+                Throwable().stackTraceToString()
+            ).contains("observeForever")
+            assertThat(pagedList.loadedCount).isEqualTo(90)
+        }
+        adapter.submitList(checkNotNull(livePagedList.value))
+        assertThat(adapter.itemCount).isEqualTo(90)
+
+        (0 until totalSize).forEach {
+            // getting that item will trigger load around which should load the item immediately
+            assertThat(adapter.getItem(it)).isEqualTo("$it/$it")
+        }
+    }
+
     companion object {
         @Suppress("DEPRECATION")
         private val dataSource = object : PositionalDataSource<String>() {
@@ -90,5 +151,14 @@
         )
 
         private val config = Config(10)
+        private val DIFF_STRING = object : DiffUtil.ItemCallback<String>() {
+            override fun areItemsTheSame(oldItem: String, newItem: String): Boolean {
+                return oldItem == newItem
+            }
+
+            override fun areContentsTheSame(oldItem: String, newItem: String): Boolean {
+                return oldItem == newItem
+            }
+        }
     }
 }
diff --git a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
index 228fa79..7938e7c 100644
--- a/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
+++ b/recyclerview/recyclerview/src/androidTest/java/androidx/recyclerview/widget/RecyclerViewLayoutTest.java
@@ -2710,7 +2710,19 @@
         TestLayoutManager layoutManager = new SimpleTestLayoutManager();
 
         RecyclerView recyclerView = new RecyclerView(getActivity());
-        recyclerView.setAdapter(new TestAdapter(1000));
+        // Setting height of RV to a fixed value so that it doesn't change based on the device
+        //  that the test is running on.
+        recyclerView.setLayoutParams(
+                new ViewGroup.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, 500)
+        );
+        recyclerView.setAdapter(
+                new TestAdapter(
+                        10,
+                        new RecyclerView.LayoutParams(
+                                ViewGroup.LayoutParams.MATCH_PARENT,
+                                100)
+                )
+        );
         recyclerView.setLayoutManager(layoutManager);
         layoutManager.expectLayouts(1);
         setRecyclerView(recyclerView);
diff --git a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
index 6244b7f..0e664f5 100644
--- a/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
+++ b/recyclerview/recyclerview/src/main/java/androidx/recyclerview/widget/RecyclerView.java
@@ -4630,7 +4630,7 @@
             final int width = getWidth();
             final int padding = mClipToPadding ? getPaddingTop() : 0;
             c.rotate(90);
-            c.translate(-padding, -width);
+            c.translate(padding, -width);
             needsInvalidate |= mRightGlow != null && mRightGlow.draw(c);
             c.restoreToCount(restore);
         }
@@ -5618,7 +5618,7 @@
 
             // Handle cases where parameter values aren't defined.
             if (duration == UNDEFINED_DURATION) {
-                duration = computeScrollDuration(dx, dy, 0, 0);
+                duration = computeScrollDuration(dx, dy);
             }
             if (interpolator == null) {
                 interpolator = sQuinticInterpolator;
@@ -5648,31 +5648,21 @@
             postOnAnimation();
         }
 
-        private float distanceInfluenceForSnapDuration(float f) {
-            f -= 0.5f; // center the values about 0.
-            f *= 0.3f * (float) Math.PI / 2.0f;
-            return (float) Math.sin(f);
-        }
-
-        private int computeScrollDuration(int dx, int dy, int vx, int vy) {
+        /**
+         * Computes of an animated scroll in milliseconds.
+         * @param dx           x distance in pixels.
+         * @param dy           y distance in pixels.
+         * @return The duration of the animated scroll in milliseconds.
+         */
+        private int computeScrollDuration(int dx, int dy) {
             final int absDx = Math.abs(dx);
             final int absDy = Math.abs(dy);
             final boolean horizontal = absDx > absDy;
-            final int velocity = (int) Math.sqrt(vx * vx + vy * vy);
-            final int delta = (int) Math.sqrt(dx * dx + dy * dy);
             final int containerSize = horizontal ? getWidth() : getHeight();
-            final int halfContainerSize = containerSize / 2;
-            final float distanceRatio = Math.min(1.f, 1.f * delta / containerSize);
-            final float distance = halfContainerSize + halfContainerSize
-                    * distanceInfluenceForSnapDuration(distanceRatio);
 
-            final int duration;
-            if (velocity > 0) {
-                duration = 4 * Math.round(1000 * Math.abs(distance / velocity));
-            } else {
-                float absDelta = (float) (horizontal ? absDx : absDy);
-                duration = (int) (((absDelta / containerSize) + 1) * 300);
-            }
+            float absDelta = (float) (horizontal ? absDx : absDy);
+            final int duration = (int) (((absDelta / containerSize) + 1) * 300);
+
             return Math.min(duration, MAX_SCROLL_DURATION);
         }
 
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
index df23b64..82f5446 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/KotlinCompilationUtil.kt
@@ -19,6 +19,7 @@
 import com.tschuchort.compiletesting.KotlinCompilation
 import java.io.File
 import java.io.OutputStream
+import java.net.URLClassLoader
 
 /**
  * Helper class for Kotlin Compile Testing library to have common setup for room.
@@ -43,10 +44,100 @@
         compilation.kotlincArguments += "-Xjava-source-roots=${javaSrcRoot.absolutePath}"
         compilation.jvmDefault = "enable"
         compilation.jvmTarget = "1.8"
-        compilation.inheritClassPath = true
+        compilation.inheritClassPath = false
         compilation.verbose = false
-        compilation.classpaths += classpaths
+        compilation.classpaths = Classpaths.inheritedClasspath + classpaths
         compilation.messageOutputStream = outputStream
+        compilation.kotlinStdLibJar = Classpaths.kotlinStdLibJar
+        compilation.kotlinStdLibCommonJar = Classpaths.kotlinStdLibCommonJar
+        compilation.kotlinStdLibJdkJar = Classpaths.kotlinStdLibJdkJar
+        compilation.kotlinReflectJar = Classpaths.kotlinReflectJar
+        compilation.kotlinScriptRuntimeJar = Classpaths.kotlinScriptRuntimeJar
         return compilation
     }
-}
\ No newline at end of file
+
+    /**
+     * Helper object to persist common classpaths resolved by KCT to make sure it does not
+     * re-resolve host classpath repeatedly and also runs compilation with a smaller classpath.
+     * see: https://github.com/tschuchortdev/kotlin-compile-testing/issues/113
+     */
+    private object Classpaths {
+
+        val inheritedClasspath: List<File>
+
+        /**
+         * These jars are files that Kotlin Compile Testing discovers from classpath. It uses a
+         * rather expensive way of discovering these so we cache them here for now.
+         *
+         * We can remove this cache once we update to a version that includes the fix in KCT:
+         * https://github.com/tschuchortdev/kotlin-compile-testing/pull/114
+         */
+        val kotlinStdLibJar: File?
+        val kotlinStdLibCommonJar: File?
+        val kotlinStdLibJdkJar: File?
+        val kotlinReflectJar: File?
+        val kotlinScriptRuntimeJar: File?
+
+        init {
+            // create a KotlinCompilation to resolve common jars
+            val compilation = KotlinCompilation()
+            kotlinStdLibJar = compilation.kotlinStdLibJar
+            kotlinStdLibCommonJar = compilation.kotlinStdLibCommonJar
+            kotlinStdLibJdkJar = compilation.kotlinStdLibJdkJar
+            kotlinReflectJar = compilation.kotlinReflectJar
+            kotlinScriptRuntimeJar = compilation.kotlinScriptRuntimeJar
+
+            val hostClasspaths = getClasspathFromClassloader(
+                KotlinCompilationUtil::class.java.classLoader
+            )
+            inheritedClasspath = hostClasspaths.filter {
+                // size of this classpath has a rather significant impact on kotlin compilation
+                // tests hence limit it to things we need and no more.
+                it.path.contains("room") ||
+                    it.path.contains("androidx") ||
+                    it.path.contains("junit") ||
+                    it.path.contains("android.jar")
+            }
+        }
+    }
+
+    // ported from https://github.com/google/compile-testing/blob/master/src/main/java/com
+    // /google/testing/compile/Compiler.java#L231
+    private fun getClasspathFromClassloader(referenceClassLoader: ClassLoader): List<File> {
+        val platformClassLoader: ClassLoader = ClassLoader.getPlatformClassLoader()
+        var currentClassloader = referenceClassLoader
+        val systemClassLoader = ClassLoader.getSystemClassLoader()
+
+        // Concatenate search paths from all classloaders in the hierarchy
+        // 'till the system classloader.
+        val classpaths: MutableSet<String> = LinkedHashSet()
+        while (true) {
+            if (currentClassloader === systemClassLoader) {
+                classpaths.addAll(
+                    System.getProperty("java.class.path")
+                        .split(System.getProperty("path.separator"))
+                )
+                break
+            }
+            if (currentClassloader === platformClassLoader) {
+                break
+            }
+            check(currentClassloader is URLClassLoader) {
+                """Classpath for compilation could not be extracted
+                since $currentClassloader is not an instance of URLClassloader
+                """.trimIndent()
+            }
+            // We only know how to extract classpaths from URLClassloaders.
+            currentClassloader.urLs.forEach { url ->
+                check(url.protocol == "file") {
+                    """Given classloader consists of classpaths which are unsupported for
+                    compilation.
+                    """.trimIndent()
+                }
+                classpaths.add(url.path)
+            }
+            currentClassloader = currentClassloader.parent
+        }
+        return classpaths.map { File(it) }.filter { it.exists() }
+    }
+}
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
index cf24bd3..cb942f4 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/ProcessorTestExt.kt
@@ -85,8 +85,8 @@
 }
 
 /**
- * Runs the compilation test with all 3 backends (javac, kapt, ksp) if possible (e.g. javac
- * cannot test kotlin sources).
+ * Runs the compilation test with ksp and one of javac or kapt, depending on whether input has
+ * kotlin sources.
  *
  * The [handler] will be invoked only for the first round. If you need to test multi round
  * processing, use `handlers = listOf(..., ...)`.
@@ -113,14 +113,18 @@
     classpath: List<File> = emptyList(),
     handlers: List<(XTestInvocation) -> Unit>
 ) {
+    val javaApRunner = if (sources.any { it is Source.KotlinSource }) {
+        KaptCompilationTestRunner
+    } else {
+        JavacCompilationTestRunner
+    }
     runTests(
         params = TestCompilationParameters(
             sources = sources,
             classpath = classpath,
             handlers = handlers
         ),
-        JavacCompilationTestRunner,
-        KaptCompilationTestRunner,
+        javaApRunner,
         KspCompilationTestRunner
     )
 }
diff --git a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
index 25faf77..a8b1502 100644
--- a/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
+++ b/room/compiler-processing-testing/src/main/java/androidx/room/compiler/processing/util/XTestInvocation.kt
@@ -52,8 +52,7 @@
 
     private val postCompilationAssertions = mutableListOf<CompilationResultSubject.() -> Unit>()
 
-    val isKsp: Boolean
-        get() = processingEnv.backend == XProcessingEnv.Backend.KSP
+    val isKsp: Boolean = processingEnv.backend == XProcessingEnv.Backend.KSP
 
     /**
      * Registers a block that will be called with a [CompilationResultSubject] when compilation
diff --git a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
index 4284a63..464de02 100644
--- a/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
+++ b/room/compiler-processing/src/main/java/androidx/room/compiler/processing/ksp/KspType.kt
@@ -52,6 +52,9 @@
     }
 
     override val typeElement by lazy {
+        // for primitive types, we could technically return null from here as they are not backed
+        // by a type element in javac but in Kotlin we have types for them, hence returning them
+        // is better.
         val declaration = ksType.declaration as? KSClassDeclaration
         declaration?.let {
             env.wrapClassDeclaration(it)
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
index 0132761..498357d 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/ProcessorErrors.kt
@@ -123,7 +123,7 @@
     val QUERY_PARAMETERS_CANNOT_START_WITH_UNDERSCORE = "Query/Insert method parameters cannot " +
         "start with underscore (_)."
 
-    fun cannotFindQueryResultAdapter(returnTypeName: String) = "Not sure how to convert a " +
+    fun cannotFindQueryResultAdapter(returnTypeName: TypeName) = "Not sure how to convert a " +
         "Cursor to this method's return type ($returnTypeName)."
 
     val INSERTION_DOES_NOT_HAVE_ANY_PARAMETERS_TO_INSERT = "Method annotated with" +
diff --git a/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
index 614badf..b495976 100644
--- a/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/processor/QueryMethodProcessor.kt
@@ -206,7 +206,7 @@
         context.checker.check(
             resultBinder.adapter != null,
             executableElement,
-            ProcessorErrors.cannotFindQueryResultAdapter(returnType.toString())
+            ProcessorErrors.cannotFindQueryResultAdapter(returnType.typeName)
         )
 
         val inTransaction = executableElement.hasAnnotation(Transaction::class)
diff --git a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
index 067a9d2..fdce370 100644
--- a/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/solver/query/result/PojoRowAdapter.kt
@@ -92,7 +92,7 @@
                 )
             }
             if (matchedFields.isEmpty()) {
-                context.logger.e(ProcessorErrors.cannotFindQueryResultAdapter(out.toString()))
+                context.logger.e(ProcessorErrors.cannotFindQueryResultAdapter(out.typeName))
             }
         } else {
             matchedFields = remainingFields.map { it }
diff --git a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
index 4c6b0c2..44ad888 100644
--- a/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
+++ b/room/compiler/src/main/kotlin/androidx/room/vo/RelationCollector.kt
@@ -324,7 +324,7 @@
                 if (rowAdapter == null) {
                     context.logger.e(
                         relation.field.element,
-                        cannotFindQueryResultAdapter(relation.pojoType.toString())
+                        cannotFindQueryResultAdapter(relation.pojoType.typeName)
                     )
                     null
                 } else {
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
index d8b0dda3..a0b0f76 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/CustomConverterProcessorTest.kt
@@ -17,16 +17,17 @@
 package androidx.room.processor
 
 import androidx.room.TypeConverter
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.typeName
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_EMPTY_CLASS
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_MUST_BE_PUBLIC
 import androidx.room.processor.ProcessorErrors.TYPE_CONVERTER_UNBOUND_GENERIC
-import androidx.room.testing.TestInvocation
+import androidx.room.testing.context
 import androidx.room.vo.CustomTypeConverter
 import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
 import com.squareup.javapoet.ClassName
 import com.squareup.javapoet.MethodSpec
 import com.squareup.javapoet.ParameterSpec
@@ -39,17 +40,15 @@
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import simpleRun
 import java.util.Date
 import javax.lang.model.element.Modifier
-import javax.tools.JavaFileObject
 
 @RunWith(JUnit4::class)
 class CustomConverterProcessorTest {
     companion object {
         val CONVERTER = ClassName.get("foo.bar", "MyConverter")!!
         val CONVERTER_QNAME = CONVERTER.packageName() + "." + CONVERTER.simpleName()
-        val CONTAINER = JavaFileObjects.forSourceString(
+        val CONTAINER = Source.java(
             "foo.bar.Container",
             """
                 package foo.bar;
@@ -65,7 +64,7 @@
         singleClass(createConverter(TypeName.SHORT.box(), TypeName.CHAR.box())) { converter, _ ->
             assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
             assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -73,7 +72,7 @@
         singleClass(createConverter(TypeName.SHORT, TypeName.CHAR.box())) { converter, _ ->
             assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
             assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -81,7 +80,7 @@
         singleClass(createConverter(TypeName.INT.box(), TypeName.DOUBLE)) { converter, _ ->
             assertThat(converter?.fromTypeName, `is`(TypeName.INT.box()))
             assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -89,7 +88,7 @@
         singleClass(createConverter(TypeName.INT, TypeName.DOUBLE)) { converter, _ ->
             assertThat(converter?.fromTypeName, `is`(TypeName.INT))
             assertThat(converter?.toTypeName, `is`(TypeName.DOUBLE))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -108,9 +107,11 @@
         val list = ParameterizedTypeName.get(List::class.typeName, typeVarT)
         val typeVarK = TypeVariableName.get("K")
         val map = ParameterizedTypeName.get(Map::class.typeName, typeVarK, typeVarT)
-        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) {
-            _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
+        singleClass(createConverter(list, map, listOf(typeVarK, typeVarT))) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(TYPE_CONVERTER_UNBOUND_GENERIC)
+            }
+        }
     }
 
     @Test
@@ -122,13 +123,13 @@
         singleClass(createConverter(list, map)) { converter, _ ->
             assertThat(converter?.fromTypeName, `is`(list as TypeName))
             assertThat(converter?.toTypeName, `is`(map as TypeName))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
     fun testNoConverters() {
         singleClass(
-            JavaFileObjects.forSourceString(
+            Source.java(
                 CONVERTER_QNAME,
                 """
                 package ${CONVERTER.packageName()};
@@ -136,14 +137,17 @@
                 }
                 """
             )
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+            }
+        }
     }
 
     @Test
     fun checkNoArgConstructor() {
         singleClass(
-            JavaFileObjects.forSourceString(
+            Source.java(
                 CONVERTER_QNAME,
                 """
                 package ${CONVERTER.packageName()};
@@ -156,14 +160,17 @@
                 }
                 """
             )
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(TYPE_CONVERTER_MISSING_NOARG_CONSTRUCTOR)
+            }
+        }
     }
 
     @Test
     fun checkNoArgConstructor_withStatic() {
         singleClass(
-            JavaFileObjects.forSourceString(
+            Source.java(
                 CONVERTER_QNAME,
                 """
                 package ${CONVERTER.packageName()};
@@ -180,13 +187,13 @@
             assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
             assertThat(converter?.toTypeName, `is`(TypeName.INT))
             assertThat(converter?.isStatic, `is`(true))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
     fun checkPublic() {
         singleClass(
-            JavaFileObjects.forSourceString(
+            Source.java(
                 CONVERTER_QNAME,
                 """
                 package ${CONVERTER.packageName()};
@@ -198,12 +205,15 @@
                 }
                 """
             )
-        ) { converter, _ ->
+        ) { converter, invocation ->
             assertThat(converter?.fromTypeName, `is`(TypeName.SHORT))
             assertThat(converter?.toTypeName, `is`(TypeName.INT))
             assertThat(converter?.isStatic, `is`(true))
-        }.failsToCompile().withErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC).and()
-            .withErrorCount(2)
+            invocation.assertCompilationResult {
+                hasErrorContaining(TYPE_CONVERTER_MUST_BE_PUBLIC)
+                hasErrorCount(2)
+            }
+        }
     }
 
     @Suppress("PLATFORM_CLASS_MAPPED_TO_KOTLIN")
@@ -216,7 +226,7 @@
 
         val baseConverter = createConverter(list, map, listOf(typeVarT, typeVarK))
         val extendingQName = "foo.bar.Extending"
-        val extendingClass = JavaFileObjects.forSourceString(
+        val extendingClass = Source.java(
             extendingQName,
             "package foo.bar;\n" +
                 TypeSpec.classBuilder(ClassName.bestGuess(extendingQName)).apply {
@@ -229,7 +239,9 @@
                 }.build().toString()
         )
 
-        simpleRun(baseConverter, extendingClass) { invocation ->
+        runProcessorTest(
+            sources = listOf(baseConverter, extendingClass)
+        ) { invocation ->
             val element = invocation.processingEnv.requireTypeElement(extendingQName)
             val converter = CustomConverterProcessor(invocation.context, element)
                 .process().firstOrNull()
@@ -250,22 +262,25 @@
                     ) as TypeName
                 )
             )
-        }.compilesWithoutError()
+        }
     }
 
     @Test
     fun checkDuplicates() {
         singleClass(
             createConverter(TypeName.SHORT.box(), TypeName.CHAR.box(), duplicate = true)
-        ) { converter, _ ->
+        ) { converter, invocation ->
             assertThat(converter?.fromTypeName, `is`(TypeName.SHORT.box()))
             assertThat(converter?.toTypeName, `is`(TypeName.CHAR.box()))
-        }.failsToCompile().withErrorContaining("Multiple methods define the same conversion")
+            invocation.assertCompilationResult {
+                hasErrorContaining("Multiple methods define the same conversion")
+            }
+        }
     }
 
     @Test
     fun invalidConverterType() {
-        val source = JavaFileObjects.forSourceString(
+        val source = Source.java(
             "foo.bar.Container",
             """
                 package foo.bar;
@@ -274,15 +289,22 @@
                 public class Container {}
                 """
         )
-        simpleRun(source) { invocation ->
+        runProcessorTest(listOf(source)) { invocation ->
             val result = CustomConverterProcessor.findConverters(
                 invocation.context,
                 invocation.processingEnv.requireTypeElement("foo.bar.Container")
             )
             Truth.assertThat(result.converters).isEmpty()
-        }.failsToCompile().withErrorContaining(
-            ProcessorErrors.typeConverterMustBeDeclared(TypeName.INT)
-        )
+            invocation.assertCompilationResult {
+                if (invocation.isKsp) {
+                    // for KSP it always has a type element but we rather assert the other error
+                    // instead of not running the code path in ksp tests
+                    hasErrorContaining(TYPE_CONVERTER_EMPTY_CLASS)
+                } else {
+                    hasErrorContaining(ProcessorErrors.typeConverterMustBeDeclared(TypeName.INT))
+                }
+            }
+        }
     }
 
     private fun createConverter(
@@ -290,7 +312,7 @@
         to: TypeName,
         typeVariables: List<TypeVariableName> = emptyList(),
         duplicate: Boolean = false
-    ): JavaFileObject {
+    ): Source {
         val code = TypeSpec.classBuilder(CONVERTER).apply {
             addTypeVariables(typeVariables)
             addModifiers(Modifier.PUBLIC)
@@ -310,17 +332,19 @@
                 addMethod(buildMethod("convertF2"))
             }
         }.build().toString()
-        return JavaFileObjects.forSourceString(
+        return Source.java(
             CONVERTER.toString(),
             "package ${CONVERTER.packageName()};\n$code"
         )
     }
 
     private fun singleClass(
-        vararg jfo: JavaFileObject,
-        handler: (CustomTypeConverter?, TestInvocation) -> Unit
-    ): CompileTester {
-        return simpleRun(*((jfo.toList() + CONTAINER).toTypedArray())) { invocation ->
+        vararg sources: Source,
+        handler: (CustomTypeConverter?, XTestInvocation) -> Unit
+    ) {
+        runProcessorTest(
+            sources = sources.toList() + CONTAINER
+        ) { invocation ->
             val processed = CustomConverterProcessor.findConverters(
                 invocation.context,
                 invocation.processingEnv.requireTypeElement("foo.bar.Container")
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
index d951b1a..51ddc28 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/DaoProcessorTest.kt
@@ -18,18 +18,18 @@
 
 import COMMON
 import androidx.room.compiler.processing.isTypeElement
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.compileFiles
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.ext.RoomTypeNames
-import androidx.room.testing.TestInvocation
-import androidx.room.testing.TestProcessor
+import androidx.room.testing.context
 import androidx.room.vo.Dao
 import androidx.room.vo.ReadQueryMethod
 import androidx.room.vo.Warning
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
-import compileLibrarySource
+import com.squareup.javapoet.TypeName
 import createVerifierFromEntitiesAndViews
+import getSystemClasspathFiles
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
@@ -38,7 +38,7 @@
 import java.io.File
 
 @RunWith(Parameterized::class)
-class DaoProcessorTest(val enableVerification: Boolean) {
+class DaoProcessorTest(private val enableVerification: Boolean) {
 
     companion object {
         const val DAO_PREFIX = """
@@ -64,15 +64,22 @@
                     }
                 }
                 """
-        ) { _, _ ->
-        }.compilesWithoutError()
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorCount(0)
+            }
+        }
     }
 
     @Test
     fun testNonAbstract() {
-        singleDao("@Dao public class MyDao {}") { _, _ -> }
-            .failsToCompile()
-            .withErrorContaining(ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE)
+        singleDao("@Dao public class MyDao {}") { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.DAO_MUST_BE_AN_ABSTRACT_CLASS_OR_AN_INTERFACE
+                )
+            }
+        }
     }
 
     @Test
@@ -83,30 +90,39 @@
                     int getFoo();
                 }
         """
-        ) { _, _ ->
-        }.failsToCompile()
-            .withErrorContaining(ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasErrorContaining(ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
+            }
+        }
     }
 
     @Test
     fun testAbstractMethodWithoutQueryInLibraryClass() {
-        val libraryClasspath = compileLibrarySource(
+        val librarySource = Source.java(
             "test.library.MissingAnnotationsBaseDao",
             """
+                package test.library;
                 public interface MissingAnnotationsBaseDao {
                     int getFoo();
                 }
                 """
         )
+        val libraryClasspath = compileFiles(
+            listOf(librarySource)
+        )
         singleDao(
             "@Dao public interface MyDao extends test.library.MissingAnnotationsBaseDao {}",
-            classpathFiles = libraryClasspath
-        ) { _, _ -> }
-            .failsToCompile()
-            .withErrorContaining(
-                ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD +
-                    " - test.library.MissingAnnotationsBaseDao.getFoo()"
-            )
+            classpathFiles = listOf(libraryClasspath) + getSystemClasspathFiles()
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasRawOutputContaining(
+                    ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD +
+                        " - test.library.MissingAnnotationsBaseDao.getFoo()"
+                )
+                hasErrorContaining(ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
+            }
+        }
     }
 
     @Test
@@ -119,10 +135,11 @@
                     int getFoo(int x);
                 }
         """
-        ) { _, _ ->
-        }.failsToCompile().withErrorContaining(
-            ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD
-        )
+        ) { _, invocation ->
+            invocation.assertCompilationResult {
+                hasError(ProcessorErrors.INVALID_ANNOTATION_COUNT_IN_DAO_METHOD)
+            }
+        }
     }
 
     @Test
@@ -138,7 +155,7 @@
             assertThat(dao.queryMethods.size, `is`(1))
             val method = dao.queryMethods.first()
             assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -154,7 +171,7 @@
             assertThat(dao.queryMethods.size, `is`(1))
             val method = dao.queryMethods.first()
             assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -175,7 +192,7 @@
             assertThat(dao.insertionMethods.size, `is`(1))
             val insertMethod = dao.insertionMethods.first()
             assertThat(insertMethod.name, `is`("insert"))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -191,7 +208,7 @@
             assertThat(dao.queryMethods.size, `is`(1))
             val method = dao.queryMethods.first()
             assertThat(method.name, `is`("getIds"))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -227,7 +244,7 @@
                     `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH))
                 )
             }
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -263,7 +280,7 @@
                     `is`(setOf(Warning.ALL, Warning.CURSOR_MISMATCH))
                 )
             }
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -283,14 +300,16 @@
                     abstract java.util.List<Merged> loadUsers();
                 }
                 """
-        ) { dao, _ ->
+        ) { dao, invocation ->
             assertThat(dao.queryMethods.size, `is`(1))
             assertThat(
                 dao.queryMethods.filterIsInstance<ReadQueryMethod>().first().inTransaction,
                 `is`(false)
             )
-        }.compilesWithoutError()
-            .withWarningContaining(ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+            invocation.assertCompilationResult {
+                hasWarning(ProcessorErrors.TRANSACTION_MISSING_ON_RELATION)
+            }
+        }
     }
 
     @Test
@@ -311,14 +330,16 @@
                     abstract java.util.List<Merged> loadUsers();
                 }
                 """
-        ) { dao, _ ->
+        ) { dao, invocation ->
             assertThat(dao.queryMethods.size, `is`(1))
             assertThat(
                 dao.queryMethods.filterIsInstance<ReadQueryMethod>().first().inTransaction,
                 `is`(false)
             )
-        }.compilesWithoutError()
-            .withWarningCount(0)
+            invocation.assertCompilationResult {
+                hasNoWarnings()
+            }
+        }
     }
 
     @Test
@@ -339,15 +360,17 @@
                     abstract java.util.List<Merged> loadUsers();
                 }
                 """
-        ) { dao, _ ->
+        ) { dao, invocation ->
             // test sanity
             assertThat(dao.queryMethods.size, `is`(1))
             assertThat(
                 dao.queryMethods.filterIsInstance<ReadQueryMethod>().first().inTransaction,
                 `is`(true)
             )
-        }.compilesWithoutError()
-            .withWarningCount(0)
+            invocation.assertCompilationResult {
+                hasNoWarnings()
+            }
+        }
     }
 
     @Test
@@ -363,7 +386,7 @@
             assertThat(dao.queryMethods.size, `is`(1))
             val method = dao.queryMethods.first()
             assertThat(method.name, `is`("deleteAllIds"))
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -375,71 +398,53 @@
                     abstract void getAllIds();
                 }
                 """
-        ) { dao, _ ->
+        ) { dao, invocation ->
             assertThat(dao.queryMethods.size, `is`(1))
             val method = dao.queryMethods.first()
             assertThat(method.name, `is`("getAllIds"))
-        }.failsToCompile().withErrorContaining(
-            "Not sure how to convert a Cursor to this method's return type (void)"
-        )
+            invocation.assertCompilationResult {
+                hasErrorContaining(
+                    ProcessorErrors.cannotFindQueryResultAdapter(TypeName.VOID)
+                )
+            }
+        }
     }
 
-    fun singleDao(
+    private fun singleDao(
         vararg inputs: String,
-        classpathFiles: Set<File> = emptySet(),
-        handler: (Dao, TestInvocation) -> Unit
-    ): CompileTester {
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-            .that(
-                listOf(
-                    JavaFileObjects.forSourceString(
-                        "foo.bar.MyDao",
-                        DAO_PREFIX + inputs.joinToString("\n")
-                    ),
-                    COMMON.USER
+        classpathFiles: List<File> = emptyList(),
+        handler: (Dao, XTestInvocation) -> Unit
+    ) {
+        runProcessorTest(
+            sources = listOf(
+                Source.java(
+                    "foo.bar.MyDao",
+                    DAO_PREFIX + inputs.joinToString("\n")
+                ),
+                Source.fromJavaFileObject(COMMON.USER)
+            ),
+            classpath = classpathFiles
+        ) { invocation: XTestInvocation ->
+            val dao = invocation.roundEnv
+                .getTypeElementsAnnotatedWith(
+                    androidx.room.Dao::class.java
                 )
-            )
-            .apply {
-                if (classpathFiles.isNotEmpty()) {
-                    withClasspath(classpathFiles)
-                }
+                .first()
+            check(dao.isTypeElement())
+            val dbVerifier = if (enableVerification) {
+                createVerifierFromEntitiesAndViews(invocation)
+            } else {
+                null
             }
-            .processedWith(
-                TestProcessor.builder()
-                    .forAnnotations(
-                        java.lang.SuppressWarnings::class,
-                        androidx.room.Dao::class,
-                        androidx.room.Entity::class,
-                        androidx.room.Relation::class,
-                        androidx.room.Transaction::class,
-                        androidx.room.ColumnInfo::class,
-                        androidx.room.PrimaryKey::class,
-                        androidx.room.Query::class
-                    )
-                    .nextRunHandler { invocation ->
-                        val dao = invocation.roundEnv
-                            .getTypeElementsAnnotatedWith(
-                                androidx.room.Dao::class.java
-                            )
-                            .first()
-                        check(dao.isTypeElement())
-                        val dbVerifier = if (enableVerification) {
-                            createVerifierFromEntitiesAndViews(invocation)
-                        } else {
-                            null
-                        }
-                        val dbType = invocation.context.processingEnv
-                            .requireType(RoomTypeNames.ROOM_DB)
-                        val parser = DaoProcessor(
-                            invocation.context,
-                            dao, dbType, dbVerifier
-                        )
-
-                        val parsedDao = parser.process()
-                        handler(parsedDao, invocation)
-                        true
-                    }
-                    .build()
+            val dbType = invocation.context.processingEnv
+                .requireType(RoomTypeNames.ROOM_DB)
+            val parser = DaoProcessor(
+                invocation.context,
+                dao, dbType, dbVerifier
             )
+
+            val parsedDao = parser.process()
+            handler(parsedDao, invocation)
+        }
     }
 }
diff --git a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
index 801d997..94c0785 100644
--- a/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/processor/QueryMethodProcessorTest.kt
@@ -19,6 +19,7 @@
 import COMMON
 import androidx.room.Dao
 import androidx.room.Query
+import androidx.room.compiler.processing.XType
 import androidx.room.ext.CommonTypeNames
 import androidx.room.ext.KotlinTypeNames
 import androidx.room.ext.LifecyclesTypeNames
@@ -26,7 +27,6 @@
 import androidx.room.ext.typeName
 import androidx.room.parser.QueryType
 import androidx.room.parser.Table
-import androidx.room.compiler.processing.XType
 import androidx.room.processor.ProcessorErrors.cannotFindQueryResultAdapter
 import androidx.room.solver.query.result.DataSourceFactoryQueryResultBinder
 import androidx.room.solver.query.result.ListQueryResultAdapter
@@ -910,7 +910,11 @@
             assertThat(adapter?.mapping?.unusedColumns, `is`(listOf("name", "lastName")))
             assertThat(adapter?.mapping?.unusedFields, `is`(adapter?.pojo?.fields as List<Field>))
         }?.failsToCompile()
-            ?.withErrorContaining(cannotFindQueryResultAdapter("foo.bar.MyClass.Pojo"))
+            ?.withErrorContaining(
+                cannotFindQueryResultAdapter(
+                    ClassName.get("foo.bar", "MyClass", "Pojo")
+                )
+            )
             ?.and()
             ?.withWarningContaining(
                 ProcessorErrors.cursorPojoMismatch(
@@ -942,7 +946,11 @@
         ) { _, _, _ ->
         }?.failsToCompile()
             ?.withErrorContaining("no such column: age")
-            ?.and()?.withErrorContaining(cannotFindQueryResultAdapter("foo.bar.MyClass.Pojo"))
+            ?.and()?.withErrorContaining(
+                cannotFindQueryResultAdapter(
+                    ClassName.get("foo.bar", "MyClass", "Pojo")
+                )
+            )
             ?.and()?.withErrorCount(2)
             ?.withWarningCount(0)
     }
@@ -1105,7 +1113,11 @@
             return assertion
         } else {
             assertion.failsToCompile()
-                .withErrorContaining(cannotFindQueryResultAdapter("foo.bar.MyClass.Pojo"))
+                .withErrorContaining(
+                    cannotFindQueryResultAdapter(
+                        ClassName.get("foo.bar", "MyClass", "Pojo")
+                    )
+                )
             return null
         }
     }
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt b/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
index 0a14017..e886664 100644
--- a/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/InProcessorTest.kt
@@ -22,22 +22,38 @@
 import com.google.common.truth.Truth.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
-import org.junit.runners.JUnit4
+import org.junit.runners.Parameterized
 
-@RunWith(JUnit4::class)
-class InProcessorTest {
+@RunWith(Parameterized::class)
+class InProcessorTest(
+    private val kotlinCode: Boolean
+) {
     @Test
     fun testInProcessorTestRuns() {
-        val source = Source.java(
-            qName = "foo.bar.MyClass",
-            code = """
+        val source = if (kotlinCode) {
+            Source.kotlin(
+                filePath = "MyClass.kt",
+                code = """
+                package foo.bar
+                abstract class MyClass {
+                @androidx.room.Query("foo")
+                abstract fun setFoo(foo: String):Unit
+                }
+                """.trimIndent()
+            )
+        } else {
+            Source.java(
+                qName = "foo.bar.MyClass",
+                code = """
                 package foo.bar;
                 abstract public class MyClass {
                 @androidx.room.Query("foo")
                 abstract public void setFoo(String foo);
                 }
-            """.trimIndent()
-        )
+                """.trimIndent()
+            )
+        }
+
         var runCount = 0
         runProcessorTest(sources = listOf(source)) {
             assertThat(
@@ -45,15 +61,23 @@
             ).isNotNull()
             runCount++
         }
-        // run 3 times: javac, kapt, ksp (if enabled)
+        // run 1 or 2 times
+        // +1 if KSP is enabled
+        // 1 for javac or kapt depending on whether source is in kotlin or java
         assertThat(
             runCount
         ).isEqualTo(
-            2 + if (CompilationTestCapabilities.canTestWithKsp) {
+            1 + if (CompilationTestCapabilities.canTestWithKsp) {
                 1
             } else {
                 0
             }
         )
     }
+
+    companion object {
+        @JvmStatic
+        @Parameterized.Parameters(name = "kotlinCode_{0}")
+        fun params() = arrayOf(true, false)
+    }
 }
diff --git a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
index 389eaf6..a801b40 100644
--- a/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/testing/test_util.kt
@@ -337,7 +337,7 @@
     return getSystemClasspathFiles() + lib
 }
 
-private fun getSystemClasspathFiles(): Set<File> {
+fun getSystemClasspathFiles(): Set<File> {
     val pathSeparator = System.getProperty("path.separator")!!
     return System.getProperty("java.class.path")!!.split(pathSeparator).map { File(it) }.toSet()
 }
diff --git a/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt b/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt
index a61bb70..fc1c911 100644
--- a/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt
+++ b/room/compiler/src/test/kotlin/androidx/room/writer/SQLiteOpenHelperWriterTest.kt
@@ -16,21 +16,17 @@
 
 package androidx.room.writer
 
-import androidx.annotation.NonNull
+import androidx.room.compiler.processing.util.Source
+import androidx.room.compiler.processing.util.XTestInvocation
+import androidx.room.compiler.processing.util.runProcessorTest
 import androidx.room.processor.DatabaseProcessor
-import androidx.room.testing.TestInvocation
-import androidx.room.testing.TestProcessor
+import androidx.room.testing.context
 import androidx.room.vo.Database
-import com.google.common.truth.Truth
-import com.google.testing.compile.CompileTester
-import com.google.testing.compile.JavaFileObjects
-import com.google.testing.compile.JavaSourcesSubjectFactory
 import org.hamcrest.CoreMatchers.`is`
 import org.hamcrest.MatcherAssert.assertThat
 import org.junit.Test
 import org.junit.runner.RunWith
 import org.junit.runners.JUnit4
-import javax.tools.JavaFileObject
 
 @RunWith(JUnit4::class)
 class SQLiteOpenHelperWriterTest {
@@ -43,7 +39,7 @@
         private const val ENTITY_PREFIX = DATABASE_PREFIX + """
             @Entity%s
             public class MyEntity {
-            """
+        """
         private const val ENTITY_SUFFIX = "}"
     }
 
@@ -68,7 +64,7 @@
                         " PRIMARY KEY(`uuid`))"
                 )
             )
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -93,7 +89,7 @@
                         "`age` INTEGER NOT NULL, PRIMARY KEY(`uuid`, `name`))"
                 )
             )
-        }.compilesWithoutError()
+        }
     }
 
     @Test
@@ -117,7 +113,7 @@
                             " `name` TEXT, `age` INTEGER NOT NULL)"
                     )
                 )
-            }.compilesWithoutError()
+            }
         }
     }
 
@@ -142,7 +138,7 @@
                             " `name` TEXT, `age` INTEGER NOT NULL)"
                     )
                 )
-            }.compilesWithoutError()
+            }
         }
     }
 
@@ -151,31 +147,31 @@
         singleView("SELECT uuid, name FROM MyEntity") { database, _ ->
             val query = SQLiteOpenHelperWriter(database).createViewQuery(database.views.first())
             assertThat(query, `is`("CREATE VIEW `MyView` AS SELECT uuid, name FROM MyEntity"))
-        }.compilesWithoutError()
+        }
     }
 
     private fun singleEntity(
         input: String,
         attributes: Map<String, String> = mapOf(),
-        handler: (Database, TestInvocation) -> Unit
-    ): CompileTester {
+        handler: (Database, XTestInvocation) -> Unit
+    ) {
         val attributesReplacement = if (attributes.isEmpty()) {
             ""
         } else {
             "(" + attributes.entries.joinToString(",") { "${it.key} = ${it.value}" } + ")"
         }
-        val entity = JavaFileObjects.forSourceString(
+        val entity = Source.java(
             "foo.bar.MyEntity",
             ENTITY_PREFIX.format(attributesReplacement) + input + ENTITY_SUFFIX
         )
-        return verify(listOf(entity), "", handler)
+        verify(listOf(entity), "", handler)
     }
 
     private fun singleView(
         query: String,
-        handler: (Database, TestInvocation) -> Unit
-    ): CompileTester {
-        val entity = JavaFileObjects.forSourceString(
+        handler: (Database, XTestInvocation) -> Unit
+    ) {
+        val entity = Source.java(
             "foo.bar.MyEntity",
             ENTITY_PREFIX.format("") + """
                     @PrimaryKey
@@ -184,9 +180,9 @@
                     @NonNull
                     String name;
                     int age;
-                """ + ENTITY_SUFFIX
+            """ + ENTITY_SUFFIX
         )
-        val view = JavaFileObjects.forSourceString(
+        val view = Source.java(
             "foo.bar.MyView",
             DATABASE_PREFIX + """
                     @DatabaseView("$query")
@@ -194,39 +190,33 @@
                         public String uuid;
                         public String name;
                     }
-                """
+            """
         )
         return verify(listOf(entity, view), "views = {MyView.class},", handler)
     }
 
     private fun verify(
-        jfos: List<JavaFileObject> = emptyList(),
+        sources: List<Source> = emptyList(),
         databaseAttribute: String,
-        handler: (Database, TestInvocation) -> Unit
-    ): CompileTester {
-        val databaseCode = """
+        handler: (Database, XTestInvocation) -> Unit
+    ) {
+        val databaseCode = Source.java(
+            "foo.bar.MyDatabase",
+            """
             package foo.bar;
             import androidx.room.*;
             @Database(entities = {MyEntity.class}, $databaseAttribute version = 3)
             abstract public class MyDatabase extends RoomDatabase {
             }
-        """
-        return Truth.assertAbout(JavaSourcesSubjectFactory.javaSources())
-            .that(jfos + JavaFileObjects.forSourceString("foo.bar.MyDatabase", databaseCode))
-            .processedWith(
-                TestProcessor.builder()
-                    .forAnnotations(
-                        androidx.room.Database::class,
-                        NonNull::class
-                    )
-                    .nextRunHandler { invocation ->
-                        val db = invocation.roundEnv
-                            .getTypeElementsAnnotatedWith(androidx.room.Database::class.java)
-                            .first()
-                        handler(DatabaseProcessor(invocation.context, db).process(), invocation)
-                        true
-                    }
-                    .build()
-            )
+            """
+        )
+        runProcessorTest(
+            sources = sources + databaseCode
+        ) { invocation ->
+            val db = invocation.roundEnv
+                .getTypeElementsAnnotatedWith(androidx.room.Database::class.java)
+                .first()
+            handler(DatabaseProcessor(invocation.context, db).process(), invocation)
+        }
     }
 }
diff --git a/settings.gradle b/settings.gradle
index ba08f5b..a8b94f33 100644
--- a/settings.gradle
+++ b/settings.gradle
@@ -30,12 +30,28 @@
     WEAR,
 }
 
-private static Set<BuildType> createRequestedFilter() {
+private String getRequestedProjectSubsetName() {
+    if (startParameter.projectProperties.containsKey("androidx.projects")) {
+        return startParameter.projectProperties["androidx.projects"].toUpperCase()
+    }
+    if (System.getenv().containsKey("ANDROIDX_PROJECTS")) {
+        return System.getenv()["ANDROIDX_PROJECTS"].toUpperCase()
+    }
+    return null
+}
+
+boolean isAllProjects() {
+    String projectSubsetName = getRequestedProjectSubsetName()
+    return requestedProjectSubsetName == null || requestedProjectSubsetName == "ALL"
+}
+
+private Set<BuildType> createRequestedFilter() {
     Set<BuildType> filter = new HashSet<>()
-    if (!System.getenv().containsKey("ANDROIDX_PROJECTS")) return null
-    String[] requestedFilter = System.getenv("ANDROIDX_PROJECTS").split(",")
+    String projectSubsetName = getRequestedProjectSubsetName()
+    if (projectSubsetName == null) return null
+    String[] requestedFilter = projectSubsetName.split(",")
     for (String requestedType : requestedFilter) {
-        switch (requestedType.toUpperCase()) {
+        switch (requestedType) {
             case "MAIN":
                 filter.add(BuildType.MAIN)
                 break
@@ -74,7 +90,8 @@
  * Note that null value means all the projects should be included
  */
 @Field
-Set<BuildType> requestedFilter = createRequestedFilter()
+Set<BuildType> requestedFilter
+requestedFilter = createRequestedFilter()
 
 boolean shouldIncludeForFilter(List<BuildType> includeList) {
     if (includeList.empty) return true
@@ -178,6 +195,7 @@
     includeProject(":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-dep", "buildSrc-tests/max-dep-versions/buildSrc-tests-max-dep-versions-dep", [BuildType.MAIN])
     includeProject(":buildSrc-tests:max-dep-versions:buildSrc-tests-max-dep-versions-main", "buildSrc-tests/max-dep-versions/buildSrc-tests-max-dep-versions-main", [BuildType.MAIN])
 }
+includeProject(":buildSrc-tests:project-subsets", "buildSrc-tests/project-subsets", [BuildType.MAIN])
 includeProject(":camera:camera-camera2", "camera/camera-camera2", [BuildType.MAIN])
 includeProject(":camera:camera-camera2-pipe", "camera/camera-camera2-pipe", [BuildType.MAIN])
 includeProject(":camera:camera-camera2-pipe-integration", "camera/camera-camera2-pipe-integration", [BuildType.MAIN])
@@ -299,6 +317,7 @@
 includeProject(":core:core-animation-integration-tests:testapp", "core/core-animation-integration-tests/testapp", [BuildType.MAIN])
 includeProject(":core:core-animation-testing", "core/core-animation-testing", [BuildType.MAIN])
 includeProject(":core:core-appdigest", "core/core-appdigest", [BuildType.MAIN])
+includeProject(":core:core-google-shortcuts", "core/core-google-shortcuts", [BuildType.MAIN])
 includeProject(":core:core-ktx", "core/core-ktx", [BuildType.MAIN])
 includeProject(":cursoradapter:cursoradapter", "cursoradapter/cursoradapter", [BuildType.MAIN])
 includeProject(":customview:customview", "customview/customview", [BuildType.MAIN])
@@ -655,11 +674,6 @@
 includeProject(":icing:nativeLib", new File(externalRoot, "icing/nativeLib"), [BuildType.MAIN])
 includeProject(":noto-emoji-compat", new File(externalRoot, "noto-fonts/emoji-compat"), [BuildType.MAIN])
 
-static boolean isAllProjects() {
-    if (!System.getenv().containsKey("ANDROIDX_PROJECTS")) return true
-    return System.getenv("ANDROIDX_PROJECTS") == "ALL"
-}
-
 if (isAllProjects()) {
     includeProject(":docs-tip-of-tree", "docs-tip-of-tree")
     includeProject(":docs-public", "docs-public")
diff --git a/slidingpanelayout/slidingpanelayout/api/current.txt b/slidingpanelayout/slidingpanelayout/api/current.txt
index be84592..75407f9 100644
--- a/slidingpanelayout/slidingpanelayout/api/current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/current.txt
@@ -9,15 +9,15 @@
     method @Deprecated public boolean canSlide();
     method public void close();
     method public boolean closePane();
-    method @ColorInt public int getCoveredFadeColor();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
     method public final int getLockMode();
     method @Px public int getParallaxDistance();
-    method @ColorInt public int getSliderFadeColor();
+    method @Deprecated @ColorInt public int getSliderFadeColor();
     method public boolean isOpen();
     method public boolean isSlideable();
     method public void open();
     method public boolean openPane();
-    method public void setCoveredFadeColor(@ColorInt int);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
     method public final void setLockMode(int);
     method public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
     method public void setParallaxDistance(@Px int);
@@ -27,7 +27,7 @@
     method @Deprecated public void setShadowResource(@DrawableRes int);
     method public void setShadowResourceLeft(int);
     method public void setShadowResourceRight(int);
-    method public void setSliderFadeColor(@ColorInt int);
+    method @Deprecated public void setSliderFadeColor(@ColorInt int);
     method @Deprecated public void smoothSlideClosed();
     method @Deprecated public void smoothSlideOpen();
     field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
diff --git a/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_current.txt b/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_current.txt
index be84592..75407f9 100644
--- a/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/public_plus_experimental_current.txt
@@ -9,15 +9,15 @@
     method @Deprecated public boolean canSlide();
     method public void close();
     method public boolean closePane();
-    method @ColorInt public int getCoveredFadeColor();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
     method public final int getLockMode();
     method @Px public int getParallaxDistance();
-    method @ColorInt public int getSliderFadeColor();
+    method @Deprecated @ColorInt public int getSliderFadeColor();
     method public boolean isOpen();
     method public boolean isSlideable();
     method public void open();
     method public boolean openPane();
-    method public void setCoveredFadeColor(@ColorInt int);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
     method public final void setLockMode(int);
     method public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
     method public void setParallaxDistance(@Px int);
@@ -27,7 +27,7 @@
     method @Deprecated public void setShadowResource(@DrawableRes int);
     method public void setShadowResourceLeft(int);
     method public void setShadowResourceRight(int);
-    method public void setSliderFadeColor(@ColorInt int);
+    method @Deprecated public void setSliderFadeColor(@ColorInt int);
     method @Deprecated public void smoothSlideClosed();
     method @Deprecated public void smoothSlideOpen();
     field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
diff --git a/slidingpanelayout/slidingpanelayout/api/restricted_current.txt b/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
index be84592..75407f9 100644
--- a/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
+++ b/slidingpanelayout/slidingpanelayout/api/restricted_current.txt
@@ -9,15 +9,15 @@
     method @Deprecated public boolean canSlide();
     method public void close();
     method public boolean closePane();
-    method @ColorInt public int getCoveredFadeColor();
+    method @Deprecated @ColorInt public int getCoveredFadeColor();
     method public final int getLockMode();
     method @Px public int getParallaxDistance();
-    method @ColorInt public int getSliderFadeColor();
+    method @Deprecated @ColorInt public int getSliderFadeColor();
     method public boolean isOpen();
     method public boolean isSlideable();
     method public void open();
     method public boolean openPane();
-    method public void setCoveredFadeColor(@ColorInt int);
+    method @Deprecated public void setCoveredFadeColor(@ColorInt int);
     method public final void setLockMode(int);
     method public void setPanelSlideListener(androidx.slidingpanelayout.widget.SlidingPaneLayout.PanelSlideListener?);
     method public void setParallaxDistance(@Px int);
@@ -27,7 +27,7 @@
     method @Deprecated public void setShadowResource(@DrawableRes int);
     method public void setShadowResourceLeft(int);
     method public void setShadowResourceRight(int);
-    method public void setSliderFadeColor(@ColorInt int);
+    method @Deprecated public void setSliderFadeColor(@ColorInt int);
     method @Deprecated public void smoothSlideClosed();
     method @Deprecated public void smoothSlideOpen();
     field public static final int LOCK_MODE_LOCKED_CLOSED = 2; // 0x2
diff --git a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
index 8076cd9..9e2f06d 100644
--- a/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
+++ b/slidingpanelayout/slidingpanelayout/src/main/java/androidx/slidingpanelayout/widget/SlidingPaneLayout.java
@@ -21,7 +21,6 @@
 import android.graphics.Canvas;
 import android.graphics.Paint;
 import android.graphics.PixelFormat;
-import android.graphics.PorterDuff;
 import android.graphics.Rect;
 import android.graphics.drawable.Drawable;
 import android.os.Build;
@@ -57,14 +56,13 @@
 
 /**
  * SlidingPaneLayout provides a horizontal, multi-pane layout for use at the top level
- * of a UI. A left (or first) pane is treated as a content list or browser, subordinate to a
+ * of a UI. A left (or start) pane is treated as a content list or browser, subordinate to a
  * primary detail view for displaying content.
  *
- * <p>Child views may overlap if their combined width exceeds the available width
- * in the SlidingPaneLayout. When this occurs the user may slide the topmost view out of the way
- * by dragging it, or by navigating in the direction of the overlapped view using a keyboard.
- * If the content of the dragged child view is itself horizontally scrollable, the user may
- * grab it by the very edge.</p>
+ * <p>Child views overlap if their combined width exceeds the available width
+ * in the SlidingPaneLayout. Each of child views is expand out to fill the available width in
+ * the SlidingPaneLayout. When this occurs, the user may slide the topmost view out of the way
+ * by dragging it, and dragging back it from the very edge.</p>
  *
  * <p>Thanks to this sliding behavior, SlidingPaneLayout may be suitable for creating layouts
  * that can smoothly adapt across many different screen sizes, expanding out fully on larger
@@ -82,40 +80,18 @@
  * displaying the contents of the selected thread. Inappropriate uses of SlidingPaneLayout include
  * switching between disparate functions of your app, such as jumping from a social stream view
  * to a view of your personal profile - cases such as this should use the navigation drawer
- * pattern instead. ({@link androidx.drawerlayout.widget.DrawerLayout DrawerLayout} implements this pattern.)</p>
+ * pattern instead. ({@link androidx.drawerlayout.widget.DrawerLayout DrawerLayout} implements
+ * this pattern.)</p>
  *
  * <p>Like {@link android.widget.LinearLayout LinearLayout}, SlidingPaneLayout supports
  * the use of the layout parameter <code>layout_weight</code> on child views to determine
  * how to divide leftover space after measurement is complete. It is only relevant for width.
  * When views do not overlap weight behaves as it does in a LinearLayout.</p>
- *
- * <p>When views do overlap, weight on a slideable pane indicates that the pane should be
- * sized to fill all available space in the closed state. Weight on a pane that becomes covered
- * indicates that the pane should be sized to fill all available space except a small minimum strip
- * that the user may use to grab the slideable view and pull it back over into a closed state.</p>
  */
 public class SlidingPaneLayout extends ViewGroup implements Openable {
     private static final String TAG = "SlidingPaneLayout";
 
     /**
-     * Default size of the overhang for a pane in the open state.
-     * At least this much of a sliding pane will remain visible.
-     * This indicates that there is more content available and provides
-     * a "physical" edge to grab to pull it closed.
-     */
-    private static final int DEFAULT_OVERHANG_SIZE = 32; // dp;
-
-    /**
-     * If no fade color is given by default it will fade to 80% gray.
-     */
-    private static final int DEFAULT_FADE_COLOR = 0xcccccccc;
-
-    /**
-     * The fade color used for the sliding panel. 0 = no fading.
-     */
-    private int mSliderFadeColor = DEFAULT_FADE_COLOR;
-
-    /**
      * Minimum velocity that will be detected as a fling
      */
     private static final int MIN_FLING_VELOCITY = 400; // dips per second
@@ -125,11 +101,6 @@
             "androidx.slidingpanelayout.widget.SlidingPaneLayout";
 
     /**
-     * The fade color used for the panel covered by the slider. 0 = no fading.
-     */
-    private int mCoveredFadeColor;
-
-    /**
      * Drawable used to draw the shadow between panes by default.
      */
     private Drawable mShadowDrawableLeft;
@@ -140,13 +111,6 @@
     private Drawable mShadowDrawableRight;
 
     /**
-     * The size of the overhang in pixels.
-     * This is the minimum section of the sliding panel that will
-     * be visible in the open state to allow for a closing drag.
-     */
-    private final int mOverhangSize;
-
-    /**
      * True if a panel can slide with the current measurements
      */
     private boolean mCanSlide;
@@ -259,13 +223,16 @@
     public interface PanelSlideListener {
         /**
          * Called when a sliding pane's position changes.
-         * @param panel The child view that was moved
+         *
+         * @param panel       The child view that was moved
          * @param slideOffset The new offset of this sliding pane within its range, from 0-1
          */
         void onPanelSlide(@NonNull View panel, float slideOffset);
+
         /**
          * Called when a sliding pane becomes slid completely open. The pane may or may not
          * be interactive at this point depending on how much of the pane is visible.
+         *
          * @param panel The child view that was slid to an open position, revealing other panes
          */
         void onPanelOpened(@NonNull View panel);
@@ -273,6 +240,7 @@
         /**
          * Called when a sliding pane becomes slid completely closed. The pane is now guaranteed
          * to be interactive. It may now obscure other views in the layout.
+         *
          * @param panel The child view that was slid to a closed position
          */
         void onPanelClosed(@NonNull View panel);
@@ -286,9 +254,11 @@
         @Override
         public void onPanelSlide(View panel, float slideOffset) {
         }
+
         @Override
         public void onPanelOpened(View panel) {
         }
+
         @Override
         public void onPanelClosed(View panel) {
         }
@@ -306,7 +276,6 @@
         super(context, attrs, defStyle);
 
         final float density = context.getResources().getDisplayMetrics().density;
-        mOverhangSize = (int) (DEFAULT_OVERHANG_SIZE * density + 0.5f);
 
         setWillNotDraw(false);
 
@@ -331,7 +300,6 @@
 
     /**
      * @return The distance the lower pane will parallax by when the upper pane is fully closed.
-     *
      * @see #setParallaxDistance(int)
      */
     @Px
@@ -343,17 +311,21 @@
      * Set the color used to fade the sliding pane out when it is slid most of the way offscreen.
      *
      * @param color An ARGB-packed color value
+     * @deprecated SlidingPaneLayout no longer uses this field.
      */
+    @Deprecated
     public void setSliderFadeColor(@ColorInt int color) {
-        mSliderFadeColor = color;
     }
 
     /**
      * @return The ARGB-packed color value used to fade the sliding pane
+     *
+     * @deprecated This field is no longer populated by SlidingPaneLayout.
      */
+    @Deprecated
     @ColorInt
     public int getSliderFadeColor() {
-        return mSliderFadeColor;
+        return 0;
     }
 
     /**
@@ -361,17 +333,21 @@
      * will become fully covered in the closed state.
      *
      * @param color An ARGB-packed color value
+     * @deprecated SlidingPaneLayout no longer uses this field.
      */
+    @Deprecated
     public void setCoveredFadeColor(@ColorInt int color) {
-        mCoveredFadeColor = color;
     }
 
     /**
      * @return The ARGB-packed color value used to fade the fixed pane
+     *
+     * @deprecated This field is no longer populated by SlidingPaneLayout
      */
+    @Deprecated
     @ColorInt
     public int getCoveredFadeColor() {
-        return mCoveredFadeColor;
+        return 0;
     }
 
     public void setPanelSlideListener(@Nullable PanelSlideListener listener) {
@@ -601,93 +577,49 @@
             }
 
             widthRemaining -= childWidth;
+            // Skip first child (list pane), the list pane is always a non-sliding pane.
+            if (i == 0) {
+                continue;
+            }
             canSlide |= lp.slideable = widthRemaining < 0;
             if (lp.slideable) {
                 mSlideableView = child;
             }
         }
-
-        // Resolve weight and make sure non-sliding panels are smaller than the full screen.
+        // Second pass. Resolve weight.
+        // Child views overlap when the combined width of child views cannot fit into the
+        // available width. Each of child views is sized to fill all available space. If there is
+        // no overlap, distribute the extra width proportionally to weight.
         if (canSlide || weightSum > 0) {
-            final int fixedPanelWidthLimit = widthAvailable - mOverhangSize;
-
             for (int i = 0; i < childCount; i++) {
                 final View child = getChildAt(i);
-
                 if (child.getVisibility() == GONE) {
                     continue;
                 }
 
                 final LayoutParams lp = (LayoutParams) child.getLayoutParams();
-
-                if (child.getVisibility() == GONE) {
-                    continue;
-                }
-
                 final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
                 final int measuredWidth = skippedFirstPass ? 0 : child.getMeasuredWidth();
-                if (canSlide && child != mSlideableView) {
-                    if (lp.width < 0 && (measuredWidth > fixedPanelWidthLimit || lp.weight > 0)) {
-                        // Fixed panels in a sliding configuration should
-                        // be clamped to the fixed panel limit.
-                        final int childHeightSpec;
-                        if (skippedFirstPass) {
-                            // Do initial height measurement if we skipped measuring this view
-                            // the first time around.
-                            if (lp.height == LayoutParams.WRAP_CONTENT) {
-                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
-                                        MeasureSpec.AT_MOST);
-                            } else if (lp.height == LayoutParams.MATCH_PARENT) {
-                                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
-                                        MeasureSpec.EXACTLY);
-                            } else {
-                                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                                        MeasureSpec.EXACTLY);
-                            }
-                        } else {
-                            childHeightSpec = MeasureSpec.makeMeasureSpec(
-                                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
-                        }
-                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
-                                fixedPanelWidthLimit, MeasureSpec.EXACTLY);
-                        child.measure(childWidthSpec, childHeightSpec);
-                    }
-                } else if (lp.weight > 0) {
-                    int childHeightSpec;
-                    if (lp.width == 0) {
-                        // This was skipped the first time; figure out a real height spec.
-                        if (lp.height == LayoutParams.WRAP_CONTENT) {
-                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
-                                    MeasureSpec.AT_MOST);
-                        } else if (lp.height == LayoutParams.MATCH_PARENT) {
-                            childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
-                                    MeasureSpec.EXACTLY);
-                        } else {
-                            childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
-                                    MeasureSpec.EXACTLY);
-                        }
-                    } else {
-                        childHeightSpec = MeasureSpec.makeMeasureSpec(
-                                child.getMeasuredHeight(), MeasureSpec.EXACTLY);
-                    }
+                int newWidth = measuredWidth;
+                int childWidthSpec = 0;
+                if (canSlide) {
+                    // Child view consumes available space if the combined width cannot fit into
+                    // the layout available width.
+                    final int horizontalMargin = lp.leftMargin + lp.rightMargin;
+                    newWidth = widthAvailable - horizontalMargin;
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(
+                            newWidth, MeasureSpec.EXACTLY);
 
-                    if (canSlide) {
-                        // Consume available space
-                        final int horizontalMargin = lp.leftMargin + lp.rightMargin;
-                        final int newWidth = widthAvailable - horizontalMargin;
-                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
-                                newWidth, MeasureSpec.EXACTLY);
-                        if (measuredWidth != newWidth) {
-                            child.measure(childWidthSpec, childHeightSpec);
-                        }
-                    } else {
-                        // Distribute the extra width proportionally similar to LinearLayout
-                        final int widthToDistribute = Math.max(0, widthRemaining);
-                        final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
-                        final int childWidthSpec = MeasureSpec.makeMeasureSpec(
-                                measuredWidth + addedWidth, MeasureSpec.EXACTLY);
-                        child.measure(childWidthSpec, childHeightSpec);
-                    }
+                } else if (lp.weight > 0) {
+                    // Distribute the extra width proportionally similar to LinearLayout
+                    final int widthToDistribute = Math.max(0, widthRemaining);
+                    final int addedWidth = (int) (lp.weight * widthToDistribute / weightSum);
+                    newWidth = measuredWidth + addedWidth;
+                    childWidthSpec = MeasureSpec.makeMeasureSpec(newWidth, MeasureSpec.EXACTLY);
+                }
+                final int childHeightSpec = measureChildHeight(child, maxLayoutHeight);
+                if (measuredWidth != newWidth) {
+                    child.measure(childWidthSpec, childHeightSpec);
                 }
             }
         }
@@ -704,14 +636,33 @@
         }
     }
 
+    private static int measureChildHeight(@NonNull View child,
+            int maxLayoutHeight) {
+        final LayoutParams lp = (LayoutParams) child.getLayoutParams();
+        final int childHeightSpec;
+        final boolean skippedFirstPass = lp.width == 0 && lp.weight > 0;
+        if (skippedFirstPass) {
+            // This was skipped the first time; figure out a real height spec.
+            if (lp.height == LayoutParams.WRAP_CONTENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
+                        MeasureSpec.AT_MOST);
+            } else if (lp.height == LayoutParams.MATCH_PARENT) {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(maxLayoutHeight,
+                        MeasureSpec.EXACTLY);
+            } else {
+                childHeightSpec = MeasureSpec.makeMeasureSpec(lp.height,
+                        MeasureSpec.EXACTLY);
+            }
+        } else {
+            childHeightSpec = MeasureSpec.makeMeasureSpec(
+                    child.getMeasuredHeight(), MeasureSpec.EXACTLY);
+        }
+        return childHeightSpec;
+    }
+
     @Override
     protected void onLayout(boolean changed, int l, int t, int r, int b) {
         final boolean isLayoutRtl = isLayoutRtlSupport();
-        if (isLayoutRtl) {
-            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
-        } else {
-            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
-        }
         final int width = r - l;
         final int paddingStart = isLayoutRtl ? getPaddingRight() : getPaddingLeft();
         final int paddingEnd = isLayoutRtl ? getPaddingLeft() : getPaddingRight();
@@ -739,8 +690,7 @@
 
             if (lp.slideable) {
                 final int margin = lp.leftMargin + lp.rightMargin;
-                final int range = Math.min(nextXStart,
-                        width - paddingEnd - mOverhangSize) - xStart - margin;
+                final int range = Math.min(nextXStart, width - paddingEnd) - xStart - margin;
                 mSlideRange = range;
                 final int lpMargin = isLayoutRtl ? lp.rightMargin : lp.leftMargin;
                 lp.dimWhenOffset = xStart + lpMargin + range + childWidth / 2 > width - paddingEnd;
@@ -776,14 +726,6 @@
                 if (mParallaxBy != 0) {
                     parallaxOtherViews(mSlideOffset);
                 }
-                if (((LayoutParams) mSlideableView.getLayoutParams()).dimWhenOffset) {
-                    dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
-                }
-            } else {
-                // Reset the dim level of all children; it's irrelevant when nothing moves.
-                for (int i = 0; i < childCount; i++) {
-                    dimChildView(getChildAt(i), 0, mSliderFadeColor);
-                }
             }
             updateObscuredViewsVisibility(mSlideableView);
         }
@@ -1030,41 +972,18 @@
             parallaxOtherViews(mSlideOffset);
         }
 
-        if (lp.dimWhenOffset) {
-            dimChildView(mSlideableView, mSlideOffset, mSliderFadeColor);
-        }
         dispatchOnPanelSlide(mSlideableView);
     }
 
-    @SuppressWarnings("deprecation")
-    private void dimChildView(View v, float mag, int fadeColor) {
-        final LayoutParams lp = (LayoutParams) v.getLayoutParams();
-
-        if (mag > 0 && fadeColor != 0) {
-            final int baseAlpha = (fadeColor & 0xff000000) >>> 24;
-            int imag = (int) (baseAlpha * mag);
-            int color = imag << 24 | (fadeColor & 0xffffff);
-            if (lp.dimPaint == null) {
-                lp.dimPaint = new Paint();
-            }
-            lp.dimPaint.setColorFilter(new android.graphics.PorterDuffColorFilter(
-                    color, PorterDuff.Mode.SRC_OVER));
-            if (v.getLayerType() != View.LAYER_TYPE_HARDWARE) {
-                v.setLayerType(View.LAYER_TYPE_HARDWARE, lp.dimPaint);
-            }
-            invalidateChildRegion(v);
-        } else if (v.getLayerType() != View.LAYER_TYPE_NONE) {
-            if (lp.dimPaint != null) {
-                lp.dimPaint.setColorFilter(null);
-            }
-            final DisableLayerRunnable dlr = new DisableLayerRunnable(v);
-            mPostedRunnables.add(dlr);
-            ViewCompat.postOnAnimation(this, dlr);
-        }
-    }
-
     @Override
     protected boolean drawChild(Canvas canvas, View child, long drawingTime) {
+        final boolean isLayoutRtl = isLayoutRtlSupport();
+        final boolean enableEdgeRightTracking = isLayoutRtl ^ isOpen();
+        if (enableEdgeRightTracking) {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_RIGHT);
+        } else {
+            mDragHelper.setEdgeTrackingEnabled(ViewDragHelper.EDGE_LEFT);
+        }
         final LayoutParams lp = (LayoutParams) child.getLayoutParams();
         boolean result;
         final int save = canvas.save();
@@ -1143,7 +1062,7 @@
      * Smoothly animate mDraggingPane to the target X position within its range.
      *
      * @param slideOffset position to animate to
-     * @param velocity initial velocity in case of fling, or 0.
+     * @param velocity    initial velocity in case of fling, or 0.
      */
     boolean smoothSlideTo(float slideOffset, int velocity) {
         if (!mCanSlide) {
@@ -1185,11 +1104,10 @@
     }
 
     /**
+     * @param d drawable to use as a shadow
      * @deprecated Renamed to {@link #setShadowDrawableLeft(Drawable d)} to support LTR (left to
      * right language) and {@link #setShadowDrawableRight(Drawable d)} to support RTL (right to left
      * language) during opening/closing.
-     *
-     * @param d drawable to use as a shadow
      */
     @Deprecated
     public void setShadowDrawable(Drawable d) {
@@ -1287,9 +1205,6 @@
 
     private void parallaxOtherViews(float slideOffset) {
         final boolean isLayoutRtl = isLayoutRtlSupport();
-        final LayoutParams slideLp = (LayoutParams) mSlideableView.getLayoutParams();
-        final boolean dimViews = slideLp.dimWhenOffset
-                && (isLayoutRtl ? slideLp.rightMargin : slideLp.leftMargin) <= 0;
         final int childCount = getChildCount();
         for (int i = 0; i < childCount; i++) {
             final View v = getChildAt(i);
@@ -1301,23 +1216,18 @@
             final int dx = oldOffset - newOffset;
 
             v.offsetLeftAndRight(isLayoutRtl ? -dx : dx);
-
-            if (dimViews) {
-                dimChildView(v, isLayoutRtl ? mParallaxOffset - 1
-                        : 1 - mParallaxOffset, mCoveredFadeColor);
-            }
         }
     }
 
     /**
      * Tests scrollability within child views of v given a delta of dx.
      *
-     * @param v View to test for horizontal scrollability
+     * @param v      View to test for horizontal scrollability
      * @param checkV Whether the view v passed should itself be checked for scrollability (true),
      *               or just its children (false).
-     * @param dx Delta scrolled in pixels
-     * @param x X coordinate of the active touch point
-     * @param y Y coordinate of the active touch point
+     * @param dx     Delta scrolled in pixels
+     * @param x      X coordinate of the active touch point
+     * @param y      Y coordinate of the active touch point
      * @return true if child views of v can be scrolled by delta of dx.
      */
     protected boolean canScroll(View v, boolean checkV, int dx, int x, int y) {
@@ -1334,7 +1244,7 @@
                 if (x + scrollX >= child.getLeft() && x + scrollX < child.getRight()
                         && y + scrollY >= child.getTop() && y + scrollY < child.getBottom()
                         && canScroll(child, true, dx, x + scrollX - child.getLeft(),
-                                y + scrollY - child.getTop())) {
+                        y + scrollY - child.getTop())) {
                     return true;
                 }
             }
@@ -1447,7 +1357,7 @@
 
             int left;
             if (isLayoutRtlSupport()) {
-                int startToRight =  getPaddingRight() + lp.rightMargin;
+                int startToRight = getPaddingRight() + lp.rightMargin;
                 if (xvel < 0 || (xvel == 0 && mSlideOffset > 0.5f)) {
                     startToRight += mSlideRange;
                 }
@@ -1507,14 +1417,19 @@
         }
 
         @Override
+        public void onEdgeTouched(int edgeFlags, int pointerId) {
+            mDragHelper.captureChildView(mSlideableView, pointerId);
+        }
+
+        @Override
         public void onEdgeDragStarted(int edgeFlags, int pointerId) {
             mDragHelper.captureChildView(mSlideableView, pointerId);
         }
     }
 
     public static class LayoutParams extends ViewGroup.MarginLayoutParams {
-        private static final int[] ATTRS = new int[] {
-            android.R.attr.layout_weight
+        private static final int[] ATTRS = new int[]{
+                android.R.attr.layout_weight
         };
 
         /**
diff --git a/wear/wear-complications-data/api/restricted_current.txt b/wear/wear-complications-data/api/restricted_current.txt
index ace3c56..7327d38 100644
--- a/wear/wear-complications-data/api/restricted_current.txt
+++ b/wear/wear-complications-data/api/restricted_current.txt
@@ -168,7 +168,7 @@
   @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public final class ComplicationHelperActivity extends android.app.Activity implements androidx.core.app.ActivityCompat.OnRequestPermissionsResultCallback {
     ctor public ComplicationHelperActivity();
     method public static android.content.Intent createPermissionRequestHelperIntent(android.content.Context, android.content.ComponentName);
-    method public static android.content.Intent createProviderChooserHelperIntent(android.content.Context, android.content.ComponentName, int, java.util.Collection<androidx.wear.complications.data.ComplicationType!>);
+    method public static android.content.Intent createProviderChooserHelperIntent(android.content.Context, android.content.ComponentName, int, java.util.Collection<androidx.wear.complications.data.ComplicationType!>, String?);
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_PERMISSION_REQUEST_ONLY = "android.support.wearable.complications.ACTION_PERMISSION_REQUEST_ONLY";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_REQUEST_UPDATE_ALL_ACTIVE = "android.support.wearable.complications.ACTION_REQUEST_UPDATE_ALL_ACTIVE";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String ACTION_START_PROVIDER_CHOOSER = "android.support.wearable.complications.ACTION_START_PROVIDER_CHOOSER";
@@ -198,6 +198,7 @@
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_PENDING_INTENT = "android.support.wearable.complications.EXTRA_PENDING_INTENT";
     field public static final String EXTRA_PROVIDER_INFO = "android.support.wearable.complications.EXTRA_PROVIDER_INFO";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_SUPPORTED_TYPES = "android.support.wearable.complications.EXTRA_SUPPORTED_TYPES";
+    field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_WATCHFACE_INSTANCE_ID = "androidx.wear.complications.EXTRA_WATCHFACE_INSTANCE_ID";
     field @RestrictTo(androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP) public static final String EXTRA_WATCH_FACE_COMPONENT_NAME = "android.support.wearable.complications.EXTRA_WATCH_FACE_COMPONENT_NAME";
   }
 
diff --git a/wear/wear-complications-data/src/androidTest/java/androidx/wear/complications/ComplicationHelperActivityTest.kt b/wear/wear-complications-data/src/androidTest/java/androidx/wear/complications/ComplicationHelperActivityTest.kt
index fdb6534..e1a8e20 100644
--- a/wear/wear-complications-data/src/androidTest/java/androidx/wear/complications/ComplicationHelperActivityTest.kt
+++ b/wear/wear-complications-data/src/androidTest/java/androidx/wear/complications/ComplicationHelperActivityTest.kt
@@ -72,16 +72,26 @@
         }
     }
 
+    @Test
+    public fun instanceId() {
+        assertThat(
+            createIntent(instanceId = "ID-1")
+                .getStringExtra(ProviderChooserIntent.EXTRA_WATCHFACE_INSTANCE_ID)
+        ).isEqualTo("ID-1")
+    }
+
     /** Creates an intent with default values for unspecified parameters. */
     private fun createIntent(
         watchFaceComponentName: ComponentName = defaultWatchFaceComponentName,
         complicationId: Int = defaultComplicationId,
+        instanceId: String? = null,
         vararg supportedTypes: ComplicationType = defaultSupportedTypes
     ) = ComplicationHelperActivity.createProviderChooserHelperIntent(
         context,
         watchFaceComponentName,
         complicationId,
-        supportedTypes.asList()
+        supportedTypes.asList(),
+        instanceId
     )
 
     private companion object {
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
index 727ae00..c74a757 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ComplicationHelperActivity.java
@@ -212,17 +212,22 @@
      * @param supportedTypes the types supported by the complication, in decreasing order of
      *     preference. If a provider can supply data for more than one of these types, the type
      *     chosen will be whichever was specified first.
+     * @param watchFaceInstanceId The ID of the watchface being edited.
      */
     @NonNull
     public static Intent createProviderChooserHelperIntent(
             @NonNull Context context,
             @NonNull ComponentName watchFace,
             int watchFaceComplicationId,
-            @NonNull Collection<ComplicationType> supportedTypes) {
+            @NonNull Collection<ComplicationType> supportedTypes,
+            @Nullable String watchFaceInstanceId) {
         Intent intent = new Intent(context, ComplicationHelperActivity.class);
         intent.setAction(ACTION_START_PROVIDER_CHOOSER);
         intent.putExtra(ProviderChooserIntent.EXTRA_WATCH_FACE_COMPONENT_NAME, watchFace);
         intent.putExtra(ProviderChooserIntent.EXTRA_COMPLICATION_ID, watchFaceComplicationId);
+        if (watchFaceInstanceId != null) {
+            intent.putExtra(ProviderChooserIntent.EXTRA_WATCHFACE_INSTANCE_ID, watchFaceInstanceId);
+        }
         int[] wireSupportedTypes = new int[supportedTypes.size()];
         int i = 0;
         for (ComplicationType supportedType : supportedTypes) {
diff --git a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderChooserIntent.java b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderChooserIntent.java
index e5bb125..6d3d444 100644
--- a/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderChooserIntent.java
+++ b/wear/wear-complications-data/src/main/java/androidx/wear/complications/ProviderChooserIntent.java
@@ -84,6 +84,15 @@
             "android.support.wearable.complications.EXTRA_COMPLICATION_ID";
 
     /**
+     * Key for an extra that holds the watch face instance id.
+     *
+     * @hide
+     */
+    @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
+    public static final String EXTRA_WATCHFACE_INSTANCE_ID =
+            "androidx.wear.complications.EXTRA_WATCHFACE_INSTANCE_ID";
+
+    /**
      * Key for an extra used to include details of the chosen provider in the activity result
      * returned by the provider chooser.
      *
diff --git a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/WatchFaceConfigActivity.kt b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/WatchFaceConfigActivity.kt
index 78d69e4..88baad2 100644
--- a/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/WatchFaceConfigActivity.kt
+++ b/wear/wear-watchface-editor/samples/src/main/java/androidx/wear/watchface/editor/sample/WatchFaceConfigActivity.kt
@@ -214,5 +214,7 @@
     override fun onStop() {
         super.onStop()
         editorSession.close()
+        // Make sure the activity closes.
+        finish()
     }
 }
diff --git a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
index 94c6a30..1e9fd092 100644
--- a/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
+++ b/wear/wear-watchface-editor/src/androidTest/java/androidx/wear/watchface/editor/EditingSessionTest.kt
@@ -38,6 +38,7 @@
 import androidx.test.filters.MediumTest
 import androidx.wear.complications.ComplicationBounds
 import androidx.wear.complications.DefaultComplicationProviderPolicy
+import androidx.wear.complications.ProviderChooserIntent
 import androidx.wear.complications.ProviderInfoRetriever
 import androidx.wear.complications.SystemProviders
 import androidx.wear.complications.data.ComplicationType
@@ -194,9 +195,15 @@
 /** Fake ComplicationHelperActivity for testing. */
 public class TestComplicationHelperActivity : Activity() {
 
+    public companion object {
+        public var lastIntent: Intent? = null
+    }
+
     override fun onCreate(savedInstanceState: Bundle?) {
         super.onCreate(savedInstanceState)
 
+        lastIntent = intent
+
         setResult(
             123,
             Intent().apply {
@@ -717,6 +724,12 @@
                     0
                 )
             ).isEqualTo("Provider3")
+
+            assertThat(
+                TestComplicationHelperActivity.lastIntent?.extras?.getString(
+                    ProviderChooserIntent.EXTRA_WATCHFACE_INSTANCE_ID
+                )
+            ).isEqualTo(testInstanceId)
         }
     }
 
diff --git a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
index 67d1102..24299b1 100644
--- a/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
+++ b/wear/wear-watchface-editor/src/main/java/androidx/wear/watchface/editor/EditorSession.kt
@@ -292,7 +292,7 @@
         pendingComplicationProviderChooserResult = CompletableDeferred<Boolean>()
         pendingComplicationProviderId = complicationId
         chooseComplicationProvider.launch(
-            ComplicationProviderChooserRequest(this, complicationId)
+            ComplicationProviderChooserRequest(this, complicationId, instanceId)
         )
         return pendingComplicationProviderChooserResult!!.await()
     }
@@ -554,7 +554,8 @@
 
 internal class ComplicationProviderChooserRequest(
     internal val editorSession: EditorSession,
-    internal val complicationId: Int
+    internal val complicationId: Int,
+    internal val instanceId: String?
 )
 
 internal class ComplicationProviderChooserResult(
@@ -576,7 +577,8 @@
             context,
             input.editorSession.watchFaceComponentName,
             input.complicationId,
-            input.editorSession.complicationState[input.complicationId]!!.supportedTypes
+            input.editorSession.complicationState[input.complicationId]!!.supportedTypes,
+            input.instanceId
         )
         if (useTestComplicationHelperActivity) {
             intent.component = ComponentName(
diff --git a/wear/wear-watchface/api/current.txt b/wear/wear-watchface/api/current.txt
index 2de2ff2..18a58f9 100644
--- a/wear/wear-watchface/api/current.txt
+++ b/wear/wear-watchface/api/current.txt
@@ -96,7 +96,6 @@
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationDoubleTapped(int complicationId);
     method public default void onComplicationSingleTapped(int complicationId);
   }
 
diff --git a/wear/wear-watchface/api/public_plus_experimental_current.txt b/wear/wear-watchface/api/public_plus_experimental_current.txt
index 2de2ff2..18a58f9 100644
--- a/wear/wear-watchface/api/public_plus_experimental_current.txt
+++ b/wear/wear-watchface/api/public_plus_experimental_current.txt
@@ -96,7 +96,6 @@
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationDoubleTapped(int complicationId);
     method public default void onComplicationSingleTapped(int complicationId);
   }
 
diff --git a/wear/wear-watchface/api/restricted_current.txt b/wear/wear-watchface/api/restricted_current.txt
index 544e8b9..70efcc5 100644
--- a/wear/wear-watchface/api/restricted_current.txt
+++ b/wear/wear-watchface/api/restricted_current.txt
@@ -96,7 +96,6 @@
   }
 
   public static interface ComplicationsManager.TapCallback {
-    method public default void onComplicationDoubleTapped(int complicationId);
     method public default void onComplicationSingleTapped(int complicationId);
   }
 
diff --git a/wear/wear-watchface/samples/src/main/AndroidManifest.xml b/wear/wear-watchface/samples/src/main/AndroidManifest.xml
index 77e6821..1bf118d 100644
--- a/wear/wear-watchface/samples/src/main/AndroidManifest.xml
+++ b/wear/wear-watchface/samples/src/main/AndroidManifest.xml
@@ -61,7 +61,7 @@
                 android:resource="@drawable/watch_preview" />
             <meta-data
                 android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
-                android:value="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL"/>
+                android:value="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR"/>
             <meta-data
                 android:name="android.service.wallpaper"
                 android:resource="@xml/watch_face" />
@@ -95,7 +95,7 @@
                 android:resource="@drawable/watch_preview" />
             <meta-data
                 android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
-                android:value="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL"/>
+                android:value="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR"/>
             <meta-data
                 android:name="android.service.wallpaper"
                 android:resource="@xml/watch_face" />
@@ -129,7 +129,7 @@
                 android:resource="@drawable/watch_preview" />
             <meta-data
                 android:name="com.google.android.wearable.watchface.wearableConfigurationAction"
-                android:value="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL"/>
+                android:value="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR"/>
             <meta-data
                 android:name="android.service.wallpaper"
                 android:resource="@xml/watch_face" />
@@ -145,7 +145,7 @@
             android:label="Config"
             android:theme="@android:style/Theme.Translucent.NoTitleBar">
             <intent-filter>
-                <action android:name="com.google.android.clockwork.watchfaces.complication.CONFIG_DIGITAL" />
+                <action android:name="androidx.wear.watchface.editor.action.WATCH_FACE_EDITOR" />
 
                 <category android:name="com.google.android.wearable.watchface.category.WEARABLE_CONFIGURATION" />
                 <category android:name="android.intent.category.DEFAULT" />
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
index 1a83c6e..27670a0 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Complication.kt
@@ -655,4 +655,33 @@
             (unitSquareBounds.bottom * screen.height()).toInt()
         )
     }
+
+    @UiThread
+    internal fun dump(writer: IndentingPrintWriter) {
+        writer.println("Complication $id:")
+        writer.increaseIndent()
+        writer.println("fixedComplicationProvider=$fixedComplicationProvider")
+        writer.println("enabled=$enabled")
+        writer.println("renderer.isHighlighted=${renderer.isHighlighted}")
+        writer.println("boundsType=$boundsType")
+        writer.println("complicationConfigExtras=$complicationConfigExtras")
+        writer.println("supportedTypes=${supportedTypes.joinToString { it.toString() }}")
+        writer.println("complicationConfigExtras=$complicationConfigExtras")
+        writer.println(
+            "defaultProviderPolicy.primaryProvider=${defaultProviderPolicy.primaryProvider}"
+        )
+        writer.println(
+            "defaultProviderPolicy.secondaryProvider=${defaultProviderPolicy.secondaryProvider}"
+        )
+        writer.println(
+            "defaultProviderPolicy.systemProviderFallback=" +
+                "${defaultProviderPolicy.systemProviderFallback}"
+        )
+        writer.println("data=${renderer.getIdAndData()?.complicationData}")
+        val bounds = complicationBounds.perComplicationTypeBounds.map {
+            "${it.key} -> ${it.value}"
+        }
+        writer.println("bounds=[$bounds]")
+        writer.decreaseIndent()
+    }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
index 38aec04..112b510 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ComplicationsManager.kt
@@ -21,7 +21,6 @@
 import android.content.Context
 import android.content.Intent
 import android.icu.util.Calendar
-import android.os.Bundle
 import android.support.wearable.watchface.accessibility.AccessibilityUtils
 import android.support.wearable.watchface.accessibility.ContentDescriptionLabel
 import androidx.annotation.UiThread
@@ -68,14 +67,6 @@
          * @param complicationId The watch face's id for the complication single tapped
          */
         public fun onComplicationSingleTapped(complicationId: Int) {}
-
-        /**
-         * Called when the user double taps on a complication, launches the complication
-         * configuration activity.
-         *
-         * @param complicationId The watch face's id for the complication double tapped
-         */
-        public fun onComplicationDoubleTapped(complicationId: Int) {}
     }
 
     private lateinit var watchFaceHostApi: WatchFaceHostApi
@@ -371,48 +362,6 @@
     }
 
     /**
-     * Called when the user double taps on a complication, launches the complication
-     * configuration activity unless the complication is fixed in which case it does
-     * nothing.
-     *
-     * @param complicationId The watch face's id for the complication double tapped
-     */
-    @SuppressWarnings("SyntheticAccessor")
-    @UiThread
-    internal fun onComplicationDoubleTapped(complicationId: Int) {
-        // Check if the complication is missing permissions.
-        val complication = complications[complicationId] ?: return
-        if (complication.fixedComplicationProvider) {
-            return
-        }
-        val data = complication.renderer.getIdAndData() ?: return
-        if (data.complicationData.type == ComplicationType.NO_PERMISSION) {
-            watchFaceHostApi.getContext().startActivity(
-                ComplicationHelperActivity.createPermissionRequestHelperIntent(
-                    watchFaceHostApi.getContext(),
-                    getComponentName(watchFaceHostApi.getContext())
-                )
-            )
-            return
-        }
-        watchFaceHostApi.getContext().startActivity(
-            ComplicationHelperActivity.createProviderChooserHelperIntent(
-                watchFaceHostApi.getContext(),
-                getComponentName(watchFaceHostApi.getContext()),
-                complicationId,
-                complication.supportedTypes
-            ).addFlags(Intent.FLAG_ACTIVITY_NEW_TASK).apply {
-                complication.complicationConfigExtras?.let {
-                    replaceExtras(Bundle(it).apply { putAll(extras!!) })
-                }
-            }
-        )
-        for (complicationListener in complicationListeners) {
-            complicationListener.onComplicationDoubleTapped(complicationId)
-        }
-    }
-
-    /**
      * Adds a [TapCallback] which is called whenever the user interacts with a complication.
      */
     @UiThread
@@ -428,4 +377,14 @@
     public fun removeTapListener(tapCallback: TapCallback) {
         complicationListeners.remove(tapCallback)
     }
+
+    @UiThread
+    internal fun dump(writer: IndentingPrintWriter) {
+        writer.println("ComplicationsManager:")
+        writer.increaseIndent()
+        for ((_, complication) in complications) {
+            complication.dump(writer)
+        }
+        writer.decreaseIndent()
+    }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/IndentingPrintWriter.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/IndentingPrintWriter.kt
new file mode 100644
index 0000000..02005c5
--- /dev/null
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/IndentingPrintWriter.kt
@@ -0,0 +1,110 @@
+/*
+ * 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.watchface
+
+import android.util.Printer
+import java.io.PrintWriter
+import java.io.Writer
+
+/**
+ * Lightweight wrapper around [java.io.PrintWriter] that automatically indents newlines based
+ * on internal state.
+ *
+ * Delays writing indent until first actual write on a newline, enabling indent modification
+ * after newline.
+ */
+internal class IndentingPrintWriter(
+    writer: Writer,
+    private val singleIndent: String = "\t"
+) : Printer {
+    private val writer: PrintWriter = PrintWriter(writer)
+
+    /** Mutable version of current indent  */
+    private val indentBuilder = StringBuilder()
+
+    /** Cache of current [.indentBuilder] value  */
+    private var currentIndent: CharArray? = null
+
+    /**
+     * Flag indicating if we're currently sitting on an empty line, and that next write should be
+     * prefixed with the current indent.
+     */
+    private var emptyLine = true
+
+    /** Increases the indentation level for future lines.  */
+    fun increaseIndent() {
+        indentBuilder.append(singleIndent)
+        currentIndent = null
+    }
+
+    /** Decreases the indentation level for future lines.  */
+    fun decreaseIndent() {
+        indentBuilder.delete(0, singleIndent.length)
+        currentIndent = null
+    }
+
+    /** Prints `string`, followed by a newline.  */
+    // Printer
+    override fun println(string: String) {
+        print(string)
+        print("\n")
+    }
+
+    /** Prints `string`, or `"null"`  */
+    fun print(string: String?) {
+        val str = string ?: "null"
+        write(str, 0, str.length)
+    }
+
+    /** Ensures that all pending data is sent out to the target  */
+    fun flush() {
+        writer.flush()
+    }
+
+    private fun write(string: String, offset: Int, count: Int) {
+        val bufferEnd = offset + count
+        var lineStart = offset
+        var lineEnd = offset
+
+        // March through incoming buffer looking for newlines
+        while (lineEnd < bufferEnd) {
+            val ch = string[lineEnd++]
+            if (ch == '\n') {
+                maybeWriteIndent()
+                writer.write(string, lineStart, lineEnd - lineStart)
+                lineStart = lineEnd
+                emptyLine = true
+            }
+        }
+        if (lineStart != lineEnd) {
+            maybeWriteIndent()
+            writer.write(string, lineStart, lineEnd - lineStart)
+        }
+    }
+
+    private fun maybeWriteIndent() {
+        if (emptyLine) {
+            emptyLine = false
+            if (indentBuilder.isNotEmpty()) {
+                if (currentIndent == null) {
+                    currentIndent = indentBuilder.toString().toCharArray()
+                }
+                writer.write(currentIndent, 0, currentIndent!!.size)
+            }
+        }
+    }
+}
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
index 91e83df..0ae9d02 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/ObservableWatchData.kt
@@ -95,6 +95,14 @@
             observers.remove(observer)
         }
     }
+
+    override fun toString(): String {
+        return if (hasValue()) {
+            value.toString()
+        } else {
+            "<unset>"
+        }
+    }
 }
 
 /**
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 1589292..19d5c06 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
@@ -155,4 +155,15 @@
         selectedComplicationId,
         outlineTint
     )
+
+    internal fun dump(writer: IndentingPrintWriter) {
+        writer.println("RenderParameters:")
+        writer.increaseIndent()
+        writer.println("drawMode=${drawMode.name}")
+        writer.println("selectedComplicationId=$selectedComplicationId")
+        writer.println("outlineTint=$outlineTint")
+        val params = layerParameters.map { "${it.key} -> ${it.value.name}" }.joinToString { it }
+        writer.println("layerParameters=[$params]")
+        writer.decreaseIndent()
+    }
 }
\ No newline at end of file
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
index d2a98e8..0f5beda 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/Renderer.kt
@@ -260,6 +260,9 @@
         }
     }
 
+    @UiThread
+    internal abstract fun dump(writer: IndentingPrintWriter)
+
     /**
      * Watch faces that require [Canvas] rendering should extend their [Renderer] from this class.
      */
@@ -350,6 +353,19 @@
             bounds: Rect,
             calendar: Calendar
         )
+
+        internal override fun dump(writer: IndentingPrintWriter) {
+            writer.println("CanvasRenderer:")
+            writer.increaseIndent()
+            writer.println("canvasType=$canvasType")
+            writer.println("screenBounds=$screenBounds")
+            writer.println(
+                "interactiveDrawModeUpdateDelayMillis=$interactiveDrawModeUpdateDelayMillis"
+            )
+            writer.println("shouldAnimate=${shouldAnimate()}")
+            renderParameters.dump(writer)
+            writer.decreaseIndent()
+        }
     }
 
     /**
@@ -633,5 +649,17 @@
          */
         @UiThread
         public abstract fun render(calendar: Calendar)
+
+        internal override fun dump(writer: IndentingPrintWriter) {
+            writer.println("GlesRenderer:")
+            writer.increaseIndent()
+            writer.println("screenBounds=$screenBounds")
+            writer.println(
+                "interactiveDrawModeUpdateDelayMillis=$interactiveDrawModeUpdateDelayMillis"
+            )
+            writer.println("shouldAnimate=${shouldAnimate()}")
+            renderParameters.dump(writer)
+            writer.decreaseIndent()
+        }
     }
 }
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 118497d..02c3166 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
@@ -426,8 +426,6 @@
         CancellableUniqueTask(watchFaceHostApi.getHandler())
     private val pendingUpdateTime: CancellableUniqueTask =
         CancellableUniqueTask(watchFaceHostApi.getHandler())
-    private val pendingPostDoubleTap: CancellableUniqueTask =
-        CancellableUniqueTask(watchFaceHostApi.getHandler())
 
     internal val componentName =
         ComponentName(
@@ -721,7 +719,6 @@
     internal fun onDestroy() {
         pendingSingleTap.cancel()
         pendingUpdateTime.cancel()
-        pendingPostDoubleTap.cancel()
         renderer.onDestroy()
         watchState.isAmbient.removeObserver(ambientObserver)
         if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && !watchState.isHeadless) {
@@ -892,6 +889,7 @@
     ) {
         val tappedComplication = complicationsManager.getComplicationAt(x, y)
         if (tappedComplication == null) {
+            System.out.println("<<< tappedComplication == null")
             clearGesture()
             tapListener?.onTap(originalTapType, x, y)
             return
@@ -923,22 +921,7 @@
                     clearGesture()
                     return
                 }
-                if (pendingPostDoubleTap.isPending()) {
-                    return
-                }
-                if (pendingSingleTap.isPending()) {
-                    // The user tapped twice rapidly on the same complication so treat this as
-                    // a double tap.
-                    complicationsManager.onComplicationDoubleTapped(tappedComplication.id)
-                    clearGesture()
-
-                    // Block subsequent taps for a short time, to prevent accidental triple taps.
-                    pendingPostDoubleTap.postDelayedUnique(
-                        ViewConfiguration.getDoubleTapTimeout().toLong()
-                    ) {
-                        // NOP.
-                    }
-                } else {
+                if (!pendingSingleTap.isPending()) {
                     // Give the user immediate visual feedback, the UI feels sluggish if we defer
                     // this.
                     complicationsManager.bringAttentionToComplication(tappedComplication.id)
@@ -973,4 +956,26 @@
         lastTappedComplicationId = null
         pendingSingleTap.cancel()
     }
+
+    @UiThread
+    fun dump(writer: IndentingPrintWriter) {
+        writer.println("WatchFaceImpl ($componentName): ")
+        writer.increaseIndent()
+        writer.println("calendar=$calendar")
+        writer.println("mockTime.maxTime=${mockTime.maxTime}")
+        writer.println("mockTime.minTime=${mockTime.minTime}")
+        writer.println("mockTime.speed=${mockTime.speed}")
+        writer.println("nextDrawTimeMillis=$nextDrawTimeMillis")
+        writer.println("muteMode=$muteMode")
+        writer.println("pendingSingleTap=${pendingSingleTap.isPending()}")
+        writer.println("pendingUpdateTime=${pendingUpdateTime.isPending()}")
+        writer.println("lastTappedComplicationId=$lastTappedComplicationId")
+        writer.println("lastTappedPosition=$lastTappedPosition")
+        writer.println("userStyleRepository.userStyle=${userStyleRepository.userStyle}")
+        writer.println("userStyleRepository.schema=${userStyleRepository.schema}")
+        watchState.dump(writer)
+        complicationsManager.dump(writer)
+        renderer.dump(writer)
+        writer.decreaseIndent()
+    }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
index e9c4b6e..7105cd7 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchFaceService.kt
@@ -65,6 +65,7 @@
 import androidx.wear.watchface.data.IdAndComplicationDataWireFormat
 import androidx.wear.watchface.data.IdAndComplicationStateWireFormat
 import androidx.wear.watchface.data.SystemState
+import androidx.wear.watchface.editor.EditorService
 import androidx.wear.watchface.style.UserStyle
 import androidx.wear.watchface.style.UserStyleRepository
 import androidx.wear.watchface.style.UserStyleSetting
@@ -72,7 +73,9 @@
 import kotlinx.coroutines.CoroutineScope
 import kotlinx.coroutines.android.asCoroutineDispatcher
 import kotlinx.coroutines.launch
+import java.io.FileDescriptor
 import java.io.FileNotFoundException
+import java.io.PrintWriter
 import java.util.concurrent.CountDownLatch
 
 /** The wire format for [ComplicationData]. */
@@ -1137,6 +1140,56 @@
                 Log.e(TAG, "Failed to set accessibility labels: ", e)
             }
         }
+
+        @UiThread
+        fun dump(writer: IndentingPrintWriter) {
+            require(uiThreadHandler.looper.isCurrentThread) {
+                "dump must be called from the UIThread"
+            }
+            writer.println("WatchFaceEngine:")
+            writer.increaseIndent()
+            when {
+                this::iWatchFaceService.isInitialized -> writer.println("WSL style init flow")
+                this::watchFaceImpl.isInitialized -> writer.println("Androidx style init flow")
+                expectPreRInitFlow() -> writer.println("Expecting WSL style init")
+                else -> writer.println("Expecting androidx style style init")
+            }
+
+            if (this::iWatchFaceService.isInitialized) {
+                writer.println(
+                    "iWatchFaceService.asBinder().isBinderAlive=" +
+                        "${iWatchFaceService.asBinder().isBinderAlive}"
+                )
+                if (iWatchFaceService.asBinder().isBinderAlive) {
+                    writer.println("iWatchFaceService.apiVersion=${iWatchFaceService.apiVersion}")
+                }
+            }
+            writer.println("watchFaceInitStarted=$watchFaceInitStarted")
+            writer.println("asyncWatchFaceConstructionPending=$asyncWatchFaceConstructionPending")
+
+            if (this::interactiveInstanceId.isInitialized) {
+                writer.println("interactiveInstanceId=$interactiveInstanceId")
+            }
+
+            writer.println("frameCallbackPending=$frameCallbackPending")
+            writer.println("destroyed=$destroyed")
+
+            if (!destroyed && this::watchFaceImpl.isInitialized) {
+                watchFaceImpl.dump(writer)
+            }
+            writer.decreaseIndent()
+        }
+    }
+
+    @UiThread
+    override fun dump(fd: FileDescriptor, writer: PrintWriter, args: Array<String>) {
+        super.dump(fd, writer, args)
+        val indentingPrintWriter = IndentingPrintWriter(writer)
+        indentingPrintWriter.println("AndroidX WatchFaceService $packageName")
+        InteractiveInstanceManager.dump(indentingPrintWriter)
+        EditorService.globalEditorService.dump(indentingPrintWriter)
+        HeadlessWatchFaceImpl.dump(indentingPrintWriter)
+        indentingPrintWriter.flush()
     }
 }
 
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
index 8b664c3..d0cb375 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/WatchState.kt
@@ -18,6 +18,7 @@
 
 import android.app.NotificationManager
 import androidx.annotation.RestrictTo
+import androidx.annotation.UiThread
 
 /**
  * Describes the current state of the wearable including some hardware details such as whether or
@@ -72,7 +73,23 @@
 
     /** Whether or not this is a headless watchface. */
     public val isHeadless: Boolean
-)
+) {
+    @UiThread
+    internal fun dump(writer: IndentingPrintWriter) {
+        writer.println("WatchState:")
+        writer.increaseIndent()
+        writer.println("interruptionFilter=$interruptionFilter")
+        writer.println("isAmbient=$isAmbient")
+        writer.println("isBatteryLowAndNotCharging=$isBatteryLowAndNotCharging")
+        writer.println("isVisible=$isVisible")
+        writer.println("hasLowBitAmbient=$hasLowBitAmbient")
+        writer.println("hasBurnInProtection=$hasBurnInProtection")
+        writer.println("analogPreviewReferenceTimeMillis=$analogPreviewReferenceTimeMillis")
+        writer.println("digitalPreviewReferenceTimeMillis=$digitalPreviewReferenceTimeMillis")
+        writer.println("isHeadless=$isHeadless")
+        writer.decreaseIndent()
+    }
+}
 
 /** @hide */
 @RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
index b73c697..a468194 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/HeadlessWatchFaceImpl.kt
@@ -18,6 +18,8 @@
 
 import android.os.Handler
 import androidx.annotation.RequiresApi
+import androidx.annotation.UiThread
+import androidx.wear.watchface.IndentingPrintWriter
 import androidx.wear.watchface.WatchFaceService
 import androidx.wear.watchface.control.data.ComplicationScreenshotParams
 import androidx.wear.watchface.control.data.WatchfaceScreenshotParams
@@ -33,6 +35,32 @@
     private val uiThreadHandler: Handler
 ) : IHeadlessWatchFace.Stub() {
 
+    internal companion object {
+        @UiThread
+        fun dump(indentingPrintWriter: IndentingPrintWriter) {
+            indentingPrintWriter.println("HeadlessWatchFace instances:")
+            indentingPrintWriter.increaseIndent()
+            for (instance in headlessInstances) {
+                require(instance.uiThreadHandler.looper.isCurrentThread) {
+                    "dump must be called from the UIThread"
+                }
+                indentingPrintWriter.println("HeadlessWatchFaceImpl:")
+                indentingPrintWriter.increaseIndent()
+                instance.engine?.dump(indentingPrintWriter)
+                indentingPrintWriter.decreaseIndent()
+            }
+            indentingPrintWriter.decreaseIndent()
+        }
+
+        private val headlessInstances = HashSet<HeadlessWatchFaceImpl>()
+    }
+
+    init {
+        uiThreadHandler.runOnHandler {
+            headlessInstances.add(this)
+        }
+    }
+
     override fun getApiVersion() = IHeadlessWatchFace.API_VERSION
 
     override fun takeWatchFaceScreenshot(params: WatchfaceScreenshotParams) =
@@ -50,7 +78,10 @@
         engine!!.watchFaceImpl.userStyleRepository.schema.toWireFormat()
 
     override fun release() {
-        engine?.onDestroy()
-        engine = null
+        uiThreadHandler.runOnHandler {
+            headlessInstances.remove(this)
+            engine?.onDestroy()
+            engine = null
+        }
     }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
index ed13e05..d18f6e5 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/control/InteractiveInstanceManager.kt
@@ -18,6 +18,8 @@
 
 import android.annotation.SuppressLint
 import androidx.annotation.RequiresApi
+import androidx.annotation.UiThread
+import androidx.wear.watchface.IndentingPrintWriter
 import androidx.wear.watchface.control.data.WallpaperInteractiveWatchFaceInstanceParams
 
 /** Keeps track of [InteractiveWatchFaceImpl]s. */
@@ -27,7 +29,17 @@
     private class RefCountedInteractiveWatchFaceInstance(
         val impl: InteractiveWatchFaceImpl,
         var refcount: Int
-    )
+    ) {
+        @UiThread
+        fun dump(writer: IndentingPrintWriter) {
+            writer.println("InteractiveInstanceManager:")
+            writer.increaseIndent()
+            writer.println("impl.instanceId=${impl.instanceId}")
+            writer.println("refcount=$refcount")
+            impl.engine.dump(writer)
+            writer.decreaseIndent()
+        }
+    }
 
     class PendingWallpaperInteractiveWatchFaceInstance(
         val params: WallpaperInteractiveWatchFaceInstanceParams,
@@ -104,5 +116,22 @@
                     return returnValue
                 }
             }
+
+        @UiThread
+        fun dump(writer: IndentingPrintWriter) {
+            writer.println("InteractiveInstanceManager instances:")
+            writer.increaseIndent()
+            pendingWallpaperInteractiveWatchFaceInstance?.let {
+                writer.println(
+                    "Pending WallpaperInteractiveWatchFaceInstance id ${it.params.instanceId}"
+                )
+            }
+            synchronized(pendingWallpaperInteractiveWatchFaceInstanceLock) {
+                for ((_, value) in instances) {
+                    value.dump(writer)
+                }
+            }
+            writer.decreaseIndent()
+        }
     }
 }
diff --git a/wear/wear-watchface/src/main/java/androidx/wear/watchface/editor/EditorService.kt b/wear/wear-watchface/src/main/java/androidx/wear/watchface/editor/EditorService.kt
index 95b06d6..3138b6a 100644
--- a/wear/wear-watchface/src/main/java/androidx/wear/watchface/editor/EditorService.kt
+++ b/wear/wear-watchface/src/main/java/androidx/wear/watchface/editor/EditorService.kt
@@ -18,6 +18,7 @@
 
 import android.os.IBinder
 import androidx.annotation.RestrictTo
+import androidx.wear.watchface.IndentingPrintWriter
 import androidx.wear.watchface.editor.data.EditorStateWireFormat
 
 /**
@@ -72,4 +73,18 @@
             }
         }
     }
+
+    internal fun dump(writer: IndentingPrintWriter) {
+        writer.println("EditorService:")
+        writer.increaseIndent()
+        synchronized(lock) {
+            for ((id, observer) in observers) {
+                writer.println("id = $id, alive = ${observer.asBinder().isBinderAlive}")
+                if (observer.asBinder().isBinderAlive) {
+                    writer.println("$apiVersion = {observer.apiVersion}")
+                }
+            }
+        }
+        writer.decreaseIndent()
+    }
 }
\ No newline at end of file
diff --git a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
index 657398d..fdc5d9b 100644
--- a/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
+++ b/wear/wear-watchface/src/test/java/androidx/wear/watchface/TestCommon.kt
@@ -48,8 +48,8 @@
     private val handler: Handler,
     private val tapListener: WatchFace.TapListener?
 ) : WatchFaceService() {
+    var singleTapCount = 0
     var complicationSingleTapped: Int? = null
-    var complicationDoubleTapped: Int? = null
     var complicationSelected: Int? = null
     var mockSystemTimeMillis = 0L
     var lastUserStyle: UserStyle? = null
@@ -67,10 +67,7 @@
             object : ComplicationsManager.TapCallback {
                 override fun onComplicationSingleTapped(complicationId: Int) {
                     complicationSingleTapped = complicationId
-                }
-
-                override fun onComplicationDoubleTapped(complicationId: Int) {
-                    complicationDoubleTapped = complicationId
+                    singleTapCount++
                 }
             })
     }
@@ -84,7 +81,6 @@
 
     fun clearTappedState() {
         complicationSingleTapped = null
-        complicationDoubleTapped = null
     }
 
     init {
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 47cc2ac..8bb7060 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
@@ -92,7 +92,7 @@
 
 @Config(manifest = Config.NONE)
 @RunWith(WatchFaceTestRunner::class)
-class WatchFaceServiceTest {
+public class WatchFaceServiceTest {
 
     private val handler = mock<Handler>()
     private val iWatchFaceService = mock<IWatchFaceService>()
@@ -104,7 +104,7 @@
         `when`(surfaceHolder.surfaceFrame).thenReturn(ONE_HUNDRED_BY_ONE_HUNDRED_RECT)
     }
 
-    companion object {
+    private companion object {
         val ONE_HUNDRED_BY_ONE_HUNDRED_RECT = Rect(0, 0, 100, 100)
     }
 
@@ -444,7 +444,7 @@
     }
 
     @Before
-    fun setUp() {
+    public fun setUp() {
         `when`(handler.getLooper()).thenReturn(Looper.myLooper())
 
         // Capture tasks posted to mHandler and insert in mPendingTasks which is under our control.
@@ -476,7 +476,7 @@
     }
 
     @After
-    fun validate() {
+    public fun validate() {
         if (this::interactiveWatchFaceInstanceWCS.isInitialized) {
             interactiveWatchFaceInstanceWCS.release()
         }
@@ -489,7 +489,7 @@
     }
 
     @Test
-    fun maybeUpdateDrawMode_setsCorrectDrawMode() {
+    public fun maybeUpdateDrawMode_setsCorrectDrawMode() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -528,7 +528,7 @@
     }
 
     @Test
-    fun onDraw_calendar_setFromSystemTime() {
+    public fun onDraw_calendar_setFromSystemTime() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -542,7 +542,7 @@
     }
 
     @Test
-    fun onDraw_calendar_affectedCorrectly_with2xMockTime() {
+    public fun onDraw_calendar_affectedCorrectly_with2xMockTime() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -569,7 +569,7 @@
     }
 
     @Test
-    fun onDraw_calendar_affectedCorrectly_withMockTimeWrapping() {
+    public fun onDraw_calendar_affectedCorrectly_withMockTimeWrapping() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -641,7 +641,7 @@
     }
 
     @Test
-    fun singleTaps_correctlyDetected_and_highlightComplications() {
+    public fun singleTaps_correctlyDetected_and_highlightComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -678,82 +678,11 @@
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
         assertThat(complicationDrawableLeft.isHighlighted).isFalse()
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(2)
     }
 
     @Test
-    fun doubleTaps_correctlyDetected_and_highlightComplications() {
-        initEngine(
-            WatchFaceType.ANALOG,
-            listOf(leftComplication, rightComplication),
-            UserStyleSchema(emptyList())
-        )
-
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-        assertThat(complicationDrawableRight.isHighlighted).isFalse()
-
-        // Tap left complication.
-        doubleTapAt(30, 50, ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isEqualTo(LEFT_COMPLICATION_ID)
-        assertThat(complicationDrawableLeft.isHighlighted).isTrue()
-
-        runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-
-        // Tap right complication.
-        testWatchFaceService.reset()
-        doubleTapAt(70, 50, ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isEqualTo(RIGHT_COMPLICATION_ID)
-        assertThat(complicationDrawableRight.isHighlighted).isTrue()
-
-        runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableRight.isHighlighted).isFalse()
-
-        // Tap on blank space.
-        testWatchFaceService.reset()
-        doubleTapAt(1, 1, ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
-
-        runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
-        assertThat(complicationDrawableLeft.isHighlighted).isFalse()
-    }
-
-    @Test
-    fun doubleTap_onFixedComplication_ignored() {
-        val fixedLeftComplication =
-            Complication.createRoundRectComplicationBuilder(
-                LEFT_COMPLICATION_ID,
-                CanvasComplicationDrawable(
-                    complicationDrawableLeft,
-                    watchState.asWatchState()
-                ).apply {
-                    setIdAndData(createIdAndComplicationData(LEFT_COMPLICATION_ID), false)
-                },
-                listOf(
-                    ComplicationType.RANGED_VALUE,
-                    ComplicationType.LONG_TEXT,
-                    ComplicationType.SHORT_TEXT,
-                    ComplicationType.MONOCHROMATIC_IMAGE,
-                    ComplicationType.SMALL_IMAGE
-                ),
-                DefaultComplicationProviderPolicy(SystemProviders.SUNRISE_SUNSET),
-                ComplicationBounds(RectF(0.2f, 0.4f, 0.4f, 0.6f))
-            ).setDefaultProviderType(ComplicationType.SHORT_TEXT)
-                .setFixedComplicationProvider(true)
-                .build()
-
-        initEngine(
-            WatchFaceType.ANALOG,
-            listOf(fixedLeftComplication, rightComplication),
-            UserStyleSchema(emptyList())
-        )
-
-        // Double tap left complication.
-        doubleTapAt(30, 50, ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
-    }
-
-    @Test
-    fun fastTap_onDifferentComplications_ignored() {
+    public fun fastTap_onDifferentComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -768,20 +697,20 @@
         runPostedTasksFor(ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
         tapAt(70, 50)
 
-        // Both complications get temporarily highlighted but neither onComplicationSingleTapped
-        // nor onComplicationDoubleTapped fire.
+        // Both complications get temporarily highlighted but only the second one registers a tap.
         assertThat(complicationDrawableLeft.isHighlighted).isTrue()
         assertThat(complicationDrawableRight.isHighlighted).isTrue()
         assertThat(testWatchFaceService.complicationSingleTapped).isNull()
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
 
         runPostedTasksFor(WatchFaceImpl.CANCEL_COMPLICATION_HIGHLIGHTED_DELAY_MS)
         assertThat(complicationDrawableLeft.isHighlighted).isFalse()
         assertThat(complicationDrawableRight.isHighlighted).isFalse()
+        assertThat(testWatchFaceService.complicationSingleTapped).isEqualTo(RIGHT_COMPLICATION_ID)
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(1)
     }
 
     @Test
-    fun slow_doubleTap_recogisedAsSingleTap() {
+    public fun slow_doubleTap_recognisedAsSingleTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -795,11 +724,11 @@
         doubleTapAt(30, 50, ViewConfiguration.getDoubleTapTimeout().toLong() * 2)
 
         assertThat(testWatchFaceService.complicationSingleTapped).isEqualTo(LEFT_COMPLICATION_ID)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(1)
     }
 
     @Test
-    fun tripleTap_recogisedAsDoubleTap() {
+    public fun tripleTap_recognisedAsSingleTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -812,12 +741,12 @@
         // Quickly tap left complication thrice.
         tripleTapAt(30, 50, ViewConfiguration.getDoubleTapTimeout().toLong() / 2)
 
-        assertThat(testWatchFaceService.complicationSingleTapped).isNull()
-        assertThat(testWatchFaceService.complicationDoubleTapped).isEqualTo(LEFT_COMPLICATION_ID)
+        assertThat(testWatchFaceService.complicationSingleTapped).isEqualTo(LEFT_COMPLICATION_ID)
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(1)
     }
 
     @Test
-    fun tapCancel_after_tapDown_at_same_location_HandledAsSingleTap() {
+    public fun tapCancel_after_tapDown_at_same_location_HandledAsSingleTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -832,7 +761,7 @@
     }
 
     @Test
-    fun tapDown_then_tapDown_tapCancel_HandledAsSingleTap() {
+    public fun tapDown_then_tapDown_tapCancel_HandledAsSingleTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -850,10 +779,11 @@
         watchFaceImpl.onTapCommand(TapType.TOUCH_CANCEL, 70, 50)
         runPostedTasksFor(ViewConfiguration.getDoubleTapTimeout().toLong())
         assertThat(testWatchFaceService.complicationSingleTapped).isEqualTo(RIGHT_COMPLICATION_ID)
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(1)
     }
 
     @Test
-    fun tapDown_tapCancel_different_positions_CancelsTap() {
+    public fun tapDown_tapCancel_different_positions_CancelsTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -868,11 +798,11 @@
 
         runPostedTasksFor(ViewConfiguration.getDoubleTapTimeout().toLong())
         assertThat(testWatchFaceService.complicationSingleTapped).isNull()
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(0)
     }
 
     @Test
-    fun singleTap_recognisedAfterTripleTap() {
+    public fun singleTap_recognisedAfterTripleTap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -893,11 +823,11 @@
         assertThat(complicationDrawableRight.isHighlighted).isTrue()
         runPostedTasksFor(ViewConfiguration.getDoubleTapTimeout().toLong())
         assertThat(testWatchFaceService.complicationSingleTapped).isEqualTo(RIGHT_COMPLICATION_ID)
-        assertThat(testWatchFaceService.complicationDoubleTapped).isNull()
+        assertThat(testWatchFaceService.singleTapCount).isEqualTo(3)
     }
 
     @Test
-    fun tapListener_tap() {
+    public fun tapListener_tap() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -913,7 +843,7 @@
     }
 
     @Test
-    fun tapListener_tapComplication() {
+    public fun tapListener_tapComplication() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -929,7 +859,7 @@
     }
 
     @Test
-    fun interactiveFrameRate_reducedWhenBatteryLow() {
+    public fun interactiveFrameRate_reducedWhenBatteryLow() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -954,7 +884,7 @@
     }
 
     @Test
-    fun interactiveFrameRate_restoreWhenPowerConnectedAfterBatteryLow() {
+    public fun interactiveFrameRate_restoreWhenPowerConnectedAfterBatteryLow() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -981,7 +911,7 @@
     }
 
     @Test
-    fun computeDelayTillNextFrame_accountsForSlowDraw() {
+    public fun computeDelayTillNextFrame_accountsForSlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -998,7 +928,7 @@
     }
 
     @Test
-    fun computeDelayTillNextFrame_dropsFramesForVerySlowDraw() {
+    public fun computeDelayTillNextFrame_dropsFramesForVerySlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1014,7 +944,7 @@
     }
 
     @Test
-    fun computeDelayTillNextFrame_perservesPhaseForVerySlowDraw() {
+    public fun computeDelayTillNextFrame_perservesPhaseForVerySlowDraw() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1033,7 +963,7 @@
     }
 
     @Test
-    fun computeDelayTillNextFrame_beginFrameTimeInTheFuture() {
+    public fun computeDelayTillNextFrame_beginFrameTimeInTheFuture() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1049,7 +979,7 @@
     }
 
     @Test
-    fun getComplicationIdAt_returnsCorrectComplications() {
+    public fun getComplicationIdAt_returnsCorrectComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1067,7 +997,7 @@
     }
 
     @Test
-    fun getBackgroundComplicationId_returnsCorrectId() {
+    public fun getBackgroundComplicationId_returnsCorrectId() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1099,7 +1029,7 @@
     }
 
     @Test
-    fun getStoredUserStyleNotSupported_userStyle_isPersisted() {
+    public fun getStoredUserStyleNotSupported_userStyle_isPersisted() {
         // The style should get persisted in a file because this test is set up using the legacy
         // Wear 2.0 APIs.
         initEngine(
@@ -1158,7 +1088,7 @@
     }
 
     @Test
-    fun initWallpaperInteractiveWatchFaceInstanceWithUserStyle() {
+    public fun initWallpaperInteractiveWatchFaceInstanceWithUserStyle() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -1194,7 +1124,7 @@
     }
 
     @Test
-    fun initWallpaperInteractiveWatchFaceInstanceWithUserStyleThatDoesntMatchSchema() {
+    public fun initWallpaperInteractiveWatchFaceInstanceWithUserStyleThatDoesntMatchSchema() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -1218,7 +1148,7 @@
     }
 
     @Test
-    fun wear2ImmutablePropertiesSetCorrectly() {
+    public fun wear2ImmutablePropertiesSetCorrectly() {
         initEngine(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -1233,7 +1163,7 @@
     }
 
     @Test
-    fun wear2ImmutablePropertiesSetCorrectly2() {
+    public fun wear2ImmutablePropertiesSetCorrectly2() {
         initEngine(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -1248,7 +1178,7 @@
     }
 
     @Test
-    fun wallpaperInteractiveWatchFaceImmutablePropertiesSetCorrectly() {
+    public fun wallpaperInteractiveWatchFaceImmutablePropertiesSetCorrectly() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -1272,7 +1202,7 @@
     }
 
     @Test
-    fun onCreate_calls_setActiveComplications_withCorrectIDs() {
+    public fun onCreate_calls_setActiveComplications_withCorrectIDs() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication, backgroundComplication),
@@ -1288,7 +1218,7 @@
     }
 
     @Test
-    fun onCreate_calls_setContentDescriptionLabels_withCorrectArgs() {
+    public fun onCreate_calls_setContentDescriptionLabels_withCorrectArgs() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication, backgroundComplication),
@@ -1306,7 +1236,7 @@
     }
 
     @Test
-    fun setActiveComplications_afterDisablingSeveralComplications() {
+    public fun setActiveComplications_afterDisablingSeveralComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication, backgroundComplication),
@@ -1321,7 +1251,7 @@
     }
 
     @Test
-    fun initial_setContentDescriptionLabels_afterDisablingSeveralComplications() {
+    public fun initial_setContentDescriptionLabels_afterDisablingSeveralComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication, backgroundComplication),
@@ -1351,7 +1281,7 @@
     }
 
     @Test
-    fun moveComplications() {
+    public fun moveComplications() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1411,7 +1341,7 @@
     }
 
     @Test
-    fun getOptionForIdentifier_ListViewStyleSetting() {
+    public fun getOptionForIdentifier_ListViewStyleSetting() {
         // Check the correct Options are returned for known option names.
         assertThat(colorStyleSetting.getOptionForId(redStyleOption.id)).isEqualTo(
             redStyleOption
@@ -1428,7 +1358,7 @@
     }
 
     @Test
-    fun centerX_and_centerY_containUpToDateValues() {
+    public fun centerX_and_centerY_containUpToDateValues() {
         initEngine(WatchFaceType.ANALOG, emptyList(), UserStyleSchema(emptyList()))
 
         assertThat(renderer.centerX).isEqualTo(50f)
@@ -1450,7 +1380,7 @@
     }
 
     @Test
-    fun requestStyleBeforeSetBinder() {
+    public fun requestStyleBeforeSetBinder() {
         var userStyleRepository =
             UserStyleRepository(UserStyleSchema(emptyList()))
         var testRenderer = TestRenderer(
@@ -1489,7 +1419,7 @@
     }
 
     @Test
-    fun defaultProvidersWithFallbacks_newApi() {
+    public fun defaultProvidersWithFallbacks_newApi() {
         val provider1 = ComponentName("com.app1", "com.app1.App1")
         val provider2 = ComponentName("com.app2", "com.app2.App2")
         val complication = Complication.createRoundRectComplicationBuilder(
@@ -1517,7 +1447,7 @@
     }
 
     @Test
-    fun defaultProvidersWithFallbacks_oldApi() {
+    public fun defaultProvidersWithFallbacks_oldApi() {
         val provider1 = ComponentName("com.app1", "com.app1.App1")
         val provider2 = ComponentName("com.app2", "com.app2.App2")
         val complication = Complication.createRoundRectComplicationBuilder(
@@ -1553,7 +1483,7 @@
     }
 
     @Test
-    fun previewReferenceTimeMillisAnalog() {
+    public fun previewReferenceTimeMillisAnalog() {
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
             "interactiveInstanceId",
             DeviceConfig(
@@ -1583,7 +1513,7 @@
     }
 
     @Test
-    fun previewReferenceTimeMillisDigital() {
+    public fun previewReferenceTimeMillisDigital() {
         val instanceParams = WallpaperInteractiveWatchFaceInstanceParams(
             "interactiveInstanceId",
             DeviceConfig(
@@ -1613,7 +1543,7 @@
     }
 
     @Test
-    fun getComplicationDetails() {
+    public fun getComplicationDetails() {
         initEngine(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication, backgroundComplication),
@@ -1669,7 +1599,7 @@
     }
 
     @Test
-    fun shouldAnimateOverrideControlsEnteringAmbientMode() {
+    public fun shouldAnimateOverrideControlsEnteringAmbientMode() {
         var userStyleRepository = UserStyleRepository(UserStyleSchema(emptyList()))
         var testRenderer = object : TestRenderer(
             surfaceHolder,
@@ -1712,7 +1642,7 @@
     }
 
     @Test
-    fun complicationsUserStyleSettingSelectionAppliesChanges() {
+    public fun complicationsUserStyleSettingSelectionAppliesChanges() {
         initEngine(
             WatchFaceType.DIGITAL,
             listOf(leftComplication, rightComplication),
@@ -1757,7 +1687,7 @@
     }
 
     @Test
-    fun partialComplicationOverrides() {
+    public fun partialComplicationOverrides() {
         val bothComplicationsOption = ComplicationsOption(
             LEFT_AND_RIGHT_COMPLICATIONS,
             "Left And Right",
@@ -1832,7 +1762,7 @@
     }
 
     @Test
-    fun partialComplicationOverrideAppliedToInitialStyle() {
+    public fun partialComplicationOverrideAppliedToInitialStyle() {
         val bothComplicationsOption = ComplicationsOption(
             LEFT_AND_RIGHT_COMPLICATIONS,
             "Left And Right",
@@ -1870,7 +1800,7 @@
     }
 
     @Test
-    fun observeComplicationData() {
+    public fun observeComplicationData() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             listOf(leftComplication, rightComplication),
@@ -1924,7 +1854,7 @@
     }
 
     @Test
-    fun complication_isActiveAt() {
+    public fun complication_isActiveAt() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             listOf(leftComplication),
@@ -1990,7 +1920,7 @@
     }
 
     @Test
-    fun updateInvalidCOmpliationIdDoesNotCrash() {
+    public fun updateInvalidCOmpliationIdDoesNotCrash() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             listOf(leftComplication),
@@ -2023,7 +1953,7 @@
     }
 
     @Test
-    fun invalidateRendererBeforeFullInit() {
+    public fun invalidateRendererBeforeFullInit() {
         renderer = TestRenderer(
             surfaceHolder,
             UserStyleRepository(UserStyleSchema(emptyList())),
@@ -2036,7 +1966,7 @@
     }
 
     @Test
-    fun watchStateObservableWatchDataMembersHaveValues() {
+    public fun watchStateObservableWatchDataMembersHaveValues() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -2062,7 +1992,7 @@
     }
 
     @Test
-    fun setIsBatteryLowAndNotChargingFromBatteryStatus() {
+    public fun setIsBatteryLowAndNotChargingFromBatteryStatus() {
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
             emptyList(),
@@ -2116,7 +2046,7 @@
     }
 
     @Test
-    fun isAmbientInitalisedEvenWithoutPropertiesSent() {
+    public fun isAmbientInitalisedEvenWithoutPropertiesSent() {
         userStyleRepository = UserStyleRepository(UserStyleSchema(emptyList()))
         complicationsManager = ComplicationsManager(emptyList(), userStyleRepository)
         renderer = TestRenderer(
@@ -2146,7 +2076,7 @@
     }
 
     @Test
-    fun onDestroy_clearsInstanceRecord() {
+    public fun onDestroy_clearsInstanceRecord() {
         val instanceId = "interactiveInstanceId"
         initWallpaperInteractiveWatchFaceInstance(
             WatchFaceType.ANALOG,
@@ -2171,7 +2101,7 @@
     }
 
     @Test
-    fun renderParameters_secondaryConstructor() {
+    public fun renderParameters_secondaryConstructor() {
         val params = RenderParameters(
             DrawMode.INTERACTIVE,
             mapOf(
@@ -2188,7 +2118,7 @@
     }
 
     @Test
-    fun renderParameters_secondaryConstructorEnforcesNoDrawOutlined() {
+    public fun renderParameters_secondaryConstructorEnforcesNoDrawOutlined() {
         try {
             RenderParameters(
                 DrawMode.INTERACTIVE,